From 2ffa3b4586923fbeccfac18777cdb09398b20490 Mon Sep 17 00:00:00 2001 From: nikhiljindal Date: Tue, 26 Apr 2016 00:43:56 -0700 Subject: [PATCH] Moving StorageFactory building logic to genericapiserver --- cmd/kube-apiserver/app/options/options.go | 72 +------ .../app/options/options_test.go | 61 ------ cmd/kube-apiserver/app/server.go | 138 ++------------ cmd/kube-apiserver/app/server_test.go | 100 ---------- docs/admin/kube-apiserver.md | 2 +- .../app/options/options.go | 59 ------ .../app/options/options_test.go | 61 ------ .../cmd/federated-apiserver/app/server.go | 72 ++----- pkg/apimachinery/registered/registered.go | 9 + .../default_storage_factory_builder.go | 167 +++++++++++++++++ .../default_storage_factory_builder_test.go | 177 ++++++++++++++++++ pkg/genericapiserver/server_run_options.go | 125 +++++++++++-- .../server_run_options_test.go | 82 ++++++++ 13 files changed, 574 insertions(+), 551 deletions(-) create mode 100644 pkg/genericapiserver/default_storage_factory_builder.go create mode 100644 pkg/genericapiserver/default_storage_factory_builder_test.go create mode 100644 pkg/genericapiserver/server_run_options_test.go diff --git a/cmd/kube-apiserver/app/options/options.go b/cmd/kube-apiserver/app/options/options.go index b36dd3f8ed0..4e517f3d2a8 100644 --- a/cmd/kube-apiserver/app/options/options.go +++ b/cmd/kube-apiserver/app/options/options.go @@ -23,10 +23,7 @@ import ( "k8s.io/kubernetes/pkg/admission" "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/unversioned" - apiutil "k8s.io/kubernetes/pkg/api/util" "k8s.io/kubernetes/pkg/api/validation" - "k8s.io/kubernetes/pkg/apimachinery/registered" "k8s.io/kubernetes/pkg/apiserver" "k8s.io/kubernetes/pkg/genericapiserver" kubeletclient "k8s.io/kubernetes/pkg/kubelet/client" @@ -46,7 +43,6 @@ type APIServer struct { BasicAuthFile string DefaultStorageMediaType string DeleteCollectionWorkers int - DeprecatedStorageVersion string EtcdServersOverrides []string EventTTL time.Duration KeystoneURL string @@ -62,13 +58,8 @@ type APIServer struct { SSHUser string ServiceAccountKeyFile string ServiceAccountLookup bool - StorageVersions string - // The default values for StorageVersions. StorageVersions overrides - // these; you can change this if you want to change the defaults (e.g., - // for testing). This is not actually exposed as a flag. - DefaultStorageVersions string - TokenAuthFile string - WatchCacheSizes []string + TokenAuthFile string + WatchCacheSizes []string } // NewAPIServer creates a new APIServer object with default parameters @@ -81,8 +72,6 @@ func NewAPIServer() *APIServer { DeleteCollectionWorkers: 1, EventTTL: 1 * time.Hour, MasterServiceNamespace: api.NamespaceDefault, - StorageVersions: registered.AllPreferredGroupVersions(), - DefaultStorageVersions: registered.AllPreferredGroupVersions(), KubeletConfig: kubeletclient.KubeletClientConfig{ Port: ports.KubeletPort, EnableHttps: true, @@ -92,69 +81,12 @@ func NewAPIServer() *APIServer { return &s } -// dest must be a map of group to groupVersion. -func mergeGroupVersionIntoMap(gvList string, dest map[string]unversioned.GroupVersion) error { - for _, gvString := range strings.Split(gvList, ",") { - if gvString == "" { - continue - } - // We accept two formats. "group/version" OR - // "group=group/version". The latter is used when types - // move between groups. - if !strings.Contains(gvString, "=") { - gv, err := unversioned.ParseGroupVersion(gvString) - if err != nil { - return err - } - dest[gv.Group] = gv - - } else { - parts := strings.SplitN(gvString, "=", 2) - gv, err := unversioned.ParseGroupVersion(parts[1]) - if err != nil { - return err - } - dest[parts[0]] = gv - } - } - - return nil -} - -// StorageGroupsToEncodingVersion returns a map from group name to group version, -// computed from the s.DeprecatedStorageVersion and s.StorageVersions flags. -// TODO: can we move the whole storage version concept to the generic apiserver? -func (s *APIServer) StorageGroupsToEncodingVersion() (map[string]unversioned.GroupVersion, error) { - storageVersionMap := map[string]unversioned.GroupVersion{} - if s.DeprecatedStorageVersion != "" { - storageVersionMap[""] = unversioned.GroupVersion{Group: apiutil.GetGroup(s.DeprecatedStorageVersion), Version: apiutil.GetVersion(s.DeprecatedStorageVersion)} - } - - // First, get the defaults. - if err := mergeGroupVersionIntoMap(s.DefaultStorageVersions, storageVersionMap); err != nil { - return nil, err - } - // Override any defaults with the user settings. - if err := mergeGroupVersionIntoMap(s.StorageVersions, storageVersionMap); err != nil { - return nil, err - } - - return storageVersionMap, nil -} - // AddFlags adds flags for a specific APIServer to the specified FlagSet func (s *APIServer) AddFlags(fs *pflag.FlagSet) { // Add the generic flags. s.ServerRunOptions.AddFlags(fs) // Note: the weird ""+ in below lines seems to be the only way to get gofmt to // arrange these text blocks sensibly. Grrr. - fs.StringVar(&s.DeprecatedStorageVersion, "storage-version", s.DeprecatedStorageVersion, "The version to store the legacy v1 resources with. Defaults to server preferred") - fs.MarkDeprecated("storage-version", "--storage-version is deprecated and will be removed when the v1 API is retired. See --storage-versions instead.") - fs.StringVar(&s.StorageVersions, "storage-versions", s.StorageVersions, "The per-group version to store resources in. "+ - "Specified in the format \"group1/version1,group2/version2,...\". "+ - "In the case where objects are moved from one group to the other, you may specify the format \"group1=group2/v1beta1,group3/v1beta1,...\". "+ - "You only need to pass the groups you wish to change from the defaults. "+ - "It defaults to a list of preferred versions of all registered groups, which is derived from the KUBE_API_VERSIONS environment variable.") fs.StringVar(&s.DefaultStorageMediaType, "storage-media-type", s.DefaultStorageMediaType, "The media type to use to store objects in storage. Defaults to application/json. Some resources may only support a specific media type and will ignore this setting.") fs.DurationVar(&s.EventTTL, "event-ttl", s.EventTTL, "Amount of time to retain events. Default 1 hour.") fs.StringVar(&s.BasicAuthFile, "basic-auth-file", s.BasicAuthFile, "If set, the file that will be used to admit requests to the secure port of the API server via http basic authentication.") diff --git a/cmd/kube-apiserver/app/options/options_test.go b/cmd/kube-apiserver/app/options/options_test.go index a7f67e07ad2..c2f0114114b 100644 --- a/cmd/kube-apiserver/app/options/options_test.go +++ b/cmd/kube-apiserver/app/options/options_test.go @@ -17,72 +17,11 @@ limitations under the License. package options import ( - "reflect" "testing" "github.com/spf13/pflag" - - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/unversioned" - "k8s.io/kubernetes/pkg/apis/autoscaling" - "k8s.io/kubernetes/pkg/apis/extensions" ) -func TestGenerateStorageVersionMap(t *testing.T) { - testCases := []struct { - legacyVersion string - storageVersions string - defaultVersions string - expectedMap map[string]unversioned.GroupVersion - }{ - { - legacyVersion: "v1", - storageVersions: "v1,extensions/v1beta1", - expectedMap: map[string]unversioned.GroupVersion{ - api.GroupName: {Version: "v1"}, - extensions.GroupName: {Group: "extensions", Version: "v1beta1"}, - }, - }, - { - legacyVersion: "", - storageVersions: "extensions/v1beta1,v1", - expectedMap: map[string]unversioned.GroupVersion{ - api.GroupName: {Version: "v1"}, - extensions.GroupName: {Group: "extensions", Version: "v1beta1"}, - }, - }, - { - legacyVersion: "", - storageVersions: "autoscaling=extensions/v1beta1,v1", - defaultVersions: "extensions/v1beta1,v1,autoscaling/v1", - expectedMap: map[string]unversioned.GroupVersion{ - api.GroupName: {Version: "v1"}, - autoscaling.GroupName: {Group: "extensions", Version: "v1beta1"}, - extensions.GroupName: {Group: "extensions", Version: "v1beta1"}, - }, - }, - { - legacyVersion: "", - storageVersions: "", - expectedMap: map[string]unversioned.GroupVersion{}, - }, - } - for i, test := range testCases { - s := APIServer{ - DeprecatedStorageVersion: test.legacyVersion, - StorageVersions: test.storageVersions, - DefaultStorageVersions: test.defaultVersions, - } - output, err := s.StorageGroupsToEncodingVersion() - if err != nil { - t.Errorf("%v: unexpected error: %v", i, err) - } - if !reflect.DeepEqual(test.expectedMap, output) { - t.Errorf("%v: unexpected error. expect: %v, got: %v", i, test.expectedMap, output) - } - } -} - func TestAddFlagsFlag(t *testing.T) { // TODO: This only tests the enable-swagger-ui flag for now. // Expand the test to include other flags as well. diff --git a/cmd/kube-apiserver/app/server.go b/cmd/kube-apiserver/app/server.go index 955e073f96d..4e13dd9bfff 100644 --- a/cmd/kube-apiserver/app/server.go +++ b/cmd/kube-apiserver/app/server.go @@ -21,7 +21,6 @@ package app import ( "crypto/tls" - "fmt" "net" "net/url" "strconv" @@ -35,26 +34,18 @@ import ( "k8s.io/kubernetes/pkg/admission" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/unversioned" - apiv1 "k8s.io/kubernetes/pkg/api/v1" - appsapi "k8s.io/kubernetes/pkg/apis/apps/v1alpha1" "k8s.io/kubernetes/pkg/apis/autoscaling" - autoscalingapiv1 "k8s.io/kubernetes/pkg/apis/autoscaling/v1" "k8s.io/kubernetes/pkg/apis/batch" - batchapiv1 "k8s.io/kubernetes/pkg/apis/batch/v1" "k8s.io/kubernetes/pkg/apis/extensions" - extensionsapiv1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" "k8s.io/kubernetes/pkg/apiserver" "k8s.io/kubernetes/pkg/apiserver/authenticator" "k8s.io/kubernetes/pkg/capabilities" - clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" - "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/cloudprovider" serviceaccountcontroller "k8s.io/kubernetes/pkg/controller/serviceaccount" "k8s.io/kubernetes/pkg/genericapiserver" kubeletclient "k8s.io/kubernetes/pkg/kubelet/client" "k8s.io/kubernetes/pkg/master" "k8s.io/kubernetes/pkg/registry/cachesize" - "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/serviceaccount" ) @@ -132,44 +123,17 @@ func Run(s *options.APIServer) error { glog.Fatalf("Failure to start kubelet client: %v", err) } - apiResourceConfigSource, err := parseRuntimeConfig(s) + storageGroupsToEncodingVersion, err := s.StorageGroupsToEncodingVersion() if err != nil { - glog.Fatalf("error in parsing runtime-config: %s", err) + glog.Fatalf("error generating storage version map: %s", err) } - - clientConfig := &restclient.Config{ - Host: net.JoinHostPort(s.InsecureBindAddress.String(), strconv.Itoa(s.InsecurePort)), - // Increase QPS limits. The client is currently passed to all admission plugins, - // and those can be throttled in case of higher load on apiserver - see #22340 and #22422 - // for more details. Once #22422 is fixed, we may want to remove it. - QPS: 50, - Burst: 100, - } - if len(s.DeprecatedStorageVersion) != 0 { - gv, err := unversioned.ParseGroupVersion(s.DeprecatedStorageVersion) - if err != nil { - glog.Fatalf("error in parsing group version: %s", err) - } - clientConfig.GroupVersion = &gv - } - - client, err := clientset.NewForConfig(clientConfig) + storageFactory, err := genericapiserver.BuildDefaultStorageFactory( + s.StorageConfig, s.DefaultStorageMediaType, api.Codecs, + genericapiserver.NewDefaultResourceEncodingConfig(), storageGroupsToEncodingVersion, + master.DefaultAPIResourceConfigSource(), s.RuntimeConfig) if err != nil { - glog.Errorf("Failed to create clientset: %v", err) + glog.Fatalf("error in initializing storage factory: %s", err) } - - resourceEncoding := genericapiserver.NewDefaultResourceEncodingConfig() - groupToEncoding, err := s.StorageGroupsToEncodingVersion() - if err != nil { - glog.Fatalf("error getting group encoding: %s", err) - } - for group, storageEncodingVersion := range groupToEncoding { - resourceEncoding.SetVersionEncoding(group, storageEncodingVersion, unversioned.GroupVersion{Group: group, Version: runtime.APIVersionInternal}) - } - - storageFactory := genericapiserver.NewDefaultStorageFactory(s.StorageConfig, s.DefaultStorageMediaType, api.Codecs, resourceEncoding, apiResourceConfigSource) - // third party resources are always serialized to storage using JSON - storageFactory.SetSerializer(extensions.Resource("thirdpartyresources"), "application/json", nil) storageFactory.AddCohabitatingResources(batch.Resource("jobs"), extensions.Resource("jobs")) storageFactory.AddCohabitatingResources(autoscaling.Resource("horizontalpodautoscalers"), extensions.Resource("horizontalpodautoscalers")) for _, override := range s.EtcdServersOverrides { @@ -238,6 +202,10 @@ func Run(s *options.APIServer) error { } admissionControlPluginNames := strings.Split(s.AdmissionControl, ",") + client, err := s.NewSelfClient() + if err != nil { + glog.Errorf("Failed to create clientset: %v", err) + } admissionController := admission.NewFromPlugins(client, admissionControlPluginNames, s.AdmissionControlConfigFile) genericConfig := genericapiserver.NewConfig(s.ServerRunOptions) @@ -247,7 +215,7 @@ func Run(s *options.APIServer) error { genericConfig.SupportsBasicAuth = len(s.BasicAuthFile) > 0 genericConfig.Authorizer = authorizer genericConfig.AdmissionControl = admissionController - genericConfig.APIResourceConfigSource = apiResourceConfigSource + genericConfig.APIResourceConfigSource = storageFactory.APIResourceConfigSource genericConfig.MasterServiceNamespace = s.MasterServiceNamespace genericConfig.ProxyDialer = proxyDialerFn genericConfig.ProxyTLSClientConfig = proxyTLSClientConfig @@ -275,85 +243,3 @@ func Run(s *options.APIServer) error { m.Run(s.ServerRunOptions) return nil } - -func getRuntimeConfigValue(s *options.APIServer, apiKey string, defaultValue bool) bool { - flagValue, ok := s.RuntimeConfig[apiKey] - if ok { - if flagValue == "" { - return true - } - boolValue, err := strconv.ParseBool(flagValue) - if err != nil { - glog.Fatalf("Invalid value of %s: %s, err: %v", apiKey, flagValue, err) - } - return boolValue - } - return defaultValue -} - -// Parses the given runtime-config and formats it into genericapiserver.APIResourceConfigSource -func parseRuntimeConfig(s *options.APIServer) (genericapiserver.APIResourceConfigSource, error) { - v1GroupVersionString := "api/v1" - extensionsGroupVersionString := extensionsapiv1beta1.SchemeGroupVersion.String() - versionToResourceSpecifier := map[unversioned.GroupVersion]string{ - apiv1.SchemeGroupVersion: v1GroupVersionString, - extensionsapiv1beta1.SchemeGroupVersion: extensionsGroupVersionString, - batchapiv1.SchemeGroupVersion: batchapiv1.SchemeGroupVersion.String(), - autoscalingapiv1.SchemeGroupVersion: autoscalingapiv1.SchemeGroupVersion.String(), - appsapi.SchemeGroupVersion: appsapi.SchemeGroupVersion.String(), - } - - resourceConfig := master.DefaultAPIResourceConfigSource() - - // "api/all=false" allows users to selectively enable specific api versions. - enableAPIByDefault := true - allAPIFlagValue, ok := s.RuntimeConfig["api/all"] - if ok && allAPIFlagValue == "false" { - enableAPIByDefault = false - } - - // "api/legacy=false" allows users to disable legacy api versions. - disableLegacyAPIs := false - legacyAPIFlagValue, ok := s.RuntimeConfig["api/legacy"] - if ok && legacyAPIFlagValue == "false" { - disableLegacyAPIs = true - } - _ = disableLegacyAPIs // hush the compiler while we don't have legacy APIs to disable. - - // "={true|false} allows users to enable/disable API. - // This takes preference over api/all and api/legacy, if specified. - for version, resourceSpecifier := range versionToResourceSpecifier { - enableVersion := getRuntimeConfigValue(s, resourceSpecifier, enableAPIByDefault) - if enableVersion { - resourceConfig.EnableVersions(version) - } else { - resourceConfig.DisableVersions(version) - } - } - - for key := range s.RuntimeConfig { - tokens := strings.Split(key, "/") - if len(tokens) != 3 { - continue - } - - switch { - case strings.HasPrefix(key, extensionsGroupVersionString+"/"): - if !resourceConfig.AnyResourcesForVersionEnabled(extensionsapiv1beta1.SchemeGroupVersion) { - return nil, fmt.Errorf("%v is disabled, you cannot configure its resources individually", extensionsapiv1beta1.SchemeGroupVersion) - } - - resource := strings.TrimPrefix(key, extensionsGroupVersionString+"/") - if getRuntimeConfigValue(s, key, false) { - resourceConfig.EnableResources(extensionsapiv1beta1.SchemeGroupVersion.WithResource(resource)) - } else { - resourceConfig.DisableResources(extensionsapiv1beta1.SchemeGroupVersion.WithResource(resource)) - } - - default: - // TODO enable individual resource capability for all GroupVersionResources - return nil, fmt.Errorf("%v resources cannot be enabled/disabled individually", key) - } - } - return resourceConfig, nil -} diff --git a/cmd/kube-apiserver/app/server_test.go b/cmd/kube-apiserver/app/server_test.go index cce3855ef84..ce71671b124 100644 --- a/cmd/kube-apiserver/app/server_test.go +++ b/cmd/kube-apiserver/app/server_test.go @@ -17,14 +17,10 @@ limitations under the License. package app import ( - "reflect" "regexp" "testing" "k8s.io/kubernetes/cmd/kube-apiserver/app/options" - "k8s.io/kubernetes/pkg/api/unversioned" - "k8s.io/kubernetes/pkg/genericapiserver" - "k8s.io/kubernetes/pkg/master" ) func TestLongRunningRequestRegexp(t *testing.T) { @@ -67,99 +63,3 @@ func TestLongRunningRequestRegexp(t *testing.T) { } } } - -func TestParseRuntimeConfig(t *testing.T) { - testCases := []struct { - runtimeConfig map[string]string - expectedAPIConfig func() *genericapiserver.ResourceConfig - err bool - }{ - { - runtimeConfig: map[string]string{}, - expectedAPIConfig: func() *genericapiserver.ResourceConfig { - return master.DefaultAPIResourceConfigSource() - }, - err: false, - }, - { - // Cannot override v1 resources. - runtimeConfig: map[string]string{ - "api/v1/pods": "false", - }, - expectedAPIConfig: func() *genericapiserver.ResourceConfig { - return master.DefaultAPIResourceConfigSource() - }, - err: true, - }, - { - // Disable v1. - runtimeConfig: map[string]string{ - "api/v1": "false", - }, - expectedAPIConfig: func() *genericapiserver.ResourceConfig { - config := master.DefaultAPIResourceConfigSource() - config.DisableVersions(unversioned.GroupVersion{Group: "", Version: "v1"}) - return config - }, - err: false, - }, - { - // Disable extensions. - runtimeConfig: map[string]string{ - "extensions/v1beta1": "false", - }, - expectedAPIConfig: func() *genericapiserver.ResourceConfig { - config := master.DefaultAPIResourceConfigSource() - config.DisableVersions(unversioned.GroupVersion{Group: "extensions", Version: "v1beta1"}) - return config - }, - err: false, - }, - { - // Disable deployments. - runtimeConfig: map[string]string{ - "extensions/v1beta1/deployments": "false", - }, - expectedAPIConfig: func() *genericapiserver.ResourceConfig { - config := master.DefaultAPIResourceConfigSource() - config.DisableResources(unversioned.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "deployments"}) - return config - }, - err: false, - }, - { - // Enable deployments and disable jobs. - runtimeConfig: map[string]string{ - "extensions/v1beta1/anything": "true", - "extensions/v1beta1/jobs": "false", - }, - expectedAPIConfig: func() *genericapiserver.ResourceConfig { - config := master.DefaultAPIResourceConfigSource() - config.DisableResources(unversioned.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "jobs"}) - config.EnableResources(unversioned.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "anything"}) - return config - }, - err: false, - }, - } - for _, test := range testCases { - s := &options.APIServer{ - ServerRunOptions: &genericapiserver.ServerRunOptions{ - RuntimeConfig: test.runtimeConfig, - }, - } - actualDisablers, err := parseRuntimeConfig(s) - - if err == nil && test.err { - t.Fatalf("expected error for test: %v", test) - } else if err != nil && !test.err { - t.Fatalf("unexpected error: %s, for test: %v", err, test) - } - - expectedConfig := test.expectedAPIConfig() - if err == nil && !reflect.DeepEqual(actualDisablers, expectedConfig) { - t.Fatalf("%v: unexpected apiResourceDisablers. Actual: %v\n expected: %v", test.runtimeConfig, actualDisablers, expectedConfig) - } - } - -} diff --git a/docs/admin/kube-apiserver.md b/docs/admin/kube-apiserver.md index 4ac64d0df6e..7b7b94d3148 100644 --- a/docs/admin/kube-apiserver.md +++ b/docs/admin/kube-apiserver.md @@ -119,7 +119,7 @@ kube-apiserver --watch-cache-sizes=[]: List of watch cache sizes for every resource (pods, nodes, etc.), comma separated. The individual override format: resource#size, where size is a number. It takes effect when watch-cache is enabled. ``` -###### Auto generated by spf13/cobra on 8-May-2016 +###### Auto generated by spf13/cobra on 9-May-2016 diff --git a/federation/cmd/federated-apiserver/app/options/options.go b/federation/cmd/federated-apiserver/app/options/options.go index b36dd3f8ed0..2a8f37886c7 100644 --- a/federation/cmd/federated-apiserver/app/options/options.go +++ b/federation/cmd/federated-apiserver/app/options/options.go @@ -23,8 +23,6 @@ import ( "k8s.io/kubernetes/pkg/admission" "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/unversioned" - apiutil "k8s.io/kubernetes/pkg/api/util" "k8s.io/kubernetes/pkg/api/validation" "k8s.io/kubernetes/pkg/apimachinery/registered" "k8s.io/kubernetes/pkg/apiserver" @@ -92,69 +90,12 @@ func NewAPIServer() *APIServer { return &s } -// dest must be a map of group to groupVersion. -func mergeGroupVersionIntoMap(gvList string, dest map[string]unversioned.GroupVersion) error { - for _, gvString := range strings.Split(gvList, ",") { - if gvString == "" { - continue - } - // We accept two formats. "group/version" OR - // "group=group/version". The latter is used when types - // move between groups. - if !strings.Contains(gvString, "=") { - gv, err := unversioned.ParseGroupVersion(gvString) - if err != nil { - return err - } - dest[gv.Group] = gv - - } else { - parts := strings.SplitN(gvString, "=", 2) - gv, err := unversioned.ParseGroupVersion(parts[1]) - if err != nil { - return err - } - dest[parts[0]] = gv - } - } - - return nil -} - -// StorageGroupsToEncodingVersion returns a map from group name to group version, -// computed from the s.DeprecatedStorageVersion and s.StorageVersions flags. -// TODO: can we move the whole storage version concept to the generic apiserver? -func (s *APIServer) StorageGroupsToEncodingVersion() (map[string]unversioned.GroupVersion, error) { - storageVersionMap := map[string]unversioned.GroupVersion{} - if s.DeprecatedStorageVersion != "" { - storageVersionMap[""] = unversioned.GroupVersion{Group: apiutil.GetGroup(s.DeprecatedStorageVersion), Version: apiutil.GetVersion(s.DeprecatedStorageVersion)} - } - - // First, get the defaults. - if err := mergeGroupVersionIntoMap(s.DefaultStorageVersions, storageVersionMap); err != nil { - return nil, err - } - // Override any defaults with the user settings. - if err := mergeGroupVersionIntoMap(s.StorageVersions, storageVersionMap); err != nil { - return nil, err - } - - return storageVersionMap, nil -} - // AddFlags adds flags for a specific APIServer to the specified FlagSet func (s *APIServer) AddFlags(fs *pflag.FlagSet) { // Add the generic flags. s.ServerRunOptions.AddFlags(fs) // Note: the weird ""+ in below lines seems to be the only way to get gofmt to // arrange these text blocks sensibly. Grrr. - fs.StringVar(&s.DeprecatedStorageVersion, "storage-version", s.DeprecatedStorageVersion, "The version to store the legacy v1 resources with. Defaults to server preferred") - fs.MarkDeprecated("storage-version", "--storage-version is deprecated and will be removed when the v1 API is retired. See --storage-versions instead.") - fs.StringVar(&s.StorageVersions, "storage-versions", s.StorageVersions, "The per-group version to store resources in. "+ - "Specified in the format \"group1/version1,group2/version2,...\". "+ - "In the case where objects are moved from one group to the other, you may specify the format \"group1=group2/v1beta1,group3/v1beta1,...\". "+ - "You only need to pass the groups you wish to change from the defaults. "+ - "It defaults to a list of preferred versions of all registered groups, which is derived from the KUBE_API_VERSIONS environment variable.") fs.StringVar(&s.DefaultStorageMediaType, "storage-media-type", s.DefaultStorageMediaType, "The media type to use to store objects in storage. Defaults to application/json. Some resources may only support a specific media type and will ignore this setting.") fs.DurationVar(&s.EventTTL, "event-ttl", s.EventTTL, "Amount of time to retain events. Default 1 hour.") fs.StringVar(&s.BasicAuthFile, "basic-auth-file", s.BasicAuthFile, "If set, the file that will be used to admit requests to the secure port of the API server via http basic authentication.") diff --git a/federation/cmd/federated-apiserver/app/options/options_test.go b/federation/cmd/federated-apiserver/app/options/options_test.go index a7f67e07ad2..c2f0114114b 100644 --- a/federation/cmd/federated-apiserver/app/options/options_test.go +++ b/federation/cmd/federated-apiserver/app/options/options_test.go @@ -17,72 +17,11 @@ limitations under the License. package options import ( - "reflect" "testing" "github.com/spf13/pflag" - - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/unversioned" - "k8s.io/kubernetes/pkg/apis/autoscaling" - "k8s.io/kubernetes/pkg/apis/extensions" ) -func TestGenerateStorageVersionMap(t *testing.T) { - testCases := []struct { - legacyVersion string - storageVersions string - defaultVersions string - expectedMap map[string]unversioned.GroupVersion - }{ - { - legacyVersion: "v1", - storageVersions: "v1,extensions/v1beta1", - expectedMap: map[string]unversioned.GroupVersion{ - api.GroupName: {Version: "v1"}, - extensions.GroupName: {Group: "extensions", Version: "v1beta1"}, - }, - }, - { - legacyVersion: "", - storageVersions: "extensions/v1beta1,v1", - expectedMap: map[string]unversioned.GroupVersion{ - api.GroupName: {Version: "v1"}, - extensions.GroupName: {Group: "extensions", Version: "v1beta1"}, - }, - }, - { - legacyVersion: "", - storageVersions: "autoscaling=extensions/v1beta1,v1", - defaultVersions: "extensions/v1beta1,v1,autoscaling/v1", - expectedMap: map[string]unversioned.GroupVersion{ - api.GroupName: {Version: "v1"}, - autoscaling.GroupName: {Group: "extensions", Version: "v1beta1"}, - extensions.GroupName: {Group: "extensions", Version: "v1beta1"}, - }, - }, - { - legacyVersion: "", - storageVersions: "", - expectedMap: map[string]unversioned.GroupVersion{}, - }, - } - for i, test := range testCases { - s := APIServer{ - DeprecatedStorageVersion: test.legacyVersion, - StorageVersions: test.storageVersions, - DefaultStorageVersions: test.defaultVersions, - } - output, err := s.StorageGroupsToEncodingVersion() - if err != nil { - t.Errorf("%v: unexpected error: %v", i, err) - } - if !reflect.DeepEqual(test.expectedMap, output) { - t.Errorf("%v: unexpected error. expect: %v, got: %v", i, test.expectedMap, output) - } - } -} - func TestAddFlagsFlag(t *testing.T) { // TODO: This only tests the enable-swagger-ui flag for now. // Expand the test to include other flags as well. diff --git a/federation/cmd/federated-apiserver/app/server.go b/federation/cmd/federated-apiserver/app/server.go index 41d66a1da78..a133b21dc7f 100644 --- a/federation/cmd/federated-apiserver/app/server.go +++ b/federation/cmd/federated-apiserver/app/server.go @@ -20,8 +20,6 @@ limitations under the License. package app import ( - "net" - "strconv" "strings" "github.com/golang/glog" @@ -34,11 +32,8 @@ import ( "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/apiserver" "k8s.io/kubernetes/pkg/apiserver/authenticator" - clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" - "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/genericapiserver" "k8s.io/kubernetes/pkg/registry/cachesize" - "k8s.io/kubernetes/pkg/runtime" ) // NewAPIServerCommand creates a *cobra.Command object with default parameters @@ -62,21 +57,21 @@ cluster's shared state through which all other components interact.`, func Run(s *options.APIServer) error { genericapiserver.DefaultAndValidateRunOptions(s.ServerRunOptions) - apiResourceConfigSource, err := parseRuntimeConfig(s) + // TODO: register cluster federation resources here. + resourceConfig := genericapiserver.NewResourceConfig() + + storageGroupsToEncodingVersion, err := s.StorageGroupsToEncodingVersion() if err != nil { - glog.Fatalf("error in parsing runtime-config: %s", err) + glog.Fatalf("error generating storage version map: %s", err) + } + storageFactory, err := genericapiserver.BuildDefaultStorageFactory( + s.StorageConfig, s.DefaultStorageMediaType, api.Codecs, + genericapiserver.NewDefaultResourceEncodingConfig(), storageGroupsToEncodingVersion, + resourceConfig, s.RuntimeConfig) + if err != nil { + glog.Fatalf("error in initializing storage factory: %s", err) } - resourceEncoding := genericapiserver.NewDefaultResourceEncodingConfig() - groupToEncoding, err := s.StorageGroupsToEncodingVersion() - if err != nil { - glog.Fatalf("error getting group encoding: %s", err) - } - for group, storageEncodingVersion := range groupToEncoding { - resourceEncoding.SetVersionEncoding(group, storageEncodingVersion, unversioned.GroupVersion{Group: group, Version: runtime.APIVersionInternal}) - } - - storageFactory := genericapiserver.NewDefaultStorageFactory(s.StorageConfig, s.DefaultStorageMediaType, api.Codecs, resourceEncoding, apiResourceConfigSource) for _, override := range s.EtcdServersOverrides { tokens := strings.Split(override, "#") if len(tokens) != 2 { @@ -119,27 +114,10 @@ func Run(s *options.APIServer) error { } admissionControlPluginNames := strings.Split(s.AdmissionControl, ",") - clientConfig := &restclient.Config{ - Host: net.JoinHostPort(s.InsecureBindAddress.String(), strconv.Itoa(s.InsecurePort)), - // Increase QPS limits. The client is currently passed to all admission plugins, - // and those can be throttled in case of higher load on apiserver - see #22340 and #22422 - // for more details. Once #22422 is fixed, we may want to remove it. - QPS: 50, - Burst: 100, - } - if len(s.DeprecatedStorageVersion) != 0 { - gv, err := unversioned.ParseGroupVersion(s.DeprecatedStorageVersion) - if err != nil { - glog.Fatalf("error in parsing group version: %s", err) - } - clientConfig.GroupVersion = &gv - } - - client, err := clientset.NewForConfig(clientConfig) + client, err := s.NewSelfClient() if err != nil { glog.Errorf("Failed to create clientset: %v", err) } - admissionController := admission.NewFromPlugins(client, admissionControlPluginNames, s.AdmissionControlConfigFile) genericConfig := genericapiserver.NewConfig(s.ServerRunOptions) @@ -149,7 +127,7 @@ func Run(s *options.APIServer) error { genericConfig.SupportsBasicAuth = len(s.BasicAuthFile) > 0 genericConfig.Authorizer = authorizer genericConfig.AdmissionControl = admissionController - genericConfig.APIResourceConfigSource = apiResourceConfigSource + genericConfig.APIResourceConfigSource = storageFactory.APIResourceConfigSource genericConfig.MasterServiceNamespace = s.MasterServiceNamespace genericConfig.Serializer = api.Codecs @@ -168,25 +146,3 @@ func Run(s *options.APIServer) error { m.Run(s.ServerRunOptions) return nil } - -func getRuntimeConfigValue(s *options.APIServer, apiKey string, defaultValue bool) bool { - flagValue, ok := s.RuntimeConfig[apiKey] - if ok { - if flagValue == "" { - return true - } - boolValue, err := strconv.ParseBool(flagValue) - if err != nil { - glog.Fatalf("Invalid value of %s: %s, err: %v", apiKey, flagValue, err) - } - return boolValue - } - return defaultValue -} - -// Parses the given runtime-config and formats it into genericapiserver.APIResourceConfigSource -func parseRuntimeConfig(s *options.APIServer) (genericapiserver.APIResourceConfigSource, error) { - // TODO: parse the relevant group version when we add any. - resourceConfig := genericapiserver.NewResourceConfig() - return resourceConfig, nil -} diff --git a/pkg/apimachinery/registered/registered.go b/pkg/apimachinery/registered/registered.go index c1e1511beee..c418de3b05b 100644 --- a/pkg/apimachinery/registered/registered.go +++ b/pkg/apimachinery/registered/registered.go @@ -168,6 +168,15 @@ func IsRegisteredVersion(v unversioned.GroupVersion) bool { return found } +// RegisteredGroupVersions returns all registered group versions. +func RegisteredGroupVersions() []unversioned.GroupVersion { + ret := []unversioned.GroupVersion{} + for groupVersion := range registeredVersions { + ret = append(ret, groupVersion) + } + return ret +} + // IsThirdPartyAPIGroupVersion returns true if the api version is a user-registered group/version. func IsThirdPartyAPIGroupVersion(gv unversioned.GroupVersion) bool { for ix := range thirdPartyGroupVersions { diff --git a/pkg/genericapiserver/default_storage_factory_builder.go b/pkg/genericapiserver/default_storage_factory_builder.go new file mode 100644 index 00000000000..ef727662a86 --- /dev/null +++ b/pkg/genericapiserver/default_storage_factory_builder.go @@ -0,0 +1,167 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 genericapiserver + +import ( + "fmt" + "strconv" + "strings" + + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apimachinery/registered" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/storage/storagebackend" + "k8s.io/kubernetes/pkg/util/config" +) + +// Builds the DefaultStorageFactory. +// Merges defaultResourceConfig with the user specified overrides and merges +// defaultAPIResourceConfig with the corresponding user specified overrides as well. +func BuildDefaultStorageFactory(storageConfig storagebackend.Config, defaultMediaType string, serializer runtime.StorageSerializer, + defaultResourceEncoding *DefaultResourceEncodingConfig, storageEncodingOverrides map[string]unversioned.GroupVersion, defaultAPIResourceConfig *ResourceConfig, resourceConfigOverrides config.ConfigurationMap) (*DefaultStorageFactory, error) { + + resourceEncodingConfig := mergeResourceEncodingConfigs(defaultResourceEncoding, storageEncodingOverrides) + apiResourceConfig, err := mergeAPIResourceConfigs(defaultAPIResourceConfig, resourceConfigOverrides) + if err != nil { + return nil, err + } + return NewDefaultStorageFactory(storageConfig, defaultMediaType, serializer, resourceEncodingConfig, apiResourceConfig), nil +} + +// Merges the given defaultAPIResourceConfig with the given storageEncodingOverrides. +func mergeResourceEncodingConfigs(defaultResourceEncoding *DefaultResourceEncodingConfig, storageEncodingOverrides map[string]unversioned.GroupVersion) *DefaultResourceEncodingConfig { + resourceEncodingConfig := defaultResourceEncoding + for group, storageEncodingVersion := range storageEncodingOverrides { + resourceEncodingConfig.SetVersionEncoding(group, storageEncodingVersion, unversioned.GroupVersion{Group: group, Version: runtime.APIVersionInternal}) + } + return resourceEncodingConfig +} + +// Merges the given defaultAPIResourceConfig with the given resourceConfigOverrides. +func mergeAPIResourceConfigs(defaultAPIResourceConfig *ResourceConfig, resourceConfigOverrides config.ConfigurationMap) (*ResourceConfig, error) { + resourceConfig := defaultAPIResourceConfig + overrides := resourceConfigOverrides + + // "api/all=false" allows users to selectively enable specific api versions. + allAPIFlagValue, ok := overrides["api/all"] + if ok && allAPIFlagValue == "false" { + // Disable all group versions. + for _, groupVersion := range registered.RegisteredGroupVersions() { + if resourceConfig.AnyResourcesForVersionEnabled(groupVersion) { + resourceConfig.DisableVersions(groupVersion) + } + } + } + + // "api/legacy=false" allows users to disable legacy api versions. + disableLegacyAPIs := false + legacyAPIFlagValue, ok := overrides["api/legacy"] + if ok && legacyAPIFlagValue == "false" { + disableLegacyAPIs = true + } + _ = disableLegacyAPIs // hush the compiler while we don't have legacy APIs to disable. + + // "={true|false} allows users to enable/disable API. + // This takes preference over api/all and api/legacy, if specified. + // Iterate through all group/version overrides specified in runtimeConfig. + for key := range overrides { + if key == "api/all" || key == "api/legacy" { + // Have already handled them above. Can skip them here. + continue + } + tokens := strings.Split(key, "/") + if len(tokens) != 2 { + continue + } + groupVersionString := tokens[0] + "/" + tokens[1] + // HACK: Hack for "v1" legacy group version. + // Remove when we stop supporting the legacy group version. + if groupVersionString == "api/v1" { + groupVersionString = "v1" + } + groupVersion, err := unversioned.ParseGroupVersion(groupVersionString) + if err != nil { + return nil, fmt.Errorf("invalid key %s", key) + } + // Verify that the groupVersion is registered. + if !registered.IsRegisteredVersion(groupVersion) { + return nil, fmt.Errorf("group version %s that has not been registered", groupVersion.String()) + } + enabled, err := getRuntimeConfigValue(overrides, key, false) + if err != nil { + return nil, err + } + if enabled { + resourceConfig.EnableVersions(groupVersion) + } else { + resourceConfig.DisableVersions(groupVersion) + } + } + + // Iterate through all group/version/resource overrides specified in runtimeConfig. + for key := range overrides { + tokens := strings.Split(key, "/") + if len(tokens) != 3 { + continue + } + groupVersionString := tokens[0] + "/" + tokens[1] + // HACK: Hack for "v1" legacy group version. + // Remove when we stop supporting the legacy group version. + if groupVersionString == "api/v1" { + groupVersionString = "v1" + } + groupVersion, err := unversioned.ParseGroupVersion(groupVersionString) + if err != nil { + return nil, fmt.Errorf("invalid key %s", key) + } + resource := tokens[2] + // Verify that the groupVersion is registered. + if !registered.IsRegisteredVersion(groupVersion) { + return nil, fmt.Errorf("group version %s that has not been registered", groupVersion.String()) + } + + if !resourceConfig.AnyResourcesForVersionEnabled(groupVersion) { + return nil, fmt.Errorf("%v is disabled, you cannot configure its resources individually", groupVersion) + } + + enabled, err := getRuntimeConfigValue(overrides, key, false) + if err != nil { + return nil, err + } + if enabled { + resourceConfig.EnableResources(groupVersion.WithResource(resource)) + } else { + resourceConfig.DisableResources(groupVersion.WithResource(resource)) + } + } + return resourceConfig, nil +} + +func getRuntimeConfigValue(overrides config.ConfigurationMap, apiKey string, defaultValue bool) (bool, error) { + flagValue, ok := overrides[apiKey] + if ok { + if flagValue == "" { + return true, nil + } + boolValue, err := strconv.ParseBool(flagValue) + if err != nil { + return false, fmt.Errorf("invalid value of %s: %s, err: %v", apiKey, flagValue, err) + } + return boolValue, nil + } + return defaultValue, nil +} diff --git a/pkg/genericapiserver/default_storage_factory_builder_test.go b/pkg/genericapiserver/default_storage_factory_builder_test.go new file mode 100644 index 00000000000..fc80696f8a8 --- /dev/null +++ b/pkg/genericapiserver/default_storage_factory_builder_test.go @@ -0,0 +1,177 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 genericapiserver + +import ( + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/api/unversioned" + apiv1 "k8s.io/kubernetes/pkg/api/v1" + extensionsapiv1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" +) + +func TestParseRuntimeConfig(t *testing.T) { + extensionsGroupVersion := extensionsapiv1beta1.SchemeGroupVersion + apiv1GroupVersion := apiv1.SchemeGroupVersion + testCases := []struct { + runtimeConfig map[string]string + defaultResourceConfig func() *ResourceConfig + expectedAPIConfig func() *ResourceConfig + err bool + }{ + { + // everything default value. + runtimeConfig: map[string]string{}, + defaultResourceConfig: func() *ResourceConfig { + return NewResourceConfig() + }, + expectedAPIConfig: func() *ResourceConfig { + return NewResourceConfig() + }, + err: false, + }, + { + // no runtimeConfig override. + runtimeConfig: map[string]string{}, + defaultResourceConfig: func() *ResourceConfig { + config := NewResourceConfig() + config.DisableVersions(extensionsapiv1beta1.SchemeGroupVersion) + return config + }, + expectedAPIConfig: func() *ResourceConfig { + config := NewResourceConfig() + config.DisableVersions(extensionsapiv1beta1.SchemeGroupVersion) + return config + }, + err: false, + }, + { + // version enabled by runtimeConfig override. + runtimeConfig: map[string]string{ + "extensions/v1beta1": "", + }, + defaultResourceConfig: func() *ResourceConfig { + config := NewResourceConfig() + config.DisableVersions(extensionsapiv1beta1.SchemeGroupVersion) + return config + }, + expectedAPIConfig: func() *ResourceConfig { + config := NewResourceConfig() + config.EnableVersions(extensionsapiv1beta1.SchemeGroupVersion) + return config + }, + err: false, + }, + { + // disable resource + runtimeConfig: map[string]string{ + "api/v1/pods": "false", + }, + defaultResourceConfig: func() *ResourceConfig { + config := NewResourceConfig() + config.EnableVersions(apiv1GroupVersion) + return config + }, + expectedAPIConfig: func() *ResourceConfig { + config := NewResourceConfig() + config.EnableVersions(apiv1GroupVersion) + config.DisableResources(apiv1GroupVersion.WithResource("pods")) + return config + }, + err: false, + }, + { + // Disable v1. + runtimeConfig: map[string]string{ + "api/v1": "false", + }, + defaultResourceConfig: func() *ResourceConfig { + return NewResourceConfig() + }, + expectedAPIConfig: func() *ResourceConfig { + config := NewResourceConfig() + config.DisableVersions(apiv1GroupVersion) + return config + }, + err: false, + }, + { + // Enable deployments and disable jobs. + runtimeConfig: map[string]string{ + "extensions/v1beta1/anything": "true", + "extensions/v1beta1/jobs": "false", + }, + defaultResourceConfig: func() *ResourceConfig { + config := NewResourceConfig() + config.EnableVersions(extensionsGroupVersion) + return config + }, + + expectedAPIConfig: func() *ResourceConfig { + config := NewResourceConfig() + config.EnableVersions(extensionsGroupVersion) + config.DisableResources(extensionsGroupVersion.WithResource("jobs")) + config.EnableResources(extensionsGroupVersion.WithResource("anything")) + return config + }, + err: false, + }, + { + // invalid runtime config + runtimeConfig: map[string]string{ + "invalidgroup/version": "false", + }, + defaultResourceConfig: func() *ResourceConfig { + return NewResourceConfig() + }, + expectedAPIConfig: func() *ResourceConfig { + return NewResourceConfig() + }, + err: true, + }, + { + // cannot disable individual resource when version is not enabled. + runtimeConfig: map[string]string{ + "api/v1/pods": "false", + }, + defaultResourceConfig: func() *ResourceConfig { + return NewResourceConfig() + }, + expectedAPIConfig: func() *ResourceConfig { + config := NewResourceConfig() + config.DisableResources(unversioned.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}) + return config + }, + err: true, + }, + } + for _, test := range testCases { + actualDisablers, err := mergeAPIResourceConfigs(test.defaultResourceConfig(), test.runtimeConfig) + if err == nil && test.err { + t.Fatalf("expected error for test: %v", test) + } else if err != nil && !test.err { + t.Fatalf("unexpected error: %s, for test: %v", err, test) + } + + expectedConfig := test.expectedAPIConfig() + if err == nil && !reflect.DeepEqual(actualDisablers, expectedConfig) { + t.Fatalf("%v: unexpected apiResourceDisablers. Actual: %v\n expected: %v", test.runtimeConfig, actualDisablers, expectedConfig) + } + } + +} diff --git a/pkg/genericapiserver/server_run_options.go b/pkg/genericapiserver/server_run_options.go index 424e9215995..48ef92a74c4 100644 --- a/pkg/genericapiserver/server_run_options.go +++ b/pkg/genericapiserver/server_run_options.go @@ -18,11 +18,19 @@ package genericapiserver import ( "net" + "strconv" + "strings" + "k8s.io/kubernetes/pkg/api/unversioned" + apiutil "k8s.io/kubernetes/pkg/api/util" + "k8s.io/kubernetes/pkg/apimachinery/registered" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/storage/storagebackend" "k8s.io/kubernetes/pkg/util/config" utilnet "k8s.io/kubernetes/pkg/util/net" + "github.com/golang/glog" "github.com/spf13/pflag" ) @@ -33,15 +41,17 @@ const ( // ServerRunOptions contains the options while running a generic api server. type ServerRunOptions struct { - APIGroupPrefix string - APIPrefix string - AdvertiseAddress net.IP - BindAddress net.IP - CertDirectory string - ClientCAFile string - CloudConfigFile string - CloudProvider string - CorsAllowedOriginList []string + APIGroupPrefix string + APIPrefix string + AdvertiseAddress net.IP + BindAddress net.IP + CertDirectory string + ClientCAFile string + CloudConfigFile string + CloudProvider string + CorsAllowedOriginList []string + // Used to specify the storage version that should be used for the legacy v1 api group. + DeprecatedStorageVersion string EnableLogsSupport bool EnableProfiling bool EnableSwaggerUI bool @@ -59,16 +69,22 @@ type ServerRunOptions struct { SecurePort int ServiceClusterIPRange net.IPNet // TODO: make this a list ServiceNodePortRange utilnet.PortRange - TLSCertFile string - TLSPrivateKeyFile string + StorageVersions string + // The default values for StorageVersions. StorageVersions overrides + // these; you can change this if you want to change the defaults (e.g., + // for testing). This is not actually exposed as a flag. + DefaultStorageVersions string + TLSCertFile string + TLSPrivateKeyFile string } func NewServerRunOptions() *ServerRunOptions { return &ServerRunOptions{ - APIGroupPrefix: "/apis", - APIPrefix: "/api", - BindAddress: net.ParseIP("0.0.0.0"), - CertDirectory: "/var/run/kubernetes", + APIGroupPrefix: "/apis", + APIPrefix: "/api", + BindAddress: net.ParseIP("0.0.0.0"), + CertDirectory: "/var/run/kubernetes", + DefaultStorageVersions: registered.AllPreferredGroupVersions(), StorageConfig: storagebackend.Config{ Prefix: DefaultEtcdPathPrefix, DeserializationCacheSize: DefaultDeserializationCacheSize, @@ -84,9 +100,80 @@ func NewServerRunOptions() *ServerRunOptions { MinRequestTimeout: 1800, RuntimeConfig: make(config.ConfigurationMap), SecurePort: 6443, + StorageVersions: registered.AllPreferredGroupVersions(), } } +// StorageGroupsToEncodingVersion returns a map from group name to group version, +// computed from the s.DeprecatedStorageVersion and s.StorageVersions flags. +func (s *ServerRunOptions) StorageGroupsToEncodingVersion() (map[string]unversioned.GroupVersion, error) { + storageVersionMap := map[string]unversioned.GroupVersion{} + if s.DeprecatedStorageVersion != "" { + storageVersionMap[""] = unversioned.GroupVersion{Group: apiutil.GetGroup(s.DeprecatedStorageVersion), Version: apiutil.GetVersion(s.DeprecatedStorageVersion)} + } + + // First, get the defaults. + if err := mergeGroupVersionIntoMap(s.DefaultStorageVersions, storageVersionMap); err != nil { + return nil, err + } + // Override any defaults with the user settings. + if err := mergeGroupVersionIntoMap(s.StorageVersions, storageVersionMap); err != nil { + return nil, err + } + + return storageVersionMap, nil +} + +// dest must be a map of group to groupVersion. +func mergeGroupVersionIntoMap(gvList string, dest map[string]unversioned.GroupVersion) error { + for _, gvString := range strings.Split(gvList, ",") { + if gvString == "" { + continue + } + // We accept two formats. "group/version" OR + // "group=group/version". The latter is used when types + // move between groups. + if !strings.Contains(gvString, "=") { + gv, err := unversioned.ParseGroupVersion(gvString) + if err != nil { + return err + } + dest[gv.Group] = gv + + } else { + parts := strings.SplitN(gvString, "=", 2) + gv, err := unversioned.ParseGroupVersion(parts[1]) + if err != nil { + return err + } + dest[parts[0]] = gv + } + } + + return nil +} + +// Returns a clientset which can be used to talk to this apiserver. +func (s *ServerRunOptions) NewSelfClient() (clientset.Interface, error) { + clientConfig := &restclient.Config{ + Host: net.JoinHostPort(s.InsecureBindAddress.String(), strconv.Itoa(s.InsecurePort)), + // Increase QPS limits. The client is currently passed to all admission plugins, + // and those can be throttled in case of higher load on apiserver - see #22340 and #22422 + // for more details. Once #22422 is fixed, we may want to remove it. + QPS: 50, + Burst: 100, + } + if len(s.DeprecatedStorageVersion) != 0 { + gv, err := unversioned.ParseGroupVersion(s.DeprecatedStorageVersion) + if err != nil { + glog.Fatalf("error in parsing group version: %s", err) + } + clientConfig.GroupVersion = &gv + } + + return clientset.NewForConfig(clientConfig) +} + // AddFlags adds flags for a specific APIServer to the specified FlagSet func (s *ServerRunOptions) AddFlags(fs *pflag.FlagSet) { // Note: the weird ""+ in below lines seems to be the only way to get gofmt to @@ -171,6 +258,14 @@ func (s *ServerRunOptions) AddFlags(fs *pflag.FlagSet) { fs.Var(&s.ServiceNodePortRange, "service-node-ports", "Deprecated: see --service-node-port-range instead.") fs.MarkDeprecated("service-node-ports", "see --service-node-port-range instead.") + fs.StringVar(&s.DeprecatedStorageVersion, "storage-version", s.DeprecatedStorageVersion, "The version to store the legacy v1 resources with. Defaults to server preferred") + fs.MarkDeprecated("storage-version", "--storage-version is deprecated and will be removed when the v1 API is retired. See --storage-versions instead.") + fs.StringVar(&s.StorageVersions, "storage-versions", s.StorageVersions, "The per-group version to store resources in. "+ + "Specified in the format \"group1/version1,group2/version2,...\". "+ + "In the case where objects are moved from one group to the other, you may specify the format \"group1=group2/v1beta1,group3/v1beta1,...\". "+ + "You only need to pass the groups you wish to change from the defaults. "+ + "It defaults to a list of preferred versions of all registered groups, which is derived from the KUBE_API_VERSIONS environment variable.") + fs.StringVar(&s.TLSCertFile, "tls-cert-file", s.TLSCertFile, ""+ "File containing x509 Certificate for HTTPS. (CA cert, if any, concatenated after server cert). "+ "If HTTPS serving is enabled, and --tls-cert-file and --tls-private-key-file are not provided, "+ diff --git a/pkg/genericapiserver/server_run_options_test.go b/pkg/genericapiserver/server_run_options_test.go new file mode 100644 index 00000000000..e36418da9b5 --- /dev/null +++ b/pkg/genericapiserver/server_run_options_test.go @@ -0,0 +1,82 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 genericapiserver + +import ( + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/autoscaling" + "k8s.io/kubernetes/pkg/apis/extensions" +) + +func TestGenerateStorageVersionMap(t *testing.T) { + testCases := []struct { + legacyVersion string + storageVersions string + defaultVersions string + expectedMap map[string]unversioned.GroupVersion + }{ + { + legacyVersion: "v1", + storageVersions: "v1,extensions/v1beta1", + expectedMap: map[string]unversioned.GroupVersion{ + api.GroupName: {Version: "v1"}, + extensions.GroupName: {Group: "extensions", Version: "v1beta1"}, + }, + }, + { + legacyVersion: "", + storageVersions: "extensions/v1beta1,v1", + expectedMap: map[string]unversioned.GroupVersion{ + api.GroupName: {Version: "v1"}, + extensions.GroupName: {Group: "extensions", Version: "v1beta1"}, + }, + }, + { + legacyVersion: "", + storageVersions: "autoscaling=extensions/v1beta1,v1", + defaultVersions: "extensions/v1beta1,v1,autoscaling/v1", + expectedMap: map[string]unversioned.GroupVersion{ + api.GroupName: {Version: "v1"}, + autoscaling.GroupName: {Group: "extensions", Version: "v1beta1"}, + extensions.GroupName: {Group: "extensions", Version: "v1beta1"}, + }, + }, + { + legacyVersion: "", + storageVersions: "", + expectedMap: map[string]unversioned.GroupVersion{}, + }, + } + for i, test := range testCases { + s := ServerRunOptions{ + DeprecatedStorageVersion: test.legacyVersion, + StorageVersions: test.storageVersions, + DefaultStorageVersions: test.defaultVersions, + } + output, err := s.StorageGroupsToEncodingVersion() + if err != nil { + t.Errorf("%v: unexpected error: %v", i, err) + } + if !reflect.DeepEqual(test.expectedMap, output) { + t.Errorf("%v: unexpected error. expect: %v, got: %v", i, test.expectedMap, output) + } + } +}