diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/BUILD b/staging/src/k8s.io/apiserver/pkg/server/options/BUILD index 273d132447f..1e3df709a5a 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/options/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/server/options/BUILD @@ -80,18 +80,26 @@ go_test( name = "go_default_test", srcs = [ "admission_test.go", + "api_enablement_test.go", "audit_test.go", + "authentication_test.go", + "etcd_test.go", + "server_run_options_test.go", "serving_test.go", ], data = glob(["testdata/**"]), embed = [":go_default_library"], deps = [ "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/errors:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/version:go_default_library", "//staging/src/k8s.io/apiserver/pkg/admission:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/authentication/authenticatorfactory:go_default_library", "//staging/src/k8s.io/apiserver/pkg/server:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/storage/storagebackend:go_default_library", "//staging/src/k8s.io/apiserver/pkg/util/flag:go_default_library", "//staging/src/k8s.io/client-go/discovery:go_default_library", "//staging/src/k8s.io/client-go/rest:go_default_library", diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/api_enablement_test.go b/staging/src/k8s.io/apiserver/pkg/server/options/api_enablement_test.go new file mode 100644 index 00000000000..e08fcd2a1fb --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/server/options/api_enablement_test.go @@ -0,0 +1,84 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package options + +import ( + "strings" + "testing" + + utilerrors "k8s.io/apimachinery/pkg/util/errors" + utilflag "k8s.io/apiserver/pkg/util/flag" +) + +type fakeGroupRegisty struct{} + +func (f fakeGroupRegisty) IsGroupRegistered(group string) bool { + return group == "apiregistration.k8s.io" +} + +func TestAPIEnablementOptionsValidate(t *testing.T) { + testCases := []struct { + name string + testOptions *APIEnablementOptions + expectErr string + }{ + { + name: "test when options is nil", + }, + { + name: "test when invalid key with only api/all=false", + testOptions: &APIEnablementOptions{ + RuntimeConfig: utilflag.ConfigurationMap{"api/all": "false"}, + }, + expectErr: "invalid key with only api/all=false", + }, + { + name: "test when ConfigurationMap key is invalid", + testOptions: &APIEnablementOptions{ + RuntimeConfig: utilflag.ConfigurationMap{"apiall": "false"}, + }, + expectErr: "runtime-config invalid key", + }, + { + name: "test when unknown api groups", + testOptions: &APIEnablementOptions{ + RuntimeConfig: utilflag.ConfigurationMap{"api/v1": "true"}, + }, + expectErr: "unknown api groups", + }, + { + name: "test when valid api groups", + testOptions: &APIEnablementOptions{ + RuntimeConfig: utilflag.ConfigurationMap{"apiregistration.k8s.io/v1beta1": "true"}, + }, + }, + } + testGroupRegisty := fakeGroupRegisty{} + + for _, testcase := range testCases { + t.Run(testcase.name, func(t *testing.T) { + errs := testcase.testOptions.Validate(testGroupRegisty) + if len(testcase.expectErr) != 0 && !strings.Contains(utilerrors.NewAggregate(errs).Error(), testcase.expectErr) { + t.Errorf("got err: %v, expected err: %s", errs, testcase.expectErr) + } + + if len(testcase.expectErr) == 0 && len(errs) != 0 { + t.Errorf("got err: %s, expected err nil", errs) + } + }) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/authentication_test.go b/staging/src/k8s.io/apiserver/pkg/server/options/authentication_test.go new file mode 100644 index 00000000000..3b2a581f2cc --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/server/options/authentication_test.go @@ -0,0 +1,68 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package options + +import ( + "reflect" + "testing" + + "k8s.io/apiserver/pkg/authentication/authenticatorfactory" +) + +func TestToAuthenticationRequestHeaderConfig(t *testing.T) { + testCases := []struct { + name string + testOptions *RequestHeaderAuthenticationOptions + expectConfig *authenticatorfactory.RequestHeaderConfig + }{ + { + name: "test when ClientCAFile is nil", + testOptions: &RequestHeaderAuthenticationOptions{ + UsernameHeaders: []string{"x-remote-user"}, + GroupHeaders: []string{"x-remote-group"}, + ExtraHeaderPrefixes: []string{"x-remote-extra-"}, + AllowedNames: []string{"kube-aggregator"}, + }, + }, + { + name: "test when ClientCAFile is not nil", + testOptions: &RequestHeaderAuthenticationOptions{ + ClientCAFile: "/testClientCAFile", + UsernameHeaders: []string{"x-remote-user"}, + GroupHeaders: []string{"x-remote-group"}, + ExtraHeaderPrefixes: []string{"x-remote-extra-"}, + AllowedNames: []string{"kube-aggregator"}, + }, + expectConfig: &authenticatorfactory.RequestHeaderConfig{ + UsernameHeaders: []string{"x-remote-user"}, + GroupHeaders: []string{"x-remote-group"}, + ExtraHeaderPrefixes: []string{"x-remote-extra-"}, + ClientCA: "/testClientCAFile", + AllowedClientNames: []string{"kube-aggregator"}, + }, + }, + } + + for _, testcase := range testCases { + t.Run(testcase.name, func(t *testing.T) { + resultConfig := testcase.testOptions.ToAuthenticationRequestHeaderConfig() + if !reflect.DeepEqual(resultConfig, testcase.expectConfig) { + t.Errorf("got RequestHeaderConfig: %#v, expected RequestHeaderConfig: %#v", resultConfig, testcase.expectConfig) + } + }) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/etcd_test.go b/staging/src/k8s.io/apiserver/pkg/server/options/etcd_test.go new file mode 100644 index 00000000000..27fc3c4aff7 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/server/options/etcd_test.go @@ -0,0 +1,196 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package options + +import ( + "strings" + "testing" + "time" + + "k8s.io/apimachinery/pkg/runtime/schema" + utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/apiserver/pkg/storage/storagebackend" +) + +func TestEtcdOptionsValidate(t *testing.T) { + testCases := []struct { + name string + testOptions *EtcdOptions + expectErr string + }{ + { + name: "test when ServerList is not specified", + testOptions: &EtcdOptions{ + StorageConfig: storagebackend.Config{ + Type: "etcd2", + ServerList: nil, + Prefix: "/registry", + DeserializationCacheSize: 0, + Quorum: false, + KeyFile: "/var/run/kubernetes/etcd.key", + CAFile: "/var/run/kubernetes/etcdca.crt", + CertFile: "/var/run/kubernetes/etcdce.crt", + CompactionInterval: storagebackend.DefaultCompactInterval, + CountMetricPollPeriod: time.Minute, + }, + DefaultStorageMediaType: "application/vnd.kubernetes.protobuf", + DeleteCollectionWorkers: 1, + EnableGarbageCollection: true, + EnableWatchCache: true, + DefaultWatchCacheSize: 100, + EtcdServersOverrides: []string{"/events#http://127.0.0.1:4002"}, + }, + expectErr: "--etcd-servers must be specified", + }, + { + name: "test when storage-backend is invalid", + testOptions: &EtcdOptions{ + StorageConfig: storagebackend.Config{ + Type: "etcd4", + ServerList: []string{"http://127.0.0.1"}, + Prefix: "/registry", + DeserializationCacheSize: 0, + Quorum: false, + KeyFile: "/var/run/kubernetes/etcd.key", + CAFile: "/var/run/kubernetes/etcdca.crt", + CertFile: "/var/run/kubernetes/etcdce.crt", + CompactionInterval: storagebackend.DefaultCompactInterval, + CountMetricPollPeriod: time.Minute, + }, + DefaultStorageMediaType: "application/vnd.kubernetes.protobuf", + DeleteCollectionWorkers: 1, + EnableGarbageCollection: true, + EnableWatchCache: true, + DefaultWatchCacheSize: 100, + EtcdServersOverrides: []string{"/events#http://127.0.0.1:4002"}, + }, + expectErr: "--storage-backend invalid, must be 'etcd3' or 'etcd2'. If not specified, it will default to 'etcd3'", + }, + { + name: "test when etcd-servers-overrides is invalid", + testOptions: &EtcdOptions{ + StorageConfig: storagebackend.Config{ + Type: "etcd3", + ServerList: []string{"http://127.0.0.1"}, + Prefix: "/registry", + DeserializationCacheSize: 0, + Quorum: false, + KeyFile: "/var/run/kubernetes/etcd.key", + CAFile: "/var/run/kubernetes/etcdca.crt", + CertFile: "/var/run/kubernetes/etcdce.crt", + CompactionInterval: storagebackend.DefaultCompactInterval, + CountMetricPollPeriod: time.Minute, + }, + DefaultStorageMediaType: "application/vnd.kubernetes.protobuf", + DeleteCollectionWorkers: 1, + EnableGarbageCollection: true, + EnableWatchCache: true, + DefaultWatchCacheSize: 100, + EtcdServersOverrides: []string{"/events/http://127.0.0.1:4002"}, + }, + expectErr: "--etcd-servers-overrides invalid, must be of format: group/resource#servers, where servers are URLs, semicolon separated", + }, + { + name: "test when EtcdOptions is valid", + testOptions: &EtcdOptions{ + StorageConfig: storagebackend.Config{ + Type: "etcd3", + ServerList: []string{"http://127.0.0.1"}, + Prefix: "/registry", + DeserializationCacheSize: 0, + Quorum: false, + KeyFile: "/var/run/kubernetes/etcd.key", + CAFile: "/var/run/kubernetes/etcdca.crt", + CertFile: "/var/run/kubernetes/etcdce.crt", + CompactionInterval: storagebackend.DefaultCompactInterval, + CountMetricPollPeriod: time.Minute, + }, + DefaultStorageMediaType: "application/vnd.kubernetes.protobuf", + DeleteCollectionWorkers: 1, + EnableGarbageCollection: true, + EnableWatchCache: true, + DefaultWatchCacheSize: 100, + EtcdServersOverrides: []string{"/events#http://127.0.0.1:4002"}, + }, + }, + } + + for _, testcase := range testCases { + t.Run(testcase.name, func(t *testing.T) { + errs := testcase.testOptions.Validate() + if len(testcase.expectErr) != 0 && !strings.Contains(utilerrors.NewAggregate(errs).Error(), testcase.expectErr) { + t.Errorf("got err: %v, expected err: %s", errs, testcase.expectErr) + } + if len(testcase.expectErr) == 0 && len(errs) != 0 { + t.Errorf("got err: %s, expected err nil", errs) + } + }) + } +} + +func TestParseWatchCacheSizes(t *testing.T) { + testCases := []struct { + name string + cacheSizes []string + expectWatchCacheSizes map[schema.GroupResource]int + expectErr string + }{ + { + name: "test when invalid value of watch cache size", + cacheSizes: []string{"deployments.apps#65536", "replicasets.extensions"}, + expectErr: "invalid value of watch cache size", + }, + { + name: "test when invalid size of watch cache size", + cacheSizes: []string{"deployments.apps#65536", "replicasets.extensions#655d1"}, + expectErr: "invalid size of watch cache size", + }, + { + name: "test when watch cache size is negative", + cacheSizes: []string{"deployments.apps#65536", "replicasets.extensions#-65536"}, + expectErr: "watch cache size cannot be negative", + }, + { + name: "test when parse watch cache size success", + cacheSizes: []string{"deployments.apps#65536", "replicasets.extensions#65536"}, + expectWatchCacheSizes: map[schema.GroupResource]int{ + {Group: "apps", Resource: "deployments"}: 65536, + {Group: "extensions", Resource: "replicasets"}: 65536, + }, + }, + } + + for _, testcase := range testCases { + t.Run(testcase.name, func(t *testing.T) { + result, err := ParseWatchCacheSizes(testcase.cacheSizes) + if len(testcase.expectErr) != 0 && !strings.Contains(err.Error(), testcase.expectErr) { + t.Errorf("got err: %v, expected err: %s", err, testcase.expectErr) + } + if len(testcase.expectErr) == 0 { + if err != nil { + t.Errorf("got err: %v, expected err nil", err) + } else { + for key, expectValue := range testcase.expectWatchCacheSizes { + if resultValue, exist := result[key]; !exist || resultValue != expectValue { + t.Errorf("got watch cache size: %v, expected watch cache size %v", result, testcase.expectWatchCacheSizes) + } + } + } + } + }) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/server_run_options_test.go b/staging/src/k8s.io/apiserver/pkg/server/options/server_run_options_test.go new file mode 100644 index 00000000000..0fd69b4eb01 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/server/options/server_run_options_test.go @@ -0,0 +1,125 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package options + +import ( + "net" + "strings" + "testing" + "time" + + utilerrors "k8s.io/apimachinery/pkg/util/errors" +) + +func TestServerRunOptionsValidate(t *testing.T) { + testCases := []struct { + name string + testOptions *ServerRunOptions + expectErr string + }{ + { + name: "Test when TargetRAMMB is negative value", + testOptions: &ServerRunOptions{ + AdvertiseAddress: net.ParseIP("192.168.10.10"), + CorsAllowedOriginList: []string{"10.10.10.100", "10.10.10.200"}, + MaxRequestsInFlight: 400, + MaxMutatingRequestsInFlight: 200, + RequestTimeout: time.Duration(2) * time.Minute, + MinRequestTimeout: 1800, + TargetRAMMB: -65536, + }, + expectErr: "--target-ram-mb can not be negative value", + }, + { + name: "Test when MaxRequestsInFlight is negative value", + testOptions: &ServerRunOptions{ + AdvertiseAddress: net.ParseIP("192.168.10.10"), + CorsAllowedOriginList: []string{"10.10.10.100", "10.10.10.200"}, + MaxRequestsInFlight: -400, + MaxMutatingRequestsInFlight: 200, + RequestTimeout: time.Duration(2) * time.Minute, + MinRequestTimeout: 1800, + TargetRAMMB: 65536, + }, + expectErr: "--max-requests-inflight can not be negative value", + }, + { + name: "Test when MaxMutatingRequestsInFlight is negative value", + testOptions: &ServerRunOptions{ + AdvertiseAddress: net.ParseIP("192.168.10.10"), + CorsAllowedOriginList: []string{"10.10.10.100", "10.10.10.200"}, + MaxRequestsInFlight: 400, + MaxMutatingRequestsInFlight: -200, + RequestTimeout: time.Duration(2) * time.Minute, + MinRequestTimeout: 1800, + TargetRAMMB: 65536, + }, + expectErr: "--max-mutating-requests-inflight can not be negative value", + }, + { + name: "Test when RequestTimeout is negative value", + testOptions: &ServerRunOptions{ + AdvertiseAddress: net.ParseIP("192.168.10.10"), + CorsAllowedOriginList: []string{"10.10.10.100", "10.10.10.200"}, + MaxRequestsInFlight: 400, + MaxMutatingRequestsInFlight: 200, + RequestTimeout: -time.Duration(2) * time.Minute, + MinRequestTimeout: 1800, + TargetRAMMB: 65536, + }, + expectErr: "--request-timeout can not be negative value", + }, + { + name: "Test when MinRequestTimeout is negative value", + testOptions: &ServerRunOptions{ + AdvertiseAddress: net.ParseIP("192.168.10.10"), + CorsAllowedOriginList: []string{"10.10.10.100", "10.10.10.200"}, + MaxRequestsInFlight: 400, + MaxMutatingRequestsInFlight: 200, + RequestTimeout: time.Duration(2) * time.Minute, + MinRequestTimeout: -1800, + TargetRAMMB: 65536, + }, + expectErr: "--min-request-timeout can not be negative value", + }, + { + name: "Test when ServerRunOptions is valid", + testOptions: &ServerRunOptions{ + AdvertiseAddress: net.ParseIP("192.168.10.10"), + CorsAllowedOriginList: []string{"10.10.10.100", "10.10.10.200"}, + MaxRequestsInFlight: 400, + MaxMutatingRequestsInFlight: 200, + RequestTimeout: time.Duration(2) * time.Minute, + MinRequestTimeout: 1800, + TargetRAMMB: 65536, + }, + }, + } + + for _, testcase := range testCases { + t.Run(testcase.name, func(t *testing.T) { + errs := testcase.testOptions.Validate() + if len(testcase.expectErr) != 0 && !strings.Contains(utilerrors.NewAggregate(errs).Error(), testcase.expectErr) { + t.Errorf("got err: %v, expected err: %s", errs, testcase.expectErr) + } + + if len(testcase.expectErr) == 0 && len(errs) != 0 { + t.Errorf("got err: %s, expected err nil", errs) + } + }) + } +}