diff --git a/cmd/kube-apiserver/app/server.go b/cmd/kube-apiserver/app/server.go index d6e2d5e4ce4..13e27e8cc06 100644 --- a/cmd/kube-apiserver/app/server.go +++ b/cmd/kube-apiserver/app/server.go @@ -36,10 +36,14 @@ import ( "k8s.io/kubernetes/pkg/admission" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/unversioned" + apiv1 "k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/apimachinery/registered" "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" @@ -249,7 +253,7 @@ func Run(s *options.APIServer) error { glog.Fatalf("Failure to start kubelet client: %v", err) } - apiGroupVersionOverrides, err := parseRuntimeConfig(s) + apiResourceConfigSource, err := parseRuntimeConfig(s) if err != nil { glog.Fatalf("error in parsing runtime-config: %s", err) } @@ -292,7 +296,7 @@ func Run(s *options.APIServer) error { } storageDestinations.AddAPIGroup("", etcdStorage) - if !apiGroupVersionOverrides["extensions/v1beta1"].Disable { + if apiResourceConfigSource.AnyResourcesForVersionEnabled(extensionsapiv1beta1.SchemeGroupVersion) { glog.Infof("Configuring extensions/v1beta1 storage destination") expGroup, err := registered.Group(extensions.GroupName) if err != nil { @@ -321,7 +325,7 @@ func Run(s *options.APIServer) error { // autoscaling/v1/horizontalpodautoscalers is a move from extensions/v1beta1/horizontalpodautoscalers. // The storage version needs to be either extensions/v1beta1 or autoscaling/v1. // Users must roll forward while using 1.2, because we will require the latter for 1.3. - if !apiGroupVersionOverrides["autoscaling/v1"].Disable { + if apiResourceConfigSource.AnyResourcesForVersionEnabled(autoscalingapiv1.SchemeGroupVersion) { glog.Infof("Configuring autoscaling/v1 storage destination") autoscalingGroup, err := registered.Group(autoscaling.GroupName) if err != nil { @@ -348,7 +352,7 @@ func Run(s *options.APIServer) error { // version needs to be either extensions/v1beta1 or batch/v1. Users // must roll forward while using 1.2, because we will require the // latter for 1.3. - if !apiGroupVersionOverrides["batch/v1"].Disable { + if apiResourceConfigSource.AnyResourcesForVersionEnabled(batchapiv1.SchemeGroupVersion) { glog.Infof("Configuring batch/v1 storage destination") batchGroup, err := registered.Group(batch.GroupName) if err != nil { @@ -464,7 +468,7 @@ func Run(s *options.APIServer) error { SupportsBasicAuth: len(s.BasicAuthFile) > 0, Authorizer: authorizer, AdmissionControl: admissionController, - APIGroupVersionOverrides: apiGroupVersionOverrides, + APIResourceConfigSource: apiResourceConfigSource, MasterServiceNamespace: s.MasterServiceNamespace, MasterCount: s.MasterCount, ExternalHost: s.ExternalHost, @@ -511,13 +515,24 @@ func getRuntimeConfigValue(s *options.APIServer, apiKey string, defaultValue boo return defaultValue } -// Parses the given runtime-config and formats it into map[string]ApiGroupVersionOverride -func parseRuntimeConfig(s *options.APIServer) (map[string]genericapiserver.APIGroupVersionOverride, error) { +// 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(), + } + + resourceConfig := master.DefaultAPIResourceConfigSource() + // "api/all=false" allows users to selectively enable specific api versions. - disableAllAPIs := false + enableAPIByDefault := true allAPIFlagValue, ok := s.RuntimeConfig["api/all"] if ok && allAPIFlagValue == "false" { - disableAllAPIs = true + enableAPIByDefault = false } // "api/legacy=false" allows users to disable legacy api versions. @@ -528,60 +543,40 @@ func parseRuntimeConfig(s *options.APIServer) (map[string]genericapiserver.APIGr } _ = disableLegacyAPIs // hush the compiler while we don't have legacy APIs to disable. - // "api/v1={true|false} allows users to enable/disable v1 API. + // "={true|false} allows users to enable/disable API. // This takes preference over api/all and api/legacy, if specified. - disableV1 := disableAllAPIs - v1GroupVersion := "api/v1" - disableV1 = !getRuntimeConfigValue(s, v1GroupVersion, !disableV1) - apiGroupVersionOverrides := map[string]genericapiserver.APIGroupVersionOverride{} - if disableV1 { - apiGroupVersionOverrides[v1GroupVersion] = genericapiserver.APIGroupVersionOverride{ - Disable: true, - } - } - - // "extensions/v1beta1={true|false} allows users to enable/disable the extensions API. - // This takes preference over api/all, if specified. - disableExtensions := disableAllAPIs - extensionsGroupVersion := "extensions/v1beta1" - // TODO: Make this a loop over all group/versions when there are more of them. - disableExtensions = !getRuntimeConfigValue(s, extensionsGroupVersion, !disableExtensions) - if disableExtensions { - apiGroupVersionOverrides[extensionsGroupVersion] = genericapiserver.APIGroupVersionOverride{ - Disable: true, - } - } - - disableAutoscaling := disableAllAPIs - autoscalingGroupVersion := "autoscaling/v1" - disableAutoscaling = !getRuntimeConfigValue(s, autoscalingGroupVersion, !disableAutoscaling) - if disableAutoscaling { - apiGroupVersionOverrides[autoscalingGroupVersion] = genericapiserver.APIGroupVersionOverride{ - Disable: true, - } - } - disableBatch := disableAllAPIs - batchGroupVersion := "batch/v1" - disableBatch = !getRuntimeConfigValue(s, batchGroupVersion, !disableBatch) - if disableBatch { - apiGroupVersionOverrides[batchGroupVersion] = genericapiserver.APIGroupVersionOverride{ - Disable: true, + for version, resourceSpecifier := range versionToResourceSpecifier { + enableVersion := getRuntimeConfigValue(s, resourceSpecifier, enableAPIByDefault) + if enableVersion { + resourceConfig.EnableVersions(version) + } else { + resourceConfig.DisableVersions(version) } } for key := range s.RuntimeConfig { - if strings.HasPrefix(key, v1GroupVersion+"/") { - return nil, fmt.Errorf("api/v1 resources cannot be enabled/disabled individually") - } else if strings.HasPrefix(key, extensionsGroupVersion+"/") { - resource := strings.TrimPrefix(key, extensionsGroupVersion+"/") + tokens := strings.Split(key, "/") + if len(tokens) != 3 { + continue + } - apiGroupVersionOverride := apiGroupVersionOverrides[extensionsGroupVersion] - if apiGroupVersionOverride.ResourceOverrides == nil { - apiGroupVersionOverride.ResourceOverrides = map[string]bool{} + 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) } - apiGroupVersionOverride.ResourceOverrides[resource] = getRuntimeConfigValue(s, key, false) - apiGroupVersionOverrides[extensionsGroupVersion] = apiGroupVersionOverride + + 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 apiGroupVersionOverrides, nil + return resourceConfig, nil } diff --git a/cmd/kube-apiserver/app/server_test.go b/cmd/kube-apiserver/app/server_test.go index 3d55406b1af..34a35aa755a 100644 --- a/cmd/kube-apiserver/app/server_test.go +++ b/cmd/kube-apiserver/app/server_test.go @@ -24,8 +24,10 @@ import ( "k8s.io/kubernetes/cmd/kube-apiserver/app/options" "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/genericapiserver" + "k8s.io/kubernetes/pkg/master" "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/storage" etcdstorage "k8s.io/kubernetes/pkg/storage/etcd" @@ -132,13 +134,15 @@ func TestUpdateEtcdOverrides(t *testing.T) { func TestParseRuntimeConfig(t *testing.T) { testCases := []struct { - runtimeConfig map[string]string - apiGroupVersionOverrides map[string]genericapiserver.APIGroupVersionOverride - err bool + runtimeConfig map[string]string + expectedAPIConfig func() *genericapiserver.ResourceConfig + err bool }{ { - runtimeConfig: map[string]string{}, - apiGroupVersionOverrides: map[string]genericapiserver.APIGroupVersionOverride{}, + runtimeConfig: map[string]string{}, + expectedAPIConfig: func() *genericapiserver.ResourceConfig { + return master.DefaultAPIResourceConfigSource() + }, err: false, }, { @@ -146,7 +150,9 @@ func TestParseRuntimeConfig(t *testing.T) { runtimeConfig: map[string]string{ "api/v1/pods": "false", }, - apiGroupVersionOverrides: map[string]genericapiserver.APIGroupVersionOverride{}, + expectedAPIConfig: func() *genericapiserver.ResourceConfig { + return master.DefaultAPIResourceConfigSource() + }, err: true, }, { @@ -154,10 +160,10 @@ func TestParseRuntimeConfig(t *testing.T) { runtimeConfig: map[string]string{ "api/v1": "false", }, - apiGroupVersionOverrides: map[string]genericapiserver.APIGroupVersionOverride{ - "api/v1": { - Disable: true, - }, + expectedAPIConfig: func() *genericapiserver.ResourceConfig { + config := master.DefaultAPIResourceConfigSource() + config.DisableVersions(unversioned.GroupVersion{Group: "", Version: "v1"}) + return config }, err: false, }, @@ -166,10 +172,10 @@ func TestParseRuntimeConfig(t *testing.T) { runtimeConfig: map[string]string{ "extensions/v1beta1": "false", }, - apiGroupVersionOverrides: map[string]genericapiserver.APIGroupVersionOverride{ - "extensions/v1beta1": { - Disable: true, - }, + expectedAPIConfig: func() *genericapiserver.ResourceConfig { + config := master.DefaultAPIResourceConfigSource() + config.DisableVersions(unversioned.GroupVersion{Group: "extensions", Version: "v1beta1"}) + return config }, err: false, }, @@ -178,28 +184,24 @@ func TestParseRuntimeConfig(t *testing.T) { runtimeConfig: map[string]string{ "extensions/v1beta1/deployments": "false", }, - apiGroupVersionOverrides: map[string]genericapiserver.APIGroupVersionOverride{ - "extensions/v1beta1": { - ResourceOverrides: map[string]bool{ - "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/deployments": "true", - "extensions/v1beta1/jobs": "false", + "extensions/v1beta1/anything": "true", + "extensions/v1beta1/jobs": "false", }, - apiGroupVersionOverrides: map[string]genericapiserver.APIGroupVersionOverride{ - "extensions/v1beta1": { - ResourceOverrides: map[string]bool{ - "deployments": true, - "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, }, @@ -208,7 +210,7 @@ func TestParseRuntimeConfig(t *testing.T) { s := &options.APIServer{ RuntimeConfig: test.runtimeConfig, } - apiGroupVersionOverrides, err := parseRuntimeConfig(s) + actualDisablers, err := parseRuntimeConfig(s) if err == nil && test.err { t.Fatalf("expected error for test: %v", test) @@ -216,8 +218,9 @@ func TestParseRuntimeConfig(t *testing.T) { t.Fatalf("unexpected error: %s, for test: %v", err, test) } - if err == nil && !reflect.DeepEqual(apiGroupVersionOverrides, test.apiGroupVersionOverrides) { - t.Fatalf("unexpected apiGroupVersionOverrides. Actual: %v, expected: %v", apiGroupVersionOverrides, test.apiGroupVersionOverrides) + expectedConfig := test.expectedAPIConfig() + if err == nil && !reflect.DeepEqual(actualDisablers, expectedConfig) { + t.Fatalf("%v: unexpected apiResourceDisablers. Actual: %q\n expected: %q", test.runtimeConfig, actualDisablers, expectedConfig) } } diff --git a/pkg/genericapiserver/genericapiserver.go b/pkg/genericapiserver/genericapiserver.go index 033b60386a7..f425033b3ae 100644 --- a/pkg/genericapiserver/genericapiserver.go +++ b/pkg/genericapiserver/genericapiserver.go @@ -164,15 +164,6 @@ func (s *StorageDestinations) Backends() []string { return backends.List() } -// Specifies the overrides for various API group versions. -// This can be used to enable/disable entire group versions or specific resources. -type APIGroupVersionOverride struct { - // Whether to enable or disable this group version. - Disable bool - // List of overrides for individual resources in this group version. - ResourceOverrides map[string]bool -} - // Info about an API group. type APIGroupInfo struct { GroupMeta apimachinery.GroupMeta @@ -218,7 +209,7 @@ type Config struct { // Note that this is ignored if either EnableSwaggerSupport or EnableUISupport is false. EnableSwaggerUI bool // Allows api group versions or specific resources to be conditionally enabled/disabled. - APIGroupVersionOverrides map[string]APIGroupVersionOverride + APIResourceConfigSource APIResourceConfigSource // allow downstream consumers to disable the index route EnableIndex bool EnableProfiling bool @@ -305,25 +296,24 @@ type GenericAPIServer struct { cacheTimeout time.Duration MinRequestTimeout time.Duration - mux apiserver.Mux - MuxHelper *apiserver.MuxHelper - HandlerContainer *restful.Container - RootWebService *restful.WebService - enableLogsSupport bool - enableUISupport bool - enableSwaggerSupport bool - enableSwaggerUI bool - enableProfiling bool - enableWatchCache bool - APIPrefix string - APIGroupPrefix string - corsAllowedOriginList []string - authenticator authenticator.Request - authorizer authorizer.Authorizer - AdmissionControl admission.Interface - MasterCount int - ApiGroupVersionOverrides map[string]APIGroupVersionOverride - RequestContextMapper api.RequestContextMapper + mux apiserver.Mux + MuxHelper *apiserver.MuxHelper + HandlerContainer *restful.Container + RootWebService *restful.WebService + enableLogsSupport bool + enableUISupport bool + enableSwaggerSupport bool + enableSwaggerUI bool + enableProfiling bool + enableWatchCache bool + APIPrefix string + APIGroupPrefix string + corsAllowedOriginList []string + authenticator authenticator.Request + authorizer authorizer.Authorizer + AdmissionControl admission.Interface + MasterCount int + RequestContextMapper api.RequestContextMapper // ExternalAddress is the address (hostname or IP and port) that should be used in // external (public internet) URLs for this GenericAPIServer. @@ -451,24 +441,23 @@ func New(c *Config) (*GenericAPIServer, error) { setDefaults(c) s := &GenericAPIServer{ - ServiceClusterIPRange: c.ServiceClusterIPRange, - ServiceNodePortRange: c.ServiceNodePortRange, - RootWebService: new(restful.WebService), - enableLogsSupport: c.EnableLogsSupport, - enableUISupport: c.EnableUISupport, - enableSwaggerSupport: c.EnableSwaggerSupport, - enableSwaggerUI: c.EnableSwaggerUI, - enableProfiling: c.EnableProfiling, - enableWatchCache: c.EnableWatchCache, - APIPrefix: c.APIPrefix, - APIGroupPrefix: c.APIGroupPrefix, - corsAllowedOriginList: c.CorsAllowedOriginList, - authenticator: c.Authenticator, - authorizer: c.Authorizer, - AdmissionControl: c.AdmissionControl, - ApiGroupVersionOverrides: c.APIGroupVersionOverrides, - RequestContextMapper: c.RequestContextMapper, - Serializer: c.Serializer, + ServiceClusterIPRange: c.ServiceClusterIPRange, + ServiceNodePortRange: c.ServiceNodePortRange, + RootWebService: new(restful.WebService), + enableLogsSupport: c.EnableLogsSupport, + enableUISupport: c.EnableUISupport, + enableSwaggerSupport: c.EnableSwaggerSupport, + enableSwaggerUI: c.EnableSwaggerUI, + enableProfiling: c.EnableProfiling, + enableWatchCache: c.EnableWatchCache, + APIPrefix: c.APIPrefix, + APIGroupPrefix: c.APIGroupPrefix, + corsAllowedOriginList: c.CorsAllowedOriginList, + authenticator: c.Authenticator, + authorizer: c.Authorizer, + AdmissionControl: c.AdmissionControl, + RequestContextMapper: c.RequestContextMapper, + Serializer: c.Serializer, cacheTimeout: c.CacheTimeout, MinRequestTimeout: time.Duration(c.MinRequestTimeout) * time.Second, diff --git a/pkg/genericapiserver/genericapiserver_test.go b/pkg/genericapiserver/genericapiserver_test.go index b5ce446ded8..a452552a823 100644 --- a/pkg/genericapiserver/genericapiserver_test.go +++ b/pkg/genericapiserver/genericapiserver_test.go @@ -88,7 +88,6 @@ func TestNew(t *testing.T) { assert.Equal(s.authenticator, config.Authenticator) assert.Equal(s.authorizer, config.Authorizer) assert.Equal(s.AdmissionControl, config.AdmissionControl) - assert.Equal(s.ApiGroupVersionOverrides, config.APIGroupVersionOverrides) assert.Equal(s.RequestContextMapper, config.RequestContextMapper) assert.Equal(s.cacheTimeout, config.CacheTimeout) assert.Equal(s.ExternalAddress, config.ExternalHost) diff --git a/pkg/genericapiserver/resource_config.go b/pkg/genericapiserver/resource_config.go new file mode 100644 index 00000000000..0f40e3774f1 --- /dev/null +++ b/pkg/genericapiserver/resource_config.go @@ -0,0 +1,172 @@ +/* +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 ( + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/util/sets" +) + +// APIResourceConfigSource is the interface to determine which versions and resources are enabled +type APIResourceConfigSource interface { + AnyVersionOfResourceEnabled(resource unversioned.GroupResource) bool + ResourceEnabled(resource unversioned.GroupVersionResource) bool + AllResourcesForVersionEnabled(version unversioned.GroupVersion) bool + AnyResourcesForVersionEnabled(version unversioned.GroupVersion) bool +} + +// Specifies the overrides for various API group versions. +// This can be used to enable/disable entire group versions or specific resources. +type GroupVersionResourceConfig struct { + // Whether to enable or disable this entire group version. This dominates any enablement check. + // Enable=true means the group version is enabled, and EnabledResources/DisabledResources are considered. + // Enable=false means the group version is disabled, and EnabledResources/DisabledResources are not considered. + Enable bool + + // DisabledResources lists the resources that are specifically disabled for a group/version + // DisabledResources trumps EnabledResources + DisabledResources sets.String + + // EnabledResources lists the resources that should be enabled by default. This is a little + // unusual, but we need it for compatibility with old code for now. An empty set means + // enable all, a non-empty set means that all other resources are disabled. + EnabledResources sets.String +} + +var _ APIResourceConfigSource = &ResourceConfig{} + +type ResourceConfig struct { + GroupVersionResourceConfigs map[unversioned.GroupVersion]*GroupVersionResourceConfig +} + +func NewResourceConfig() *ResourceConfig { + return &ResourceConfig{GroupVersionResourceConfigs: map[unversioned.GroupVersion]*GroupVersionResourceConfig{}} +} + +func NewGroupVersionResourceConfig() *GroupVersionResourceConfig { + return &GroupVersionResourceConfig{Enable: true, DisabledResources: sets.String{}, EnabledResources: sets.String{}} +} + +// DisableVersions disables the versions entirely. No resources (even those whitelisted in EnabledResources) will be enabled +func (o *ResourceConfig) DisableVersions(versions ...unversioned.GroupVersion) { + for _, version := range versions { + _, versionExists := o.GroupVersionResourceConfigs[version] + if !versionExists { + o.GroupVersionResourceConfigs[version] = NewGroupVersionResourceConfig() + } + + o.GroupVersionResourceConfigs[version].Enable = false + } +} + +func (o *ResourceConfig) EnableVersions(versions ...unversioned.GroupVersion) { + for _, version := range versions { + _, versionExists := o.GroupVersionResourceConfigs[version] + if !versionExists { + o.GroupVersionResourceConfigs[version] = NewGroupVersionResourceConfig() + } + + o.GroupVersionResourceConfigs[version].Enable = true + } +} + +func (o *ResourceConfig) DisableResources(resources ...unversioned.GroupVersionResource) { + for _, resource := range resources { + version := resource.GroupVersion() + _, versionExists := o.GroupVersionResourceConfigs[version] + if !versionExists { + o.GroupVersionResourceConfigs[version] = NewGroupVersionResourceConfig() + } + + o.GroupVersionResourceConfigs[version].DisabledResources.Insert(resource.Resource) + } +} + +func (o *ResourceConfig) EnableResources(resources ...unversioned.GroupVersionResource) { + for _, resource := range resources { + version := resource.GroupVersion() + _, versionExists := o.GroupVersionResourceConfigs[version] + if !versionExists { + o.GroupVersionResourceConfigs[version] = NewGroupVersionResourceConfig() + } + + o.GroupVersionResourceConfigs[version].EnabledResources.Insert(resource.Resource) + o.GroupVersionResourceConfigs[version].DisabledResources.Delete(resource.Resource) + } +} + +// AnyResourcesForVersionEnabled only considers matches based on exactly group/resource lexical matching. This means that +// resource renames across versions are NOT considered to be the same resource by this method. You'll need to manually check +// using the ResourceEnabled function. +func (o *ResourceConfig) AnyVersionOfResourceEnabled(resource unversioned.GroupResource) bool { + for version := range o.GroupVersionResourceConfigs { + if version.Group != resource.Group { + continue + } + + if o.ResourceEnabled(version.WithResource(resource.Resource)) { + return true + } + } + + return false +} + +func (o *ResourceConfig) ResourceEnabled(resource unversioned.GroupVersionResource) bool { + versionOverride, versionExists := o.GroupVersionResourceConfigs[resource.GroupVersion()] + if !versionExists { + return false + } + if !versionOverride.Enable { + return false + } + + if versionOverride.DisabledResources.Has(resource.Resource) { + return false + } + + if len(versionOverride.EnabledResources) > 0 { + return versionOverride.EnabledResources.Has(resource.Resource) + } + + return true +} + +func (o *ResourceConfig) AllResourcesForVersionEnabled(version unversioned.GroupVersion) bool { + versionOverride, versionExists := o.GroupVersionResourceConfigs[version] + if !versionExists { + return false + } + if !versionOverride.Enable { + return false + } + + if len(versionOverride.EnabledResources) == 0 && len(versionOverride.DisabledResources) == 0 { + return true + } + + return false +} + +func (o *ResourceConfig) AnyResourcesForVersionEnabled(version unversioned.GroupVersion) bool { + versionOverride, versionExists := o.GroupVersionResourceConfigs[version] + if !versionExists { + return false + } + + return versionOverride.Enable +} diff --git a/pkg/genericapiserver/resource_config_test.go b/pkg/genericapiserver/resource_config_test.go new file mode 100644 index 00000000000..0fbd6651fcc --- /dev/null +++ b/pkg/genericapiserver/resource_config_test.go @@ -0,0 +1,99 @@ +/* +Copyright 2015 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 ( + "testing" + + "k8s.io/kubernetes/pkg/api/unversioned" +) + +func TestDisabledVersion(t *testing.T) { + g1v1 := unversioned.GroupVersion{Group: "group1", Version: "version1"} + g1v2 := unversioned.GroupVersion{Group: "group1", Version: "version2"} + g2v1 := unversioned.GroupVersion{Group: "group2", Version: "version1"} + g3v1 := unversioned.GroupVersion{Group: "group3", Version: "version1"} + + resourceType := "the-resource" + disabledResourceType := "the-disabled-resource" + + config := NewResourceConfig() + + config.DisableVersions(g1v1) + config.EnableVersions(g1v2, g3v1) + config.EnableResources(g1v1.WithResource(resourceType), g2v1.WithResource(resourceType)) + config.DisableResources(g1v2.WithResource(disabledResourceType)) + + expectedEnabledResources := []unversioned.GroupVersionResource{ + g1v2.WithResource(resourceType), + g2v1.WithResource(resourceType), + } + expectedDisabledResources := []unversioned.GroupVersionResource{ + g1v1.WithResource(resourceType), g1v1.WithResource(disabledResourceType), + g1v2.WithResource(disabledResourceType), + g2v1.WithResource(disabledResourceType), + } + + for _, expectedResource := range expectedEnabledResources { + if !config.ResourceEnabled(expectedResource) { + t.Errorf("expected enabled for %v, from %v", expectedResource, config) + } + } + for _, expectedResource := range expectedDisabledResources { + if config.ResourceEnabled(expectedResource) { + t.Errorf("expected disabled for %v, from %v", expectedResource, config) + } + } + + if e, a := false, config.AnyResourcesForVersionEnabled(g1v1); e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := false, config.AllResourcesForVersionEnabled(g1v1); e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := true, config.AnyResourcesForVersionEnabled(g1v2); e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := false, config.AllResourcesForVersionEnabled(g1v2); e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := true, config.AnyResourcesForVersionEnabled(g3v1); e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := true, config.AllResourcesForVersionEnabled(g3v1); e != a { + t.Errorf("expected %v, got %v", e, a) + } + + expectedEnabledAnyVersionResources := []unversioned.GroupResource{ + {Group: "group1", Resource: resourceType}, + } + expectedDisabledAnyResources := []unversioned.GroupResource{ + {Group: "group1", Resource: disabledResourceType}, + } + + for _, expectedResource := range expectedEnabledAnyVersionResources { + if !config.AnyVersionOfResourceEnabled(expectedResource) { + t.Errorf("expected enabled for %v, from %v", expectedResource, config) + } + } + for _, expectedResource := range expectedDisabledAnyResources { + if config.AnyVersionOfResourceEnabled(expectedResource) { + t.Errorf("expected disabled for %v, from %v", expectedResource, config) + } + } + +} diff --git a/pkg/master/master.go b/pkg/master/master.go index 6af504a4aee..cfcf263d183 100644 --- a/pkg/master/master.go +++ b/pkg/master/master.go @@ -30,10 +30,14 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/rest" "k8s.io/kubernetes/pkg/api/unversioned" + apiv1 "k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/apimachinery/registered" "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" apiservermetrics "k8s.io/kubernetes/pkg/apiserver/metrics" "k8s.io/kubernetes/pkg/genericapiserver" @@ -76,7 +80,6 @@ import ( "k8s.io/kubernetes/pkg/storage" etcdmetrics "k8s.io/kubernetes/pkg/storage/etcd/metrics" etcdutil "k8s.io/kubernetes/pkg/storage/etcd/util" - "k8s.io/kubernetes/pkg/util/sets" "k8s.io/kubernetes/pkg/util/wait" daemonetcd "k8s.io/kubernetes/pkg/registry/daemonset/etcd" @@ -182,7 +185,7 @@ func (m *Master) InstallAPIs(c *Config) { apiGroupsInfo := []genericapiserver.APIGroupInfo{} // Install v1 unless disabled. - if !m.ApiGroupVersionOverrides["api/v1"].Disable { + if c.APIResourceConfigSource.AnyResourcesForVersionEnabled(apiv1.SchemeGroupVersion) { // Install v1 API. m.initV1ResourcesStorage(c) apiGroupInfo := genericapiserver.APIGroupInfo{ @@ -227,7 +230,7 @@ func (m *Master) InstallAPIs(c *Config) { allGroups := []unversioned.APIGroup{} // Install extensions unless disabled. - if !m.ApiGroupVersionOverrides["extensions/v1beta1"].Disable { + if c.APIResourceConfigSource.AnyResourcesForVersionEnabled(extensionsapiv1beta1.SchemeGroupVersion) { m.thirdPartyStorage = c.StorageDestinations.APIGroups[extensions.GroupName].Default m.thirdPartyResources = map[string]thirdPartyEntry{} @@ -269,12 +272,12 @@ func (m *Master) InstallAPIs(c *Config) { } // Install autoscaling unless disabled. - if !m.ApiGroupVersionOverrides["autoscaling/v1"].Disable { + if c.APIResourceConfigSource.AnyResourcesForVersionEnabled(autoscalingapiv1.SchemeGroupVersion) { autoscalingResources := m.getAutoscalingResources(c) autoscalingGroupMeta := registered.GroupOrDie(autoscaling.GroupName) // Hard code preferred group version to autoscaling/v1 - autoscalingGroupMeta.GroupVersion = unversioned.GroupVersion{Group: "autoscaling", Version: "v1"} + autoscalingGroupMeta.GroupVersion = autoscalingapiv1.SchemeGroupVersion apiGroupInfo := genericapiserver.APIGroupInfo{ GroupMeta: *autoscalingGroupMeta, @@ -301,12 +304,12 @@ func (m *Master) InstallAPIs(c *Config) { } // Install batch unless disabled. - if !m.ApiGroupVersionOverrides["batch/v1"].Disable { + if c.APIResourceConfigSource.AnyResourcesForVersionEnabled(batchapiv1.SchemeGroupVersion) { batchResources := m.getBatchResources(c) batchGroupMeta := registered.GroupOrDie(batch.GroupName) // Hard code preferred group version to batch/v1 - batchGroupMeta.GroupVersion = unversioned.GroupVersion{Group: "batch", Version: "v1"} + batchGroupMeta.GroupVersion = batchapiv1.SchemeGroupVersion apiGroupInfo := genericapiserver.APIGroupInfo{ GroupMeta: *batchGroupMeta, @@ -668,17 +671,6 @@ func (m *Master) thirdpartyapi(group, kind, version string) *apiserver.APIGroupV // getExperimentalResources returns the resources for extensions api func (m *Master) getExtensionResources(c *Config) map[string]rest.Storage { - // All resources except these are disabled by default. - enabledResources := sets.NewString("daemonsets", "deployments", "horizontalpodautoscalers", "ingresses", "jobs", "replicasets", "thirdpartyresources") - resourceOverrides := m.ApiGroupVersionOverrides["extensions/v1beta1"].ResourceOverrides - isEnabled := func(resource string) bool { - // Check if the resource has been overriden. - enabled, ok := resourceOverrides[resource] - if !ok { - return enabledResources.Has(resource) - } - return enabled - } restOptions := func(resource string) generic.RESTOptions { return generic.RESTOptions{ Storage: c.StorageDestinations.Get(extensions.GroupName, resource), @@ -686,18 +678,20 @@ func (m *Master) getExtensionResources(c *Config) map[string]rest.Storage { DeleteCollectionWorkers: m.deleteCollectionWorkers, } } + // TODO update when we support more than one version of this group + version := extensionsapiv1beta1.SchemeGroupVersion storage := map[string]rest.Storage{} - if isEnabled("horizontalpodautoscalers") { + if c.APIResourceConfigSource.ResourceEnabled(version.WithResource("horizontalpodautoscalers")) { m.constructHPAResources(c, storage) controllerStorage := expcontrolleretcd.NewStorage( generic.RESTOptions{Storage: c.StorageDestinations.Get("", "replicationControllers"), Decorator: m.StorageDecorator(), DeleteCollectionWorkers: m.deleteCollectionWorkers}) storage["replicationcontrollers"] = controllerStorage.ReplicationController storage["replicationcontrollers/scale"] = controllerStorage.Scale } - if isEnabled("thirdpartyresources") { - thirdPartyResourceStorage := thirdpartyresourceetcd.NewREST(restOptions("thirdpartyresources")) + thirdPartyResourceStorage := thirdpartyresourceetcd.NewREST(restOptions("thirdpartyresources")) + if c.APIResourceConfigSource.ResourceEnabled(version.WithResource("thirdpartyresources")) { thirdPartyControl := ThirdPartyController{ master: m, thirdPartyResourceRegistry: thirdPartyResourceStorage, @@ -712,32 +706,32 @@ func (m *Master) getExtensionResources(c *Config) map[string]rest.Storage { storage["thirdpartyresources"] = thirdPartyResourceStorage } - if isEnabled("daemonsets") { - daemonSetStorage, daemonSetStatusStorage := daemonetcd.NewREST(restOptions("daemonsets")) + daemonSetStorage, daemonSetStatusStorage := daemonetcd.NewREST(restOptions("daemonsets")) + if c.APIResourceConfigSource.ResourceEnabled(version.WithResource("daemonsets")) { storage["daemonsets"] = daemonSetStorage storage["daemonsets/status"] = daemonSetStatusStorage } - if isEnabled("deployments") { - deploymentStorage := deploymentetcd.NewStorage(restOptions("deployments")) + deploymentStorage := deploymentetcd.NewStorage(restOptions("deployments")) + if c.APIResourceConfigSource.ResourceEnabled(version.WithResource("deployments")) { storage["deployments"] = deploymentStorage.Deployment storage["deployments/status"] = deploymentStorage.Status storage["deployments/rollback"] = deploymentStorage.Rollback storage["deployments/scale"] = deploymentStorage.Scale } - if isEnabled("jobs") { + if c.APIResourceConfigSource.ResourceEnabled(version.WithResource("jobs")) { m.constructJobResources(c, storage) } - if isEnabled("ingresses") { - ingressStorage, ingressStatusStorage := ingressetcd.NewREST(restOptions("ingresses")) + ingressStorage, ingressStatusStorage := ingressetcd.NewREST(restOptions("ingresses")) + if c.APIResourceConfigSource.ResourceEnabled(version.WithResource("ingresses")) { storage["ingresses"] = ingressStorage storage["ingresses/status"] = ingressStatusStorage } - if isEnabled("podsecuritypolicy") { - podSecurityPolicyStorage := pspetcd.NewREST(restOptions("podsecuritypolicy")) + podSecurityPolicyStorage := pspetcd.NewREST(restOptions("podsecuritypolicy")) + if c.APIResourceConfigSource.ResourceEnabled(version.WithResource("podsecuritypolicy")) { storage["podSecurityPolicies"] = podSecurityPolicyStorage } - if isEnabled("replicasets") { - replicaSetStorage := replicasetetcd.NewStorage(restOptions("replicasets")) + replicaSetStorage := replicasetetcd.NewStorage(restOptions("replicasets")) + if c.APIResourceConfigSource.ResourceEnabled(version.WithResource("replicasets")) { storage["replicasets"] = replicaSetStorage.ReplicaSet storage["replicasets/status"] = replicaSetStorage.Status storage["replicasets/scale"] = replicaSetStorage.Scale @@ -767,17 +761,11 @@ func (m *Master) constructHPAResources(c *Config, restStorage map[string]rest.St // getAutoscalingResources returns the resources for autoscaling api func (m *Master) getAutoscalingResources(c *Config) map[string]rest.Storage { - resourceOverrides := m.ApiGroupVersionOverrides["autoscaling/v1"].ResourceOverrides - isEnabled := func(resource string) bool { - // Check if the resource has been overriden. - if enabled, ok := resourceOverrides[resource]; ok { - return enabled - } - return !m.ApiGroupVersionOverrides["autoscaling/v1"].Disable - } + // TODO update when we support more than one version of this group + version := autoscalingapiv1.SchemeGroupVersion storage := map[string]rest.Storage{} - if isEnabled("horizontalpodautoscalers") { + if c.APIResourceConfigSource.ResourceEnabled(version.WithResource("horizontalpodautoscalers")) { m.constructHPAResources(c, storage) } return storage @@ -804,17 +792,11 @@ func (m *Master) constructJobResources(c *Config, restStorage map[string]rest.St // getBatchResources returns the resources for batch api func (m *Master) getBatchResources(c *Config) map[string]rest.Storage { - resourceOverrides := m.ApiGroupVersionOverrides["batch/v1"].ResourceOverrides - isEnabled := func(resource string) bool { - // Check if the resource has been overriden. - if enabled, ok := resourceOverrides[resource]; ok { - return enabled - } - return !m.ApiGroupVersionOverrides["batch/v1"].Disable - } + // TODO update when we support more than one version of this group + version := batchapiv1.SchemeGroupVersion storage := map[string]rest.Storage{} - if isEnabled("jobs") { + if c.APIResourceConfigSource.ResourceEnabled(version.WithResource("jobs")) { m.constructJobResources(c, storage) } return storage @@ -869,3 +851,21 @@ func (m *Master) IsTunnelSyncHealthy(req *http.Request) error { } return nil } + +func DefaultAPIResourceConfigSource() *genericapiserver.ResourceConfig { + ret := genericapiserver.NewResourceConfig() + ret.EnableVersions(apiv1.SchemeGroupVersion, extensionsapiv1beta1.SchemeGroupVersion, batchapiv1.SchemeGroupVersion, autoscalingapiv1.SchemeGroupVersion) + + // all extensions resources except these are disabled by default + ret.EnableResources( + extensionsapiv1beta1.SchemeGroupVersion.WithResource("daemonsets"), + extensionsapiv1beta1.SchemeGroupVersion.WithResource("deployments"), + extensionsapiv1beta1.SchemeGroupVersion.WithResource("horizontalpodautoscalers"), + extensionsapiv1beta1.SchemeGroupVersion.WithResource("ingresses"), + extensionsapiv1beta1.SchemeGroupVersion.WithResource("jobs"), + extensionsapiv1beta1.SchemeGroupVersion.WithResource("replicasets"), + extensionsapiv1beta1.SchemeGroupVersion.WithResource("thirdpartyresources"), + ) + + return ret +} diff --git a/pkg/master/master_test.go b/pkg/master/master_test.go index 08130274921..5bb2a549e42 100644 --- a/pkg/master/master_test.go +++ b/pkg/master/master_test.go @@ -95,6 +95,7 @@ func newMaster(t *testing.T) (*Master, *etcdtesting.EtcdTestServer, Config, *ass config.KubeletClient = client.FakeKubeletClient{} config.APIPrefix = "/api" config.APIGroupPrefix = "/apis" + config.APIResourceConfigSource = DefaultAPIResourceConfigSource() config.ProxyDialer = func(network, addr string) (net.Conn, error) { return nil, nil } config.ProxyTLSClientConfig = &tls.Config{} @@ -126,7 +127,6 @@ func TestNew(t *testing.T) { assert.Equal(master.tunneler, config.Tunneler) assert.Equal(master.APIPrefix, config.APIPrefix) assert.Equal(master.APIGroupPrefix, config.APIGroupPrefix) - assert.Equal(master.ApiGroupVersionOverrides, config.APIGroupVersionOverrides) assert.Equal(master.RequestContextMapper, config.RequestContextMapper) assert.Equal(master.MasterCount, config.MasterCount) assert.Equal(master.ClusterIP, config.PublicAddress) diff --git a/test/integration/framework/master_utils.go b/test/integration/framework/master_utils.go index 41925624785..305a5dc0ec1 100644 --- a/test/integration/framework/master_utils.go +++ b/test/integration/framework/master_utils.go @@ -168,13 +168,14 @@ func NewMasterConfig() *master.Config { return &master.Config{ Config: &genericapiserver.Config{ - StorageDestinations: storageDestinations, - StorageVersions: storageVersions, - APIPrefix: "/api", - APIGroupPrefix: "/apis", - Authorizer: apiserver.NewAlwaysAllowAuthorizer(), - AdmissionControl: admit.NewAlwaysAdmit(), - Serializer: api.Codecs, + StorageDestinations: storageDestinations, + StorageVersions: storageVersions, + APIResourceConfigSource: master.DefaultAPIResourceConfigSource(), + APIPrefix: "/api", + APIGroupPrefix: "/apis", + Authorizer: apiserver.NewAlwaysAllowAuthorizer(), + AdmissionControl: admit.NewAlwaysAdmit(), + Serializer: api.Codecs, }, KubeletClient: kubeletclient.FakeKubeletClient{}, } @@ -186,6 +187,7 @@ func NewIntegrationTestMasterConfig() *master.Config { masterConfig.EnableCoreControllers = true masterConfig.EnableIndex = true masterConfig.PublicAddress = net.ParseIP("192.168.10.4") + masterConfig.APIResourceConfigSource = master.DefaultAPIResourceConfigSource() return masterConfig }