diff --git a/cmd/kube-apiserver/app/server.go b/cmd/kube-apiserver/app/server.go index e3a9da71ea..d260b4bca6 100644 --- a/cmd/kube-apiserver/app/server.go +++ b/cmd/kube-apiserver/app/server.go @@ -408,30 +408,11 @@ func (s *APIServer) Run(_ []string) error { glog.Fatalf("Failure to start kubelet client: %v", err) } - // "api/all=false" allows users to selectively enable specific api versions. - disableAllAPIs := false - allAPIFlagValue, ok := s.RuntimeConfig["api/all"] - if ok && allAPIFlagValue == "false" { - disableAllAPIs = true + apiGroupVersionOverrides, err := s.parseRuntimeConfig() + if err != nil { + glog.Fatalf("error in parsing runtime-config: %s", err) } - // "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. - - // "api/v1={true|false} allows users to enable/disable v1 API. - // This takes preference over api/all and api/legacy, if specified. - disableV1 := disableAllAPIs - disableV1 = !s.getRuntimeConfigValue("api/v1", !disableV1) - - // "extensions/v1beta1={true|false} allows users to enable/disable the experimental API. - // This takes preference over api/all, if specified. - enableExp := s.getRuntimeConfigValue("extensions/v1beta1", false) - clientConfig := &client.Config{ Host: net.JoinHostPort(s.InsecureBindAddress.String(), strconv.Itoa(s.InsecurePort)), Version: s.DeprecatedStorageVersion, @@ -458,17 +439,17 @@ func (s *APIServer) Run(_ []string) error { } storageDestinations.AddAPIGroup("", etcdStorage) - if enableExp { + if !apiGroupVersionOverrides["extensions/v1beta1"].Disable { expGroup, err := latest.Group("extensions") if err != nil { - glog.Fatalf("Experimental API is enabled in runtime config, but not enabled in the environment variable KUBE_API_VERSIONS. Error: %v", err) + glog.Fatalf("Extensions API is enabled in runtime config, but not enabled in the environment variable KUBE_API_VERSIONS. Error: %v", err) } if _, found := storageVersions[expGroup.Group]; !found { glog.Fatalf("Couldn't find the storage version for group: %q in storageVersions: %v", expGroup.Group, storageVersions) } expEtcdStorage, err := newEtcd(s.EtcdConfigFile, s.EtcdServerList, expGroup.InterfacesFor, storageVersions[expGroup.Group], s.EtcdPathPrefix) if err != nil { - glog.Fatalf("Invalid experimental storage version or misconfigured etcd: %v", err) + glog.Fatalf("Invalid extensions storage version or misconfigured etcd: %v", err) } storageDestinations.AddAPIGroup("extensions", expEtcdStorage) } @@ -558,8 +539,7 @@ func (s *APIServer) Run(_ []string) error { SupportsBasicAuth: len(s.BasicAuthFile) > 0, Authorizer: authorizer, AdmissionControl: admissionController, - DisableV1: disableV1, - EnableExp: enableExp, + APIGroupVersionOverrides: apiGroupVersionOverrides, MasterServiceNamespace: s.MasterServiceNamespace, ClusterName: s.ClusterName, ExternalHost: s.ExternalHost, @@ -680,3 +660,61 @@ func (s *APIServer) getRuntimeConfigValue(apiKey string, defaultValue bool) bool } return defaultValue } + +// Parses the given runtime-config and formats it into map[string]ApiGroupVersionOverride +func (s *APIServer) parseRuntimeConfig() (map[string]master.APIGroupVersionOverride, error) { + // "api/all=false" allows users to selectively enable specific api versions. + disableAllAPIs := false + allAPIFlagValue, ok := s.RuntimeConfig["api/all"] + if ok && allAPIFlagValue == "false" { + disableAllAPIs = true + } + + // "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. + + // "api/v1={true|false} allows users to enable/disable v1 API. + // This takes preference over api/all and api/legacy, if specified. + disableV1 := disableAllAPIs + v1GroupVersion := "api/v1" + disableV1 = !s.getRuntimeConfigValue(v1GroupVersion, !disableV1) + apiGroupVersionOverrides := map[string]master.APIGroupVersionOverride{} + if disableV1 { + apiGroupVersionOverrides[v1GroupVersion] = master.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 = !s.getRuntimeConfigValue(extensionsGroupVersion, !disableExtensions) + if disableExtensions { + apiGroupVersionOverrides[extensionsGroupVersion] = master.APIGroupVersionOverride{ + Disable: true, + } + } + + 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+"/") + + apiGroupVersionOverride := apiGroupVersionOverrides[extensionsGroupVersion] + if apiGroupVersionOverride.ResourceOverrides == nil { + apiGroupVersionOverride.ResourceOverrides = map[string]bool{} + } + apiGroupVersionOverride.ResourceOverrides[resource] = s.getRuntimeConfigValue(key, false) + apiGroupVersionOverrides[extensionsGroupVersion] = apiGroupVersionOverride + } + } + return apiGroupVersionOverrides, nil +} diff --git a/cmd/kube-apiserver/app/server_test.go b/cmd/kube-apiserver/app/server_test.go index 0d83beec9d..dc8bdf3bbf 100644 --- a/cmd/kube-apiserver/app/server_test.go +++ b/cmd/kube-apiserver/app/server_test.go @@ -154,3 +154,96 @@ func TestUpdateEtcdOverrides(t *testing.T) { } } } + +func TestParseRuntimeConfig(t *testing.T) { + testCases := []struct { + runtimeConfig map[string]string + apiGroupVersionOverrides map[string]master.APIGroupVersionOverride + err bool + }{ + { + runtimeConfig: map[string]string{}, + apiGroupVersionOverrides: map[string]master.APIGroupVersionOverride{}, + err: false, + }, + { + // Cannot override v1 resources. + runtimeConfig: map[string]string{ + "api/v1/pods": "false", + }, + apiGroupVersionOverrides: map[string]master.APIGroupVersionOverride{}, + err: true, + }, + { + // Disable v1. + runtimeConfig: map[string]string{ + "api/v1": "false", + }, + apiGroupVersionOverrides: map[string]master.APIGroupVersionOverride{ + "api/v1": { + Disable: true, + }, + }, + err: false, + }, + { + // Disable extensions. + runtimeConfig: map[string]string{ + "extensions/v1beta1": "false", + }, + apiGroupVersionOverrides: map[string]master.APIGroupVersionOverride{ + "extensions/v1beta1": { + Disable: true, + }, + }, + err: false, + }, + { + // Disable deployments. + runtimeConfig: map[string]string{ + "extensions/v1beta1/deployments": "false", + }, + apiGroupVersionOverrides: map[string]master.APIGroupVersionOverride{ + "extensions/v1beta1": { + ResourceOverrides: map[string]bool{ + "deployments": false, + }, + }, + }, + err: false, + }, + { + // Enable deployments and disable jobs. + runtimeConfig: map[string]string{ + "extensions/v1beta1/deployments": "true", + "extensions/v1beta1/jobs": "false", + }, + apiGroupVersionOverrides: map[string]master.APIGroupVersionOverride{ + "extensions/v1beta1": { + ResourceOverrides: map[string]bool{ + "deployments": true, + "jobs": false, + }, + }, + }, + err: false, + }, + } + for _, test := range testCases { + s := &APIServer{ + RuntimeConfig: test.runtimeConfig, + } + apiGroupVersionOverrides, err := s.parseRuntimeConfig() + + if err == nil && test.err { + t.Fatalf("expected error for test: %q", test) + } else if err != nil && !test.err { + t.Fatalf("unexpected error: %s, for test: %q", err, test) + } + + if err == nil && !reflect.DeepEqual(apiGroupVersionOverrides, test.apiGroupVersionOverrides) { + t.Fatalf("unexpected apiGroupVersionOverrides. Actual: %q, expected: %q", apiGroupVersionOverrides, test.apiGroupVersionOverrides) + } + } + +} diff --git a/pkg/master/master.go b/pkg/master/master.go index d62afadcd5..24bb3b1381 100644 --- a/pkg/master/master.go +++ b/pkg/master/master.go @@ -80,6 +80,7 @@ import ( "k8s.io/kubernetes/pkg/ui" "k8s.io/kubernetes/pkg/util" "k8s.io/kubernetes/pkg/util/sets" + utilSets "k8s.io/kubernetes/pkg/util/sets" daemonetcd "k8s.io/kubernetes/pkg/registry/daemonset/etcd" horizontalpodautoscaleretcd "k8s.io/kubernetes/pkg/registry/horizontalpodautoscaler/etcd" @@ -166,6 +167,15 @@ 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 +} + // Config is a structure used to configure a Master. type Config struct { StorageDestinations StorageDestinations @@ -180,9 +190,8 @@ type Config struct { EnableUISupport bool // allow downstream consumers to disable swagger EnableSwaggerSupport bool - // allow api versions to be conditionally disabled - DisableV1 bool - EnableExp bool + // Allows api group versions or specific resources to be conditionally enabled/disabled. + APIGroupVersionOverrides map[string]APIGroupVersionOverride // allow downstream consumers to disable the index route EnableIndex bool EnableProfiling bool @@ -271,26 +280,25 @@ type Master struct { cacheTimeout time.Duration minRequestTimeout time.Duration - mux apiserver.Mux - muxHelper *apiserver.MuxHelper - handlerContainer *restful.Container - rootWebService *restful.WebService - enableCoreControllers bool - enableLogsSupport bool - enableUISupport bool - enableSwaggerSupport bool - enableProfiling bool - enableWatchCache bool - apiPrefix string - apiGroupPrefix string - corsAllowedOriginList []string - authenticator authenticator.Request - authorizer authorizer.Authorizer - admissionControl admission.Interface - masterCount int - v1 bool - exp bool - requestContextMapper api.RequestContextMapper + mux apiserver.Mux + muxHelper *apiserver.MuxHelper + handlerContainer *restful.Container + rootWebService *restful.WebService + enableCoreControllers bool + enableLogsSupport bool + enableUISupport bool + enableSwaggerSupport 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 // External host is the name that should be used in external (public internet) URLs for this master externalHost string @@ -435,24 +443,23 @@ func New(c *Config) *Master { } m := &Master{ - serviceClusterIPRange: c.ServiceClusterIPRange, - serviceNodePortRange: c.ServiceNodePortRange, - rootWebService: new(restful.WebService), - enableCoreControllers: c.EnableCoreControllers, - enableLogsSupport: c.EnableLogsSupport, - enableUISupport: c.EnableUISupport, - enableSwaggerSupport: c.EnableSwaggerSupport, - enableProfiling: c.EnableProfiling, - enableWatchCache: c.EnableWatchCache, - apiPrefix: c.APIPrefix, - apiGroupPrefix: c.APIGroupPrefix, - corsAllowedOriginList: c.CorsAllowedOriginList, - authenticator: c.Authenticator, - authorizer: c.Authorizer, - admissionControl: c.AdmissionControl, - v1: !c.DisableV1, - exp: c.EnableExp, - requestContextMapper: c.RequestContextMapper, + serviceClusterIPRange: c.ServiceClusterIPRange, + serviceNodePortRange: c.ServiceNodePortRange, + rootWebService: new(restful.WebService), + enableCoreControllers: c.EnableCoreControllers, + enableLogsSupport: c.EnableLogsSupport, + enableUISupport: c.EnableUISupport, + enableSwaggerSupport: c.EnableSwaggerSupport, + 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, cacheTimeout: c.CacheTimeout, minRequestTimeout: time.Duration(c.MinRequestTimeout) * time.Second, @@ -624,7 +631,8 @@ func (m *Master) init(c *Config) { } apiVersions := []string{} - if m.v1 { + // Install v1 unless disabled. + if !m.apiGroupVersionOverrides["api/v1"].Disable { if err := m.api_v1().InstallREST(m.handlerContainer); err != nil { glog.Fatalf("Unable to setup API v1: %v", err) } @@ -637,7 +645,8 @@ func (m *Master) init(c *Config) { // allGroups records all supported groups at /apis allGroups := []unversioned.APIGroup{} - if m.exp { + // Install extensions unless disabled. + if !m.apiGroupVersionOverrides["extensions/v1beta1"].Disable { m.thirdPartyStorage = c.StorageDestinations.APIGroups["extensions"].Default m.thirdPartyResources = map[string]*thirdpartyresourcedataetcd.REST{} @@ -1023,45 +1032,71 @@ func (m *Master) thirdpartyapi(group, kind, version string) *apiserver.APIGroupV // experimental returns the resources and codec for the experimental api func (m *Master) experimental(c *Config) *apiserver.APIGroupVersion { - controllerStorage := expcontrolleretcd.NewStorage(c.StorageDestinations.get("", "replicationControllers")) + // All resources except these are disabled by default. + enabledResources := utilSets.NewString("jobs", "horizontalpodautoscalers", "ingress") + 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 + } dbClient := func(resource string) storage.Interface { return c.StorageDestinations.get("extensions", resource) } - autoscalerStorage, autoscalerStatusStorage := horizontalpodautoscaleretcd.NewREST(dbClient("horizonalpodautoscalers")) - thirdPartyResourceStorage := thirdpartyresourceetcd.NewREST(dbClient("thirdpartyresources")) - daemonSetStorage, daemonSetStatusStorage := daemonetcd.NewREST(dbClient("daemonsets")) - deploymentStorage := deploymentetcd.NewStorage(dbClient("deployments")) - jobStorage, jobStatusStorage := jobetcd.NewREST(dbClient("jobs")) - ingressStorage, ingressStatusStorage := ingressetcd.NewREST(dbClient("ingress")) - thirdPartyControl := ThirdPartyController{ - master: m, - thirdPartyResourceRegistry: thirdPartyResourceStorage, + storage := map[string]rest.Storage{} + if isEnabled("replicationcontrollers") { + controllerStorage := expcontrolleretcd.NewStorage(c.StorageDestinations.get("", "replicationControllers")) + storage["replicationcontrollers"] = controllerStorage.ReplicationController + storage["replicationcontrollers/scale"] = controllerStorage.Scale } - go func() { - util.Forever(func() { - if err := thirdPartyControl.SyncResources(); err != nil { - glog.Warningf("third party resource sync failed: %v", err) - } - }, 10*time.Second) - }() - storage := map[string]rest.Storage{ - strings.ToLower("replicationControllers"): controllerStorage.ReplicationController, - strings.ToLower("replicationControllers/scale"): controllerStorage.Scale, - strings.ToLower("horizontalpodautoscalers"): autoscalerStorage, - strings.ToLower("horizontalpodautoscalers/status"): autoscalerStatusStorage, - strings.ToLower("thirdpartyresources"): thirdPartyResourceStorage, - strings.ToLower("daemonsets"): daemonSetStorage, - strings.ToLower("daemonsets/status"): daemonSetStatusStorage, - strings.ToLower("deployments"): deploymentStorage.Deployment, - strings.ToLower("deployments/scale"): deploymentStorage.Scale, - strings.ToLower("jobs"): jobStorage, - strings.ToLower("jobs/status"): jobStatusStorage, - strings.ToLower("ingress"): ingressStorage, - strings.ToLower("ingress/status"): ingressStatusStorage, + if isEnabled("horizontalpodautoscalers") { + autoscalerStorage, autoscalerStatusStorage := horizontalpodautoscaleretcd.NewREST(dbClient("horizonalpodautoscalers")) + storage["horizontalpodautoscalers"] = autoscalerStorage + storage["horizontalpodautoscalers/status"] = autoscalerStatusStorage + } + if isEnabled("thirdpartyresources") { + thirdPartyResourceStorage := thirdpartyresourceetcd.NewREST(dbClient("thirdpartyresources")) + thirdPartyControl := ThirdPartyController{ + master: m, + thirdPartyResourceRegistry: thirdPartyResourceStorage, + } + go func() { + util.Forever(func() { + if err := thirdPartyControl.SyncResources(); err != nil { + glog.Warningf("third party resource sync failed: %v", err) + } + }, 10*time.Second) + }() + + storage["thirdpartyresources"] = thirdPartyResourceStorage } - expMeta := latest.GroupOrDie("extensions") + if isEnabled("daemonsets") { + daemonSetStorage, daemonSetStatusStorage := daemonetcd.NewREST(dbClient("daemonsets")) + storage["daemonsets"] = daemonSetStorage + storage["daemonsets/status"] = daemonSetStatusStorage + } + if isEnabled("deployments") { + deploymentStorage := deploymentetcd.NewStorage(dbClient("deployments")) + storage["deployments"] = deploymentStorage.Deployment + storage["deployments/scale"] = deploymentStorage.Scale + } + if isEnabled("jobs") { + jobStorage, jobStatusStorage := jobetcd.NewREST(dbClient("jobs")) + storage["jobs"] = jobStorage + storage["jobs/status"] = jobStatusStorage + } + if isEnabled("ingress") { + ingressStorage, ingressStatusStorage := ingressetcd.NewREST(dbClient("ingress")) + storage["ingress"] = ingressStorage + storage["ingress/status"] = ingressStatusStorage + } + + extensionsGroup := latest.GroupOrDie("extensions") return &apiserver.APIGroupVersion{ Root: m.apiGroupPrefix, @@ -1071,11 +1106,11 @@ func (m *Master) experimental(c *Config) *apiserver.APIGroupVersion { Convertor: api.Scheme, Typer: api.Scheme, - Mapper: expMeta.RESTMapper, - Codec: expMeta.Codec, - Linker: expMeta.SelfLinker, + Mapper: extensionsGroup.RESTMapper, + Codec: extensionsGroup.Codec, + Linker: extensionsGroup.SelfLinker, Storage: storage, - Version: expMeta.GroupVersion, + Version: extensionsGroup.GroupVersion, ServerVersion: latest.GroupOrDie("").GroupVersion, Admit: m.admissionControl, diff --git a/pkg/master/master_test.go b/pkg/master/master_test.go index ba393f7d9b..d1d8b380b4 100644 --- a/pkg/master/master_test.go +++ b/pkg/master/master_test.go @@ -100,8 +100,7 @@ func TestNew(t *testing.T) { assert.Equal(master.authenticator, config.Authenticator) assert.Equal(master.authorizer, config.Authorizer) assert.Equal(master.admissionControl, config.AdmissionControl) - assert.Equal(master.v1, !config.DisableV1) - assert.Equal(master.exp, config.EnableExp) + assert.Equal(master.apiGroupVersionOverrides, config.APIGroupVersionOverrides) assert.Equal(master.requestContextMapper, config.RequestContextMapper) assert.Equal(master.cacheTimeout, config.CacheTimeout) assert.Equal(master.masterCount, config.MasterCount) @@ -366,7 +365,6 @@ func TestGetNodeAddresses(t *testing.T) { func TestDiscoveryAtAPIS(t *testing.T) { master, config, assert := setUp(t) - master.exp = true // ================= preparation for master.init() ====================== portRange := util.PortRange{Base: 10, Size: 10} master.serviceNodePortRange = portRange diff --git a/test/integration/framework/master_utils.go b/test/integration/framework/master_utils.go index 27f4772a46..08a25b1586 100644 --- a/test/integration/framework/master_utils.go +++ b/test/integration/framework/master_utils.go @@ -144,7 +144,6 @@ func startMasterOrDie(masterConfig *master.Config) (*master.Master, *httptest.Se StorageDestinations: storageDestinations, StorageVersions: storageVersions, KubeletClient: client.FakeKubeletClient{}, - EnableExp: true, EnableLogsSupport: false, EnableProfiling: true, EnableSwaggerSupport: true, @@ -292,7 +291,6 @@ func RunAMaster(t *testing.T) (*master.Master, *httptest.Server) { EnableUISupport: false, APIPrefix: "/api", APIGroupPrefix: "/apis", - EnableExp: true, Authorizer: apiserver.NewAlwaysAllowAuthorizer(), AdmissionControl: admit.NewAlwaysAdmit(), StorageVersions: storageVersions,