diff --git a/pkg/master/master.go b/pkg/master/master.go index 57d7634a63c..250b488dd2a 100644 --- a/pkg/master/master.go +++ b/pkg/master/master.go @@ -23,15 +23,11 @@ import ( "reflect" "strconv" "strings" - "sync" "time" "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/meta" - "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" appsapi "k8s.io/kubernetes/pkg/apis/apps/v1alpha1" authenticationv1beta1 "k8s.io/kubernetes/pkg/apis/authentication/v1beta1" "k8s.io/kubernetes/pkg/apis/authorization" @@ -56,13 +52,12 @@ import ( "k8s.io/kubernetes/pkg/healthz" kubeletclient "k8s.io/kubernetes/pkg/kubelet/client" "k8s.io/kubernetes/pkg/master/ports" + "k8s.io/kubernetes/pkg/master/thirdparty" "k8s.io/kubernetes/pkg/registry/generic" "k8s.io/kubernetes/pkg/registry/generic/registry" "k8s.io/kubernetes/pkg/routes" - "k8s.io/kubernetes/pkg/runtime" etcdutil "k8s.io/kubernetes/pkg/storage/etcd/util" - "k8s.io/kubernetes/pkg/storage/storagebackend" "k8s.io/kubernetes/pkg/util/sets" "github.com/golang/glog" @@ -80,10 +75,6 @@ import ( policyrest "k8s.io/kubernetes/pkg/registry/policy/rest" rbacrest "k8s.io/kubernetes/pkg/registry/rbac/rest" storagerest "k8s.io/kubernetes/pkg/registry/storage/rest" - - // direct etcd registry dependencies - "k8s.io/kubernetes/pkg/registry/extensions/thirdpartyresourcedata" - thirdpartyresourcedataetcd "k8s.io/kubernetes/pkg/registry/extensions/thirdpartyresourcedata/etcd" ) const ( @@ -121,27 +112,12 @@ type EndpointReconcilerConfig struct { type Master struct { GenericAPIServer *genericapiserver.GenericAPIServer - deleteCollectionWorkers int - - // storage for third party objects - thirdPartyStorageConfig *storagebackend.Config - // map from api path to a tuple of (storage for the objects, APIGroup) - thirdPartyResources map[string]*thirdPartyEntry - // protects the map - thirdPartyResourcesLock sync.RWMutex + thirdPartyResourceServer *thirdparty.ThirdPartyResourceServer // nodeClient is used to back the tunneler nodeClient coreclient.NodeInterface } -// thirdPartyEntry combines objects storage and API group into one struct -// for easy lookup. -type thirdPartyEntry struct { - // Map from plural resource name to entry - storage map[string]*thirdpartyresourcedataetcd.REST - group unversioned.APIGroup -} - type RESTOptionsGetter func(resource unversioned.GroupResource) generic.RESTOptions type RESTStorageProvider interface { @@ -199,9 +175,10 @@ func (c completedConfig) New() (*Master, error) { } m := &Master{ - GenericAPIServer: s, - deleteCollectionWorkers: c.DeleteCollectionWorkers, - nodeClient: coreclient.NewForConfigOrDie(c.GenericConfig.LoopbackClientConfig).Nodes(), + GenericAPIServer: s, + nodeClient: coreclient.NewForConfigOrDie(c.GenericConfig.LoopbackClientConfig).Nodes(), + + thirdPartyResourceServer: thirdparty.NewThirdPartyResourceServer(s), } restOptionsFactory := restOptionsFactory{ @@ -241,7 +218,7 @@ func (c completedConfig) New() (*Master, error) { c.RESTStorageProviders[autoscaling.GroupName] = autoscalingrest.RESTStorageProvider{} c.RESTStorageProviders[batch.GroupName] = batchrest.RESTStorageProvider{} c.RESTStorageProviders[certificates.GroupName] = certificatesrest.RESTStorageProvider{} - c.RESTStorageProviders[extensions.GroupName] = extensionsrest.RESTStorageProvider{ResourceInterface: m} + c.RESTStorageProviders[extensions.GroupName] = extensionsrest.RESTStorageProvider{ResourceInterface: m.thirdPartyResourceServer} c.RESTStorageProviders[policy.GroupName] = policyrest.RESTStorageProvider{} c.RESTStorageProviders[rbac.GroupName] = &rbacrest.RESTStorageProvider{AuthorizerRBACSuperUser: c.GenericConfig.AuthorizerRBACSuperUser} c.RESTStorageProviders[storage.GroupName] = storagerest.RESTStorageProvider{} @@ -300,11 +277,11 @@ func (m *Master) InstallAPIs(c *Config, restOptionsGetter genericapiserver.RESTO // TODO seems like this bit ought to be unconditional and the REST API is controlled by the config if c.GenericConfig.APIResourceConfigSource.ResourceEnabled(extensionsapiv1beta1.SchemeGroupVersion.WithResource("thirdpartyresources")) { var err error - m.thirdPartyStorageConfig, err = c.StorageFactory.NewConfig(extensions.Resource("thirdpartyresources")) + // TODO figure out why this isn't a loopback client + m.thirdPartyResourceServer.ThirdPartyStorageConfig, err = c.StorageFactory.NewConfig(extensions.Resource("thirdpartyresources")) if err != nil { glog.Fatalf("Error getting third party storage: %v", err) } - m.thirdPartyResources = map[string]*thirdPartyEntry{} } // stabilize order. @@ -380,241 +357,6 @@ func getServersToValidate(storageFactory genericapiserver.StorageFactory) map[st return serversToValidate } -// HasThirdPartyResource returns true if a particular third party resource currently installed. -func (m *Master) HasThirdPartyResource(rsrc *extensions.ThirdPartyResource) (bool, error) { - kind, group, err := thirdpartyresourcedata.ExtractApiGroupAndKind(rsrc) - if err != nil { - return false, err - } - path := extensionsrest.MakeThirdPartyPath(group) - m.thirdPartyResourcesLock.Lock() - defer m.thirdPartyResourcesLock.Unlock() - entry := m.thirdPartyResources[path] - if entry == nil { - return false, nil - } - plural, _ := meta.KindToResource(unversioned.GroupVersionKind{ - Group: group, - Version: rsrc.Versions[0].Name, - Kind: kind, - }) - _, found := entry.storage[plural.Resource] - return found, nil -} - -func (m *Master) removeThirdPartyStorage(path, resource string) error { - m.thirdPartyResourcesLock.Lock() - defer m.thirdPartyResourcesLock.Unlock() - entry, found := m.thirdPartyResources[path] - if !found { - return nil - } - storage, found := entry.storage[resource] - if !found { - return nil - } - if err := m.removeAllThirdPartyResources(storage); err != nil { - return err - } - delete(entry.storage, resource) - if len(entry.storage) == 0 { - delete(m.thirdPartyResources, path) - m.GenericAPIServer.RemoveAPIGroupForDiscovery(extensionsrest.GetThirdPartyGroupName(path)) - } else { - m.thirdPartyResources[path] = entry - } - return nil -} - -// RemoveThirdPartyResource removes all resources matching `path`. Also deletes any stored data -func (m *Master) RemoveThirdPartyResource(path string) error { - ix := strings.LastIndex(path, "/") - if ix == -1 { - return fmt.Errorf("expected /, saw: %s", path) - } - resource := path[ix+1:] - path = path[0:ix] - - if err := m.removeThirdPartyStorage(path, resource); err != nil { - return err - } - - services := m.GenericAPIServer.HandlerContainer.RegisteredWebServices() - for ix := range services { - root := services[ix].RootPath() - if root == path || strings.HasPrefix(root, path+"/") { - m.GenericAPIServer.HandlerContainer.Remove(services[ix]) - } - } - return nil -} - -func (m *Master) removeAllThirdPartyResources(registry *thirdpartyresourcedataetcd.REST) error { - ctx := api.NewDefaultContext() - existingData, err := registry.List(ctx, nil) - if err != nil { - return err - } - list, ok := existingData.(*extensions.ThirdPartyResourceDataList) - if !ok { - return fmt.Errorf("expected a *ThirdPartyResourceDataList, got %#v", list) - } - for ix := range list.Items { - item := &list.Items[ix] - if _, err := registry.Delete(ctx, item.Name, nil); err != nil { - return err - } - } - return nil -} - -// ListThirdPartyResources lists all currently installed third party resources -// The format is / -func (m *Master) ListThirdPartyResources() []string { - m.thirdPartyResourcesLock.RLock() - defer m.thirdPartyResourcesLock.RUnlock() - result := []string{} - for key := range m.thirdPartyResources { - for rsrc := range m.thirdPartyResources[key].storage { - result = append(result, key+"/"+rsrc) - } - } - return result -} - -func (m *Master) getExistingThirdPartyResources(path string) []unversioned.APIResource { - result := []unversioned.APIResource{} - m.thirdPartyResourcesLock.Lock() - defer m.thirdPartyResourcesLock.Unlock() - entry := m.thirdPartyResources[path] - if entry != nil { - for key, obj := range entry.storage { - result = append(result, unversioned.APIResource{ - Name: key, - Namespaced: true, - Kind: obj.Kind(), - }) - } - } - return result -} - -func (m *Master) hasThirdPartyGroupStorage(path string) bool { - m.thirdPartyResourcesLock.Lock() - defer m.thirdPartyResourcesLock.Unlock() - _, found := m.thirdPartyResources[path] - return found -} - -func (m *Master) addThirdPartyResourceStorage(path, resource string, storage *thirdpartyresourcedataetcd.REST, apiGroup unversioned.APIGroup) { - m.thirdPartyResourcesLock.Lock() - defer m.thirdPartyResourcesLock.Unlock() - entry, found := m.thirdPartyResources[path] - if entry == nil { - entry = &thirdPartyEntry{ - group: apiGroup, - storage: map[string]*thirdpartyresourcedataetcd.REST{}, - } - m.thirdPartyResources[path] = entry - } - entry.storage[resource] = storage - if !found { - m.GenericAPIServer.AddAPIGroupForDiscovery(apiGroup) - } -} - -// InstallThirdPartyResource installs a third party resource specified by 'rsrc'. When a resource is -// installed a corresponding RESTful resource is added as a valid path in the web service provided by -// the master. -// -// For example, if you install a resource ThirdPartyResource{ Name: "foo.company.com", Versions: {"v1"} } -// then the following RESTful resource is created on the server: -// http:///apis/company.com/v1/foos/... -func (m *Master) InstallThirdPartyResource(rsrc *extensions.ThirdPartyResource) error { - kind, group, err := thirdpartyresourcedata.ExtractApiGroupAndKind(rsrc) - if err != nil { - return err - } - plural, _ := meta.KindToResource(unversioned.GroupVersionKind{ - Group: group, - Version: rsrc.Versions[0].Name, - Kind: kind, - }) - path := extensionsrest.MakeThirdPartyPath(group) - - groupVersion := unversioned.GroupVersionForDiscovery{ - GroupVersion: group + "/" + rsrc.Versions[0].Name, - Version: rsrc.Versions[0].Name, - } - apiGroup := unversioned.APIGroup{ - Name: group, - Versions: []unversioned.GroupVersionForDiscovery{groupVersion}, - PreferredVersion: groupVersion, - } - - thirdparty := m.thirdpartyapi(group, kind, rsrc.Versions[0].Name, plural.Resource) - - // If storage exists, this group has already been added, just update - // the group with the new API - if m.hasThirdPartyGroupStorage(path) { - m.addThirdPartyResourceStorage(path, plural.Resource, thirdparty.Storage[plural.Resource].(*thirdpartyresourcedataetcd.REST), apiGroup) - return thirdparty.UpdateREST(m.GenericAPIServer.HandlerContainer.Container) - } - - if err := thirdparty.InstallREST(m.GenericAPIServer.HandlerContainer.Container); err != nil { - glog.Errorf("Unable to setup thirdparty api: %v", err) - } - m.GenericAPIServer.HandlerContainer.Add(apiserver.NewGroupWebService(api.Codecs, path, apiGroup)) - - m.addThirdPartyResourceStorage(path, plural.Resource, thirdparty.Storage[plural.Resource].(*thirdpartyresourcedataetcd.REST), apiGroup) - return nil -} - -func (m *Master) thirdpartyapi(group, kind, version, pluralResource string) *apiserver.APIGroupVersion { - resourceStorage := thirdpartyresourcedataetcd.NewREST( - generic.RESTOptions{ - StorageConfig: m.thirdPartyStorageConfig, - Decorator: generic.UndecoratedStorage, - DeleteCollectionWorkers: m.deleteCollectionWorkers, - }, - group, - kind, - ) - - storage := map[string]rest.Storage{ - pluralResource: resourceStorage, - } - - optionsExternalVersion := registered.GroupOrDie(api.GroupName).GroupVersion - internalVersion := unversioned.GroupVersion{Group: group, Version: runtime.APIVersionInternal} - externalVersion := unversioned.GroupVersion{Group: group, Version: version} - - apiRoot := extensionsrest.MakeThirdPartyPath("") - return &apiserver.APIGroupVersion{ - Root: apiRoot, - GroupVersion: externalVersion, - - Creater: thirdpartyresourcedata.NewObjectCreator(group, version, api.Scheme), - Convertor: api.Scheme, - Copier: api.Scheme, - Typer: api.Scheme, - - Mapper: thirdpartyresourcedata.NewMapper(registered.GroupOrDie(extensions.GroupName).RESTMapper, kind, version, group), - Linker: registered.GroupOrDie(extensions.GroupName).SelfLinker, - Storage: storage, - OptionsExternalVersion: &optionsExternalVersion, - - Serializer: thirdpartyresourcedata.NewNegotiatedSerializer(api.Codecs, kind, externalVersion, internalVersion), - ParameterCodec: thirdpartyresourcedata.NewThirdPartyParameterCodec(api.ParameterCodec), - - Context: m.GenericAPIServer.RequestContextMapper(), - - MinRequestTimeout: m.GenericAPIServer.MinRequestTimeout(), - - ResourceLister: dynamicLister{m, extensionsrest.MakeThirdPartyPath(group)}, - } -} - type restOptionsFactory struct { deleteCollectionWorkers int enableGarbageCollection bool diff --git a/pkg/master/master_test.go b/pkg/master/master_test.go index 188dab6d265..559dd6c2bd3 100644 --- a/pkg/master/master_test.go +++ b/pkg/master/master_test.go @@ -440,12 +440,10 @@ func TestDiscoveryAtAPIS(t *testing.T) { } thirdPartyGV := unversioned.GroupVersionForDiscovery{GroupVersion: "company.com/v1", Version: "v1"} - master.addThirdPartyResourceStorage("/apis/company.com/v1", "foos", nil, - unversioned.APIGroup{ - Name: "company.com", - Versions: []unversioned.GroupVersionForDiscovery{thirdPartyGV}, - PreferredVersion: thirdPartyGV, - }) + master.thirdPartyResourceServer.InstallThirdPartyResource(&extensions.ThirdPartyResource{ + ObjectMeta: api.ObjectMeta{Name: "foo.company.com"}, + Versions: []extensions.APIVersion{{Name: "v1"}}, + }) resp, err = http.Get(server.URL + "/apis") if !assert.NoError(err) { diff --git a/pkg/master/thirdparty/thirdparty.go b/pkg/master/thirdparty/thirdparty.go new file mode 100644 index 00000000000..49edf4c50ac --- /dev/null +++ b/pkg/master/thirdparty/thirdparty.go @@ -0,0 +1,319 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package thirdparty + +import ( + "fmt" + "strings" + "sync" + + "github.com/golang/glog" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/meta" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apimachinery/registered" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/apiserver" + "k8s.io/kubernetes/pkg/genericapiserver" + extensionsrest "k8s.io/kubernetes/pkg/registry/extensions/rest" + "k8s.io/kubernetes/pkg/registry/extensions/thirdpartyresourcedata" + thirdpartyresourcedataetcd "k8s.io/kubernetes/pkg/registry/extensions/thirdpartyresourcedata/etcd" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/storage/storagebackend" +) + +// dynamicLister is used to list resources for dynamic third party +// apis. It implements the apiserver.APIResourceLister interface +type dynamicLister struct { + m *ThirdPartyResourceServer + path string +} + +func (d dynamicLister) ListAPIResources() []unversioned.APIResource { + return d.m.getExistingThirdPartyResources(d.path) +} + +var _ apiserver.APIResourceLister = &dynamicLister{} + +type ThirdPartyResourceServer struct { + genericAPIServer *genericapiserver.GenericAPIServer + + deleteCollectionWorkers int + + // storage for third party objects + ThirdPartyStorageConfig *storagebackend.Config + // map from api path to a tuple of (storage for the objects, APIGroup) + thirdPartyResources map[string]*thirdPartyEntry + // protects the map + thirdPartyResourcesLock sync.RWMutex + + // Useful for reliable testing. Shouldn't be used otherwise. + disableThirdPartyControllerForTesting bool +} + +func NewThirdPartyResourceServer(genericAPIServer *genericapiserver.GenericAPIServer) *ThirdPartyResourceServer { + return &ThirdPartyResourceServer{ + genericAPIServer: genericAPIServer, + thirdPartyResources: map[string]*thirdPartyEntry{}, + } +} + +// thirdPartyEntry combines objects storage and API group into one struct +// for easy lookup. +type thirdPartyEntry struct { + // Map from plural resource name to entry + storage map[string]*thirdpartyresourcedataetcd.REST + group unversioned.APIGroup +} + +// HasThirdPartyResource returns true if a particular third party resource currently installed. +func (m *ThirdPartyResourceServer) HasThirdPartyResource(rsrc *extensions.ThirdPartyResource) (bool, error) { + kind, group, err := thirdpartyresourcedata.ExtractApiGroupAndKind(rsrc) + if err != nil { + return false, err + } + path := extensionsrest.MakeThirdPartyPath(group) + m.thirdPartyResourcesLock.Lock() + defer m.thirdPartyResourcesLock.Unlock() + entry := m.thirdPartyResources[path] + if entry == nil { + return false, nil + } + plural, _ := meta.KindToResource(unversioned.GroupVersionKind{ + Group: group, + Version: rsrc.Versions[0].Name, + Kind: kind, + }) + _, found := entry.storage[plural.Resource] + return found, nil +} + +func (m *ThirdPartyResourceServer) removeThirdPartyStorage(path, resource string) error { + m.thirdPartyResourcesLock.Lock() + defer m.thirdPartyResourcesLock.Unlock() + entry, found := m.thirdPartyResources[path] + if !found { + return nil + } + storage, found := entry.storage[resource] + if !found { + return nil + } + if err := m.removeAllThirdPartyResources(storage); err != nil { + return err + } + delete(entry.storage, resource) + if len(entry.storage) == 0 { + delete(m.thirdPartyResources, path) + m.genericAPIServer.RemoveAPIGroupForDiscovery(extensionsrest.GetThirdPartyGroupName(path)) + } else { + m.thirdPartyResources[path] = entry + } + return nil +} + +// RemoveThirdPartyResource removes all resources matching `path`. Also deletes any stored data +func (m *ThirdPartyResourceServer) RemoveThirdPartyResource(path string) error { + ix := strings.LastIndex(path, "/") + if ix == -1 { + return fmt.Errorf("expected /, saw: %s", path) + } + resource := path[ix+1:] + path = path[0:ix] + + if err := m.removeThirdPartyStorage(path, resource); err != nil { + return err + } + + services := m.genericAPIServer.HandlerContainer.RegisteredWebServices() + for ix := range services { + root := services[ix].RootPath() + if root == path || strings.HasPrefix(root, path+"/") { + m.genericAPIServer.HandlerContainer.Remove(services[ix]) + } + } + return nil +} + +func (m *ThirdPartyResourceServer) removeAllThirdPartyResources(registry *thirdpartyresourcedataetcd.REST) error { + ctx := api.NewDefaultContext() + existingData, err := registry.List(ctx, nil) + if err != nil { + return err + } + list, ok := existingData.(*extensions.ThirdPartyResourceDataList) + if !ok { + return fmt.Errorf("expected a *ThirdPartyResourceDataList, got %#v", list) + } + for ix := range list.Items { + item := &list.Items[ix] + if _, err := registry.Delete(ctx, item.Name, nil); err != nil { + return err + } + } + return nil +} + +// ListThirdPartyResources lists all currently installed third party resources +// The format is / +func (m *ThirdPartyResourceServer) ListThirdPartyResources() []string { + m.thirdPartyResourcesLock.RLock() + defer m.thirdPartyResourcesLock.RUnlock() + result := []string{} + for key := range m.thirdPartyResources { + for rsrc := range m.thirdPartyResources[key].storage { + result = append(result, key+"/"+rsrc) + } + } + return result +} + +func (m *ThirdPartyResourceServer) getExistingThirdPartyResources(path string) []unversioned.APIResource { + result := []unversioned.APIResource{} + m.thirdPartyResourcesLock.Lock() + defer m.thirdPartyResourcesLock.Unlock() + entry := m.thirdPartyResources[path] + if entry != nil { + for key, obj := range entry.storage { + result = append(result, unversioned.APIResource{ + Name: key, + Namespaced: true, + Kind: obj.Kind(), + }) + } + } + return result +} + +func (m *ThirdPartyResourceServer) hasThirdPartyGroupStorage(path string) bool { + m.thirdPartyResourcesLock.Lock() + defer m.thirdPartyResourcesLock.Unlock() + _, found := m.thirdPartyResources[path] + return found +} + +func (m *ThirdPartyResourceServer) addThirdPartyResourceStorage(path, resource string, storage *thirdpartyresourcedataetcd.REST, apiGroup unversioned.APIGroup) { + m.thirdPartyResourcesLock.Lock() + defer m.thirdPartyResourcesLock.Unlock() + entry, found := m.thirdPartyResources[path] + if entry == nil { + entry = &thirdPartyEntry{ + group: apiGroup, + storage: map[string]*thirdpartyresourcedataetcd.REST{}, + } + m.thirdPartyResources[path] = entry + } + entry.storage[resource] = storage + if !found { + m.genericAPIServer.AddAPIGroupForDiscovery(apiGroup) + } +} + +// InstallThirdPartyResource installs a third party resource specified by 'rsrc'. When a resource is +// installed a corresponding RESTful resource is added as a valid path in the web service provided by +// the master. +// +// For example, if you install a resource ThirdPartyResource{ Name: "foo.company.com", Versions: {"v1"} } +// then the following RESTful resource is created on the server: +// http:///apis/company.com/v1/foos/... +func (m *ThirdPartyResourceServer) InstallThirdPartyResource(rsrc *extensions.ThirdPartyResource) error { + kind, group, err := thirdpartyresourcedata.ExtractApiGroupAndKind(rsrc) + if err != nil { + return err + } + plural, _ := meta.KindToResource(unversioned.GroupVersionKind{ + Group: group, + Version: rsrc.Versions[0].Name, + Kind: kind, + }) + path := extensionsrest.MakeThirdPartyPath(group) + + groupVersion := unversioned.GroupVersionForDiscovery{ + GroupVersion: group + "/" + rsrc.Versions[0].Name, + Version: rsrc.Versions[0].Name, + } + apiGroup := unversioned.APIGroup{ + Name: group, + Versions: []unversioned.GroupVersionForDiscovery{groupVersion}, + PreferredVersion: groupVersion, + } + + thirdparty := m.thirdpartyapi(group, kind, rsrc.Versions[0].Name, plural.Resource) + + // If storage exists, this group has already been added, just update + // the group with the new API + if m.hasThirdPartyGroupStorage(path) { + m.addThirdPartyResourceStorage(path, plural.Resource, thirdparty.Storage[plural.Resource].(*thirdpartyresourcedataetcd.REST), apiGroup) + return thirdparty.UpdateREST(m.genericAPIServer.HandlerContainer.Container) + } + + if err := thirdparty.InstallREST(m.genericAPIServer.HandlerContainer.Container); err != nil { + glog.Errorf("Unable to setup thirdparty api: %v", err) + } + m.genericAPIServer.HandlerContainer.Add(apiserver.NewGroupWebService(api.Codecs, path, apiGroup)) + + m.addThirdPartyResourceStorage(path, plural.Resource, thirdparty.Storage[plural.Resource].(*thirdpartyresourcedataetcd.REST), apiGroup) + return nil +} + +func (m *ThirdPartyResourceServer) thirdpartyapi(group, kind, version, pluralResource string) *apiserver.APIGroupVersion { + resourceStorage := thirdpartyresourcedataetcd.NewREST( + generic.RESTOptions{ + StorageConfig: m.ThirdPartyStorageConfig, + Decorator: generic.UndecoratedStorage, + DeleteCollectionWorkers: m.deleteCollectionWorkers, + }, + group, + kind, + ) + + storage := map[string]rest.Storage{ + pluralResource: resourceStorage, + } + + optionsExternalVersion := registered.GroupOrDie(api.GroupName).GroupVersion + internalVersion := unversioned.GroupVersion{Group: group, Version: runtime.APIVersionInternal} + externalVersion := unversioned.GroupVersion{Group: group, Version: version} + + apiRoot := extensionsrest.MakeThirdPartyPath("") + return &apiserver.APIGroupVersion{ + Root: apiRoot, + GroupVersion: externalVersion, + + Creater: thirdpartyresourcedata.NewObjectCreator(group, version, api.Scheme), + Convertor: api.Scheme, + Copier: api.Scheme, + Typer: api.Scheme, + + Mapper: thirdpartyresourcedata.NewMapper(registered.GroupOrDie(extensions.GroupName).RESTMapper, kind, version, group), + Linker: registered.GroupOrDie(extensions.GroupName).SelfLinker, + Storage: storage, + OptionsExternalVersion: &optionsExternalVersion, + + Serializer: thirdpartyresourcedata.NewNegotiatedSerializer(api.Codecs, kind, externalVersion, internalVersion), + ParameterCodec: thirdpartyresourcedata.NewThirdPartyParameterCodec(api.ParameterCodec), + + Context: m.genericAPIServer.RequestContextMapper(), + + MinRequestTimeout: m.genericAPIServer.MinRequestTimeout(), + + ResourceLister: dynamicLister{m, extensionsrest.MakeThirdPartyPath(group)}, + } +} diff --git a/pkg/master/thirdparty_controller.go b/pkg/master/thirdparty_controller.go deleted file mode 100644 index 1ebd83293fd..00000000000 --- a/pkg/master/thirdparty_controller.go +++ /dev/null @@ -1,35 +0,0 @@ -/* -Copyright 2014 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package master - -import ( - "k8s.io/kubernetes/pkg/api/unversioned" - "k8s.io/kubernetes/pkg/apiserver" -) - -// dynamicLister is used to list resources for dynamic third party -// apis. It implements the apiserver.APIResourceLister interface -type dynamicLister struct { - m *Master - path string -} - -func (d dynamicLister) ListAPIResources() []unversioned.APIResource { - return d.m.getExistingThirdPartyResources(d.path) -} - -var _ apiserver.APIResourceLister = &dynamicLister{}