mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-03 17:30:00 +00:00
Merge pull request #34976 from deads2k/api-30-separate-third-party
Automatic merge from submit-queue separate third party resources from master (moves + consequences) Remove the third party resource code from the main `Master` struct. I think we may be able to get this down to particular way to configure/`New` a generic API server.
This commit is contained in:
commit
6b56d7baa2
@ -23,15 +23,11 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"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/api/unversioned"
|
||||||
apiv1 "k8s.io/kubernetes/pkg/api/v1"
|
apiv1 "k8s.io/kubernetes/pkg/api/v1"
|
||||||
"k8s.io/kubernetes/pkg/apimachinery/registered"
|
|
||||||
appsapi "k8s.io/kubernetes/pkg/apis/apps/v1alpha1"
|
appsapi "k8s.io/kubernetes/pkg/apis/apps/v1alpha1"
|
||||||
authenticationv1beta1 "k8s.io/kubernetes/pkg/apis/authentication/v1beta1"
|
authenticationv1beta1 "k8s.io/kubernetes/pkg/apis/authentication/v1beta1"
|
||||||
"k8s.io/kubernetes/pkg/apis/authorization"
|
"k8s.io/kubernetes/pkg/apis/authorization"
|
||||||
@ -56,13 +52,12 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/healthz"
|
"k8s.io/kubernetes/pkg/healthz"
|
||||||
kubeletclient "k8s.io/kubernetes/pkg/kubelet/client"
|
kubeletclient "k8s.io/kubernetes/pkg/kubelet/client"
|
||||||
"k8s.io/kubernetes/pkg/master/ports"
|
"k8s.io/kubernetes/pkg/master/ports"
|
||||||
|
"k8s.io/kubernetes/pkg/master/thirdparty"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/registry/generic"
|
"k8s.io/kubernetes/pkg/registry/generic"
|
||||||
"k8s.io/kubernetes/pkg/registry/generic/registry"
|
"k8s.io/kubernetes/pkg/registry/generic/registry"
|
||||||
"k8s.io/kubernetes/pkg/routes"
|
"k8s.io/kubernetes/pkg/routes"
|
||||||
"k8s.io/kubernetes/pkg/runtime"
|
|
||||||
etcdutil "k8s.io/kubernetes/pkg/storage/etcd/util"
|
etcdutil "k8s.io/kubernetes/pkg/storage/etcd/util"
|
||||||
"k8s.io/kubernetes/pkg/storage/storagebackend"
|
|
||||||
"k8s.io/kubernetes/pkg/util/sets"
|
"k8s.io/kubernetes/pkg/util/sets"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
@ -80,10 +75,6 @@ import (
|
|||||||
policyrest "k8s.io/kubernetes/pkg/registry/policy/rest"
|
policyrest "k8s.io/kubernetes/pkg/registry/policy/rest"
|
||||||
rbacrest "k8s.io/kubernetes/pkg/registry/rbac/rest"
|
rbacrest "k8s.io/kubernetes/pkg/registry/rbac/rest"
|
||||||
storagerest "k8s.io/kubernetes/pkg/registry/storage/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 (
|
const (
|
||||||
@ -121,27 +112,12 @@ type EndpointReconcilerConfig struct {
|
|||||||
type Master struct {
|
type Master struct {
|
||||||
GenericAPIServer *genericapiserver.GenericAPIServer
|
GenericAPIServer *genericapiserver.GenericAPIServer
|
||||||
|
|
||||||
deleteCollectionWorkers int
|
thirdPartyResourceServer *thirdparty.ThirdPartyResourceServer
|
||||||
|
|
||||||
// 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
|
|
||||||
|
|
||||||
// nodeClient is used to back the tunneler
|
// nodeClient is used to back the tunneler
|
||||||
nodeClient coreclient.NodeInterface
|
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 RESTOptionsGetter func(resource unversioned.GroupResource) generic.RESTOptions
|
||||||
|
|
||||||
type RESTStorageProvider interface {
|
type RESTStorageProvider interface {
|
||||||
@ -199,9 +175,10 @@ func (c completedConfig) New() (*Master, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
m := &Master{
|
m := &Master{
|
||||||
GenericAPIServer: s,
|
GenericAPIServer: s,
|
||||||
deleteCollectionWorkers: c.DeleteCollectionWorkers,
|
nodeClient: coreclient.NewForConfigOrDie(c.GenericConfig.LoopbackClientConfig).Nodes(),
|
||||||
nodeClient: coreclient.NewForConfigOrDie(c.GenericConfig.LoopbackClientConfig).Nodes(),
|
|
||||||
|
thirdPartyResourceServer: thirdparty.NewThirdPartyResourceServer(s),
|
||||||
}
|
}
|
||||||
|
|
||||||
restOptionsFactory := restOptionsFactory{
|
restOptionsFactory := restOptionsFactory{
|
||||||
@ -241,7 +218,7 @@ func (c completedConfig) New() (*Master, error) {
|
|||||||
c.RESTStorageProviders[autoscaling.GroupName] = autoscalingrest.RESTStorageProvider{}
|
c.RESTStorageProviders[autoscaling.GroupName] = autoscalingrest.RESTStorageProvider{}
|
||||||
c.RESTStorageProviders[batch.GroupName] = batchrest.RESTStorageProvider{}
|
c.RESTStorageProviders[batch.GroupName] = batchrest.RESTStorageProvider{}
|
||||||
c.RESTStorageProviders[certificates.GroupName] = certificatesrest.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[policy.GroupName] = policyrest.RESTStorageProvider{}
|
||||||
c.RESTStorageProviders[rbac.GroupName] = &rbacrest.RESTStorageProvider{AuthorizerRBACSuperUser: c.GenericConfig.AuthorizerRBACSuperUser}
|
c.RESTStorageProviders[rbac.GroupName] = &rbacrest.RESTStorageProvider{AuthorizerRBACSuperUser: c.GenericConfig.AuthorizerRBACSuperUser}
|
||||||
c.RESTStorageProviders[storage.GroupName] = storagerest.RESTStorageProvider{}
|
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
|
// 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")) {
|
if c.GenericConfig.APIResourceConfigSource.ResourceEnabled(extensionsapiv1beta1.SchemeGroupVersion.WithResource("thirdpartyresources")) {
|
||||||
var err error
|
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 {
|
if err != nil {
|
||||||
glog.Fatalf("Error getting third party storage: %v", err)
|
glog.Fatalf("Error getting third party storage: %v", err)
|
||||||
}
|
}
|
||||||
m.thirdPartyResources = map[string]*thirdPartyEntry{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// stabilize order.
|
// stabilize order.
|
||||||
@ -380,241 +357,6 @@ func getServersToValidate(storageFactory genericapiserver.StorageFactory) map[st
|
|||||||
return serversToValidate
|
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 <api-group>/<resource-plural-name>, 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 <path>/<resource-plural-name>
|
|
||||||
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://<host>/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 {
|
type restOptionsFactory struct {
|
||||||
deleteCollectionWorkers int
|
deleteCollectionWorkers int
|
||||||
enableGarbageCollection bool
|
enableGarbageCollection bool
|
||||||
|
@ -440,12 +440,10 @@ func TestDiscoveryAtAPIS(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
thirdPartyGV := unversioned.GroupVersionForDiscovery{GroupVersion: "company.com/v1", Version: "v1"}
|
thirdPartyGV := unversioned.GroupVersionForDiscovery{GroupVersion: "company.com/v1", Version: "v1"}
|
||||||
master.addThirdPartyResourceStorage("/apis/company.com/v1", "foos", nil,
|
master.thirdPartyResourceServer.InstallThirdPartyResource(&extensions.ThirdPartyResource{
|
||||||
unversioned.APIGroup{
|
ObjectMeta: api.ObjectMeta{Name: "foo.company.com"},
|
||||||
Name: "company.com",
|
Versions: []extensions.APIVersion{{Name: "v1"}},
|
||||||
Versions: []unversioned.GroupVersionForDiscovery{thirdPartyGV},
|
})
|
||||||
PreferredVersion: thirdPartyGV,
|
|
||||||
})
|
|
||||||
|
|
||||||
resp, err = http.Get(server.URL + "/apis")
|
resp, err = http.Get(server.URL + "/apis")
|
||||||
if !assert.NoError(err) {
|
if !assert.NoError(err) {
|
||||||
|
319
pkg/master/thirdparty/thirdparty.go
vendored
Normal file
319
pkg/master/thirdparty/thirdparty.go
vendored
Normal file
@ -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 <api-group>/<resource-plural-name>, 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 <path>/<resource-plural-name>
|
||||||
|
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://<host>/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)},
|
||||||
|
}
|
||||||
|
}
|
@ -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{}
|
|
Loading…
Reference in New Issue
Block a user