mirror of https://github.com/k3s-io/k3s
separate discovery from the apiserver
parent
8f6df26755
commit
e099f5eee6
|
@ -85,6 +85,7 @@ go_library(
|
||||||
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||||
|
"//vendor/k8s.io/apiserver/pkg/endpoints/discovery:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/server:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/server:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/server/healthz:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/server/healthz:go_default_library",
|
||||||
|
|
|
@ -26,6 +26,7 @@ import (
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/discovery"
|
||||||
"k8s.io/apiserver/pkg/registry/generic"
|
"k8s.io/apiserver/pkg/registry/generic"
|
||||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||||
"k8s.io/apiserver/pkg/server/healthz"
|
"k8s.io/apiserver/pkg/server/healthz"
|
||||||
|
@ -163,9 +164,9 @@ func (c *Config) Complete() completedConfig {
|
||||||
c.APIServerServiceIP = apiServerServiceIP
|
c.APIServerServiceIP = apiServerServiceIP
|
||||||
}
|
}
|
||||||
|
|
||||||
discoveryAddresses := genericapiserver.DefaultDiscoveryAddresses{DefaultAddress: c.GenericConfig.ExternalAddress}
|
discoveryAddresses := discovery.DefaultAddresses{DefaultAddress: c.GenericConfig.ExternalAddress}
|
||||||
discoveryAddresses.DiscoveryCIDRRules = append(discoveryAddresses.DiscoveryCIDRRules,
|
discoveryAddresses.CIDRRules = append(discoveryAddresses.CIDRRules,
|
||||||
genericapiserver.DiscoveryCIDRRule{IPRange: c.ServiceIPRange, Address: net.JoinHostPort(c.APIServerServiceIP.String(), strconv.Itoa(c.APIServerServicePort))})
|
discovery.CIDRRule{IPRange: c.ServiceIPRange, Address: net.JoinHostPort(c.APIServerServiceIP.String(), strconv.Itoa(c.APIServerServicePort))})
|
||||||
c.GenericConfig.DiscoveryAddresses = discoveryAddresses
|
c.GenericConfig.DiscoveryAddresses = discoveryAddresses
|
||||||
|
|
||||||
if c.ServiceNodePortRange.Size == 0 {
|
if c.ServiceNodePortRange.Size == 0 {
|
||||||
|
@ -249,7 +250,7 @@ func (c completedConfig) New() (*Master, error) {
|
||||||
autoscalingrest.RESTStorageProvider{},
|
autoscalingrest.RESTStorageProvider{},
|
||||||
batchrest.RESTStorageProvider{},
|
batchrest.RESTStorageProvider{},
|
||||||
certificatesrest.RESTStorageProvider{},
|
certificatesrest.RESTStorageProvider{},
|
||||||
extensionsrest.RESTStorageProvider{ResourceInterface: thirdparty.NewThirdPartyResourceServer(s, c.StorageFactory)},
|
extensionsrest.RESTStorageProvider{ResourceInterface: thirdparty.NewThirdPartyResourceServer(s, s.DiscoveryGroupManager, c.StorageFactory)},
|
||||||
policyrest.RESTStorageProvider{},
|
policyrest.RESTStorageProvider{},
|
||||||
rbacrest.RESTStorageProvider{Authorizer: c.GenericConfig.Authorizer},
|
rbacrest.RESTStorageProvider{Authorizer: c.GenericConfig.Authorizer},
|
||||||
settingsrest.RESTStorageProvider{},
|
settingsrest.RESTStorageProvider{},
|
||||||
|
|
|
@ -33,7 +33,7 @@ go_library(
|
||||||
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/endpoints:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/endpoints:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/endpoints/handlers:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/endpoints/discovery:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library",
|
||||||
|
|
|
@ -28,7 +28,7 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
genericapi "k8s.io/apiserver/pkg/endpoints"
|
genericapi "k8s.io/apiserver/pkg/endpoints"
|
||||||
genericapihandlers "k8s.io/apiserver/pkg/endpoints/handlers"
|
"k8s.io/apiserver/pkg/endpoints/discovery"
|
||||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||||
"k8s.io/apiserver/pkg/registry/generic"
|
"k8s.io/apiserver/pkg/registry/generic"
|
||||||
"k8s.io/apiserver/pkg/registry/rest"
|
"k8s.io/apiserver/pkg/registry/rest"
|
||||||
|
@ -53,11 +53,13 @@ func (d dynamicLister) ListAPIResources() []metav1.APIResource {
|
||||||
return d.m.getExistingThirdPartyResources(d.path)
|
return d.m.getExistingThirdPartyResources(d.path)
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ genericapihandlers.APIResourceLister = &dynamicLister{}
|
var _ discovery.APIResourceLister = &dynamicLister{}
|
||||||
|
|
||||||
type ThirdPartyResourceServer struct {
|
type ThirdPartyResourceServer struct {
|
||||||
genericAPIServer *genericapiserver.GenericAPIServer
|
genericAPIServer *genericapiserver.GenericAPIServer
|
||||||
|
|
||||||
|
availableGroupManager discovery.GroupManager
|
||||||
|
|
||||||
deleteCollectionWorkers int
|
deleteCollectionWorkers int
|
||||||
|
|
||||||
// storage for third party objects
|
// storage for third party objects
|
||||||
|
@ -71,10 +73,11 @@ type ThirdPartyResourceServer struct {
|
||||||
disableThirdPartyControllerForTesting bool
|
disableThirdPartyControllerForTesting bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewThirdPartyResourceServer(genericAPIServer *genericapiserver.GenericAPIServer, storageFactory serverstorgage.StorageFactory) *ThirdPartyResourceServer {
|
func NewThirdPartyResourceServer(genericAPIServer *genericapiserver.GenericAPIServer, availableGroupManager discovery.GroupManager, storageFactory serverstorgage.StorageFactory) *ThirdPartyResourceServer {
|
||||||
ret := &ThirdPartyResourceServer{
|
ret := &ThirdPartyResourceServer{
|
||||||
genericAPIServer: genericAPIServer,
|
genericAPIServer: genericAPIServer,
|
||||||
thirdPartyResources: map[string]*thirdPartyEntry{},
|
thirdPartyResources: map[string]*thirdPartyEntry{},
|
||||||
|
availableGroupManager: availableGroupManager,
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
@ -133,7 +136,7 @@ func (m *ThirdPartyResourceServer) removeThirdPartyStorage(path, resource string
|
||||||
delete(entry.storage, resource)
|
delete(entry.storage, resource)
|
||||||
if len(entry.storage) == 0 {
|
if len(entry.storage) == 0 {
|
||||||
delete(m.thirdPartyResources, path)
|
delete(m.thirdPartyResources, path)
|
||||||
m.genericAPIServer.RemoveAPIGroupForDiscovery(extensionsrest.GetThirdPartyGroupName(path))
|
m.availableGroupManager.RemoveGroup(extensionsrest.GetThirdPartyGroupName(path))
|
||||||
} else {
|
} else {
|
||||||
m.thirdPartyResources[path] = entry
|
m.thirdPartyResources[path] = entry
|
||||||
}
|
}
|
||||||
|
@ -236,7 +239,7 @@ func (m *ThirdPartyResourceServer) addThirdPartyResourceStorage(path, resource s
|
||||||
}
|
}
|
||||||
entry.storage[resource] = storage
|
entry.storage[resource] = storage
|
||||||
if !found {
|
if !found {
|
||||||
m.genericAPIServer.AddAPIGroupForDiscovery(apiGroup)
|
m.availableGroupManager.AddGroup(apiGroup)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -284,7 +287,7 @@ func (m *ThirdPartyResourceServer) InstallThirdPartyResource(rsrc *extensions.Th
|
||||||
if err := thirdparty.InstallREST(m.genericAPIServer.HandlerContainer.Container); err != nil {
|
if err := thirdparty.InstallREST(m.genericAPIServer.HandlerContainer.Container); err != nil {
|
||||||
glog.Errorf("Unable to setup thirdparty api: %v", err)
|
glog.Errorf("Unable to setup thirdparty api: %v", err)
|
||||||
}
|
}
|
||||||
m.genericAPIServer.HandlerContainer.Add(genericapi.NewGroupWebService(api.Codecs, path, apiGroup))
|
m.genericAPIServer.HandlerContainer.Add(discovery.NewAPIGroupHandler(api.Codecs, apiGroup).WebService())
|
||||||
|
|
||||||
m.addThirdPartyResourceStorage(path, plural.Resource, thirdparty.Storage[plural.Resource].(*thirdpartyresourcedatastore.REST), apiGroup)
|
m.addThirdPartyResourceStorage(path, plural.Resource, thirdparty.Storage[plural.Resource].(*thirdpartyresourcedatastore.REST), apiGroup)
|
||||||
api.Registry.AddThirdPartyAPIGroupVersions(schema.GroupVersion{Group: group, Version: rsrc.Versions[0].Name})
|
api.Registry.AddThirdPartyAPIGroupVersions(schema.GroupVersion{Group: group, Version: rsrc.Versions[0].Name})
|
||||||
|
|
|
@ -55,7 +55,6 @@ go_library(
|
||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
srcs = [
|
srcs = [
|
||||||
"apiserver.go",
|
"apiserver.go",
|
||||||
"discovery.go",
|
|
||||||
"doc.go",
|
"doc.go",
|
||||||
"groupversion.go",
|
"groupversion.go",
|
||||||
"installer.go",
|
"installer.go",
|
||||||
|
@ -72,9 +71,9 @@ go_library(
|
||||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||||
|
"//vendor/k8s.io/apiserver/pkg/endpoints/discovery:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/endpoints/handlers:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/endpoints/handlers:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/endpoints/handlers/negotiation:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/endpoints/handlers/negotiation:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters:go_default_library",
|
|
||||||
"//vendor/k8s.io/apiserver/pkg/endpoints/metrics:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/endpoints/metrics:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library",
|
||||||
|
|
|
@ -1,172 +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 endpoints
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/emicklei/go-restful"
|
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
"k8s.io/apiserver/pkg/endpoints/handlers"
|
|
||||||
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
|
||||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AddApiWebService adds a service to return the supported api versions at the legacy /api.
|
|
||||||
func AddApiWebService(s runtime.NegotiatedSerializer, container *restful.Container, apiPrefix string, getAPIVersionsFunc func(req *restful.Request) *metav1.APIVersions) {
|
|
||||||
// TODO: InstallREST should register each version automatically
|
|
||||||
|
|
||||||
// Because in release 1.1, /api returns response with empty APIVersion, we
|
|
||||||
// use StripVersionNegotiatedSerializer to keep the response backwards
|
|
||||||
// compatible.
|
|
||||||
mediaTypes, _ := negotiation.MediaTypesForSerializer(s)
|
|
||||||
ss := stripVersionNegotiatedSerializer{s}
|
|
||||||
versionHandler := APIVersionHandler(ss, getAPIVersionsFunc)
|
|
||||||
ws := new(restful.WebService)
|
|
||||||
ws.Path(apiPrefix)
|
|
||||||
ws.Doc("get available API versions")
|
|
||||||
ws.Route(ws.GET("/").To(versionHandler).
|
|
||||||
Doc("get available API versions").
|
|
||||||
Operation("getAPIVersions").
|
|
||||||
Produces(mediaTypes...).
|
|
||||||
Consumes(mediaTypes...).
|
|
||||||
Writes(metav1.APIVersions{}))
|
|
||||||
container.Add(ws)
|
|
||||||
}
|
|
||||||
|
|
||||||
// stripVersionEncoder strips APIVersion field from the encoding output. It's
|
|
||||||
// used to keep the responses at the discovery endpoints backward compatible
|
|
||||||
// with release-1.1, when the responses have empty APIVersion.
|
|
||||||
type stripVersionEncoder struct {
|
|
||||||
encoder runtime.Encoder
|
|
||||||
serializer runtime.Serializer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c stripVersionEncoder) Encode(obj runtime.Object, w io.Writer) error {
|
|
||||||
buf := bytes.NewBuffer([]byte{})
|
|
||||||
err := c.encoder.Encode(obj, buf)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
roundTrippedObj, gvk, err := c.serializer.Decode(buf.Bytes(), nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
gvk.Group = ""
|
|
||||||
gvk.Version = ""
|
|
||||||
roundTrippedObj.GetObjectKind().SetGroupVersionKind(*gvk)
|
|
||||||
return c.serializer.Encode(roundTrippedObj, w)
|
|
||||||
}
|
|
||||||
|
|
||||||
// stripVersionNegotiatedSerializer will return stripVersionEncoder when
|
|
||||||
// EncoderForVersion is called. See comments for stripVersionEncoder.
|
|
||||||
type stripVersionNegotiatedSerializer struct {
|
|
||||||
runtime.NegotiatedSerializer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n stripVersionNegotiatedSerializer) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
|
|
||||||
serializer, ok := encoder.(runtime.Serializer)
|
|
||||||
if !ok {
|
|
||||||
// The stripVersionEncoder needs both an encoder and decoder, but is called from a context that doesn't have access to the
|
|
||||||
// decoder. We do a best effort cast here (since this code path is only for backwards compatibility) to get access to the caller's
|
|
||||||
// decoder.
|
|
||||||
panic(fmt.Sprintf("Unable to extract serializer from %#v", encoder))
|
|
||||||
}
|
|
||||||
versioned := n.NegotiatedSerializer.EncoderForVersion(encoder, gv)
|
|
||||||
return stripVersionEncoder{versioned, serializer}
|
|
||||||
}
|
|
||||||
|
|
||||||
func keepUnversioned(group string) bool {
|
|
||||||
return group == "" || group == "extensions"
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewApisWebService returns a webservice serving the available api version under /apis.
|
|
||||||
func NewApisWebService(s runtime.NegotiatedSerializer, apiPrefix string, f func(req *restful.Request) []metav1.APIGroup) *restful.WebService {
|
|
||||||
// Because in release 1.1, /apis returns response with empty APIVersion, we
|
|
||||||
// use StripVersionNegotiatedSerializer to keep the response backwards
|
|
||||||
// compatible.
|
|
||||||
ss := stripVersionNegotiatedSerializer{s}
|
|
||||||
mediaTypes, _ := negotiation.MediaTypesForSerializer(s)
|
|
||||||
rootAPIHandler := handlers.RootAPIHandler(ss, f)
|
|
||||||
ws := new(restful.WebService)
|
|
||||||
ws.Path(apiPrefix)
|
|
||||||
ws.Doc("get available API versions")
|
|
||||||
ws.Route(ws.GET("/").To(rootAPIHandler).
|
|
||||||
Doc("get available API versions").
|
|
||||||
Operation("getAPIVersions").
|
|
||||||
Produces(mediaTypes...).
|
|
||||||
Consumes(mediaTypes...).
|
|
||||||
Writes(metav1.APIGroupList{}))
|
|
||||||
return ws
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewGroupWebService returns a webservice serving the supported versions, preferred version, and name
|
|
||||||
// of a group. E.g., such a web service will be registered at /apis/extensions.
|
|
||||||
func NewGroupWebService(s runtime.NegotiatedSerializer, path string, group metav1.APIGroup) *restful.WebService {
|
|
||||||
ss := s
|
|
||||||
if keepUnversioned(group.Name) {
|
|
||||||
// Because in release 1.1, /apis/extensions returns response with empty
|
|
||||||
// APIVersion, we use StripVersionNegotiatedSerializer to keep the
|
|
||||||
// response backwards compatible.
|
|
||||||
ss = stripVersionNegotiatedSerializer{s}
|
|
||||||
}
|
|
||||||
mediaTypes, _ := negotiation.MediaTypesForSerializer(s)
|
|
||||||
groupHandler := handlers.GroupHandler(ss, group)
|
|
||||||
ws := new(restful.WebService)
|
|
||||||
ws.Path(path)
|
|
||||||
ws.Doc("get information of a group")
|
|
||||||
ws.Route(ws.GET("/").To(groupHandler).
|
|
||||||
Doc("get information of a group").
|
|
||||||
Operation("getAPIGroup").
|
|
||||||
Produces(mediaTypes...).
|
|
||||||
Consumes(mediaTypes...).
|
|
||||||
Writes(metav1.APIGroup{}))
|
|
||||||
return ws
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adds a service to return the supported resources, E.g., a such web service
|
|
||||||
// will be registered at /apis/extensions/v1.
|
|
||||||
func AddSupportedResourcesWebService(s runtime.NegotiatedSerializer, ws *restful.WebService, groupVersion schema.GroupVersion, lister handlers.APIResourceLister) {
|
|
||||||
ss := s
|
|
||||||
if keepUnversioned(groupVersion.Group) {
|
|
||||||
// Because in release 1.1, /apis/extensions/v1beta1 returns response
|
|
||||||
// with empty APIVersion, we use StripVersionNegotiatedSerializer to
|
|
||||||
// keep the response backwards compatible.
|
|
||||||
ss = stripVersionNegotiatedSerializer{s}
|
|
||||||
}
|
|
||||||
mediaTypes, _ := negotiation.MediaTypesForSerializer(s)
|
|
||||||
resourceHandler := handlers.SupportedResourcesHandler(ss, groupVersion, lister)
|
|
||||||
ws.Route(ws.GET("/").To(resourceHandler).
|
|
||||||
Doc("get available resources").
|
|
||||||
Operation("getAPIResources").
|
|
||||||
Produces(mediaTypes...).
|
|
||||||
Consumes(mediaTypes...).
|
|
||||||
Writes(metav1.APIResourceList{}))
|
|
||||||
}
|
|
||||||
|
|
||||||
// APIVersionHandler returns a handler which will list the provided versions as available.
|
|
||||||
func APIVersionHandler(s runtime.NegotiatedSerializer, getAPIVersionsFunc func(req *restful.Request) *metav1.APIVersions) restful.RouteFunction {
|
|
||||||
return func(req *restful.Request, resp *restful.Response) {
|
|
||||||
responsewriters.WriteObjectNegotiated(s, schema.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, getAPIVersionsFunc(req))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
licenses(["notice"])
|
||||||
|
|
||||||
|
load(
|
||||||
|
"@io_bazel_rules_go//go:def.bzl",
|
||||||
|
"go_library",
|
||||||
|
"go_test",
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = [
|
||||||
|
"addresses_test.go",
|
||||||
|
"root_test.go",
|
||||||
|
],
|
||||||
|
library = ":go_default_library",
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = [
|
||||||
|
"//vendor/github.com/stretchr/testify/assert:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/apimachinery/registered:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = [
|
||||||
|
"addresses.go",
|
||||||
|
"group.go",
|
||||||
|
"legacy.go",
|
||||||
|
"root.go",
|
||||||
|
"util.go",
|
||||||
|
"version.go",
|
||||||
|
],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = [
|
||||||
|
"//vendor/github.com/emicklei/go-restful:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
|
||||||
|
"//vendor/k8s.io/apiserver/pkg/endpoints/handlers/negotiation:go_default_library",
|
||||||
|
"//vendor/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package server
|
package discovery
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
@ -22,22 +22,22 @@ import (
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DiscoveryAddresses interface {
|
type Addresses interface {
|
||||||
ServerAddressByClientCIDRs(net.IP) []metav1.ServerAddressByClientCIDR
|
ServerAddressByClientCIDRs(net.IP) []metav1.ServerAddressByClientCIDR
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultDiscoveryAddresses is a default implementation of DiscoveryAddresses that will work in most cases
|
// DefaultAddresses is a default implementation of Addresses that will work in most cases
|
||||||
type DefaultDiscoveryAddresses struct {
|
type DefaultAddresses struct {
|
||||||
// DiscoveryCIDRRules is a list of CIDRs and Addresses to use if a client is in the range
|
// CIDRRules is a list of CIDRs and Addresses to use if a client is in the range
|
||||||
DiscoveryCIDRRules []DiscoveryCIDRRule
|
CIDRRules []CIDRRule
|
||||||
|
|
||||||
// DefaultAddress is the address (hostname or IP and port) that should be used in
|
// DefaultAddress is the address (hostname or IP and port) that should be used in
|
||||||
// if no CIDR matches more specifically.
|
// if no CIDR matches more specifically.
|
||||||
DefaultAddress string
|
DefaultAddress string
|
||||||
}
|
}
|
||||||
|
|
||||||
// DiscoveryCIDRRule is a rule for adding an alternate path to the master based on matching CIDR
|
// CIDRRule is a rule for adding an alternate path to the master based on matching CIDR
|
||||||
type DiscoveryCIDRRule struct {
|
type CIDRRule struct {
|
||||||
IPRange net.IPNet
|
IPRange net.IPNet
|
||||||
|
|
||||||
// Address is the address (hostname or IP and port) that should be used in
|
// Address is the address (hostname or IP and port) that should be used in
|
||||||
|
@ -45,7 +45,7 @@ type DiscoveryCIDRRule struct {
|
||||||
Address string
|
Address string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d DefaultDiscoveryAddresses) ServerAddressByClientCIDRs(clientIP net.IP) []metav1.ServerAddressByClientCIDR {
|
func (d DefaultAddresses) ServerAddressByClientCIDRs(clientIP net.IP) []metav1.ServerAddressByClientCIDR {
|
||||||
addressCIDRMap := []metav1.ServerAddressByClientCIDR{
|
addressCIDRMap := []metav1.ServerAddressByClientCIDR{
|
||||||
{
|
{
|
||||||
ClientCIDR: "0.0.0.0/0",
|
ClientCIDR: "0.0.0.0/0",
|
||||||
|
@ -53,13 +53,13 @@ func (d DefaultDiscoveryAddresses) ServerAddressByClientCIDRs(clientIP net.IP) [
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, rule := range d.DiscoveryCIDRRules {
|
for _, rule := range d.CIDRRules {
|
||||||
addressCIDRMap = append(addressCIDRMap, rule.ServerAddressByClientCIDRs(clientIP)...)
|
addressCIDRMap = append(addressCIDRMap, rule.ServerAddressByClientCIDRs(clientIP)...)
|
||||||
}
|
}
|
||||||
return addressCIDRMap
|
return addressCIDRMap
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d DiscoveryCIDRRule) ServerAddressByClientCIDRs(clientIP net.IP) []metav1.ServerAddressByClientCIDR {
|
func (d CIDRRule) ServerAddressByClientCIDRs(clientIP net.IP) []metav1.ServerAddressByClientCIDR {
|
||||||
addressCIDRMap := []metav1.ServerAddressByClientCIDR{}
|
addressCIDRMap := []metav1.ServerAddressByClientCIDR{}
|
||||||
|
|
||||||
if d.IPRange.Contains(clientIP) {
|
if d.IPRange.Contains(clientIP) {
|
|
@ -0,0 +1,116 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 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 discovery
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetServerAddressByClientCIDRs(t *testing.T) {
|
||||||
|
publicAddressCIDRMap := []metav1.ServerAddressByClientCIDR{
|
||||||
|
{
|
||||||
|
ClientCIDR: "0.0.0.0/0",
|
||||||
|
ServerAddress: "ExternalAddress",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
internalAddressCIDRMap := []metav1.ServerAddressByClientCIDR{
|
||||||
|
publicAddressCIDRMap[0],
|
||||||
|
{
|
||||||
|
ClientCIDR: "10.0.0.0/24",
|
||||||
|
ServerAddress: "serviceIP",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
internalIP := "10.0.0.1"
|
||||||
|
publicIP := "1.1.1.1"
|
||||||
|
testCases := []struct {
|
||||||
|
Request http.Request
|
||||||
|
ExpectedMap []metav1.ServerAddressByClientCIDR
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Request: http.Request{},
|
||||||
|
ExpectedMap: publicAddressCIDRMap,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Request: http.Request{
|
||||||
|
Header: map[string][]string{
|
||||||
|
"X-Real-Ip": {internalIP},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ExpectedMap: internalAddressCIDRMap,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Request: http.Request{
|
||||||
|
Header: map[string][]string{
|
||||||
|
"X-Real-Ip": {publicIP},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ExpectedMap: publicAddressCIDRMap,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Request: http.Request{
|
||||||
|
Header: map[string][]string{
|
||||||
|
"X-Forwarded-For": {internalIP},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ExpectedMap: internalAddressCIDRMap,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Request: http.Request{
|
||||||
|
Header: map[string][]string{
|
||||||
|
"X-Forwarded-For": {publicIP},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ExpectedMap: publicAddressCIDRMap,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
Request: http.Request{
|
||||||
|
RemoteAddr: internalIP,
|
||||||
|
},
|
||||||
|
ExpectedMap: internalAddressCIDRMap,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Request: http.Request{
|
||||||
|
RemoteAddr: publicIP,
|
||||||
|
},
|
||||||
|
ExpectedMap: publicAddressCIDRMap,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Request: http.Request{
|
||||||
|
RemoteAddr: "invalidIP",
|
||||||
|
},
|
||||||
|
ExpectedMap: publicAddressCIDRMap,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ipRange, _ := net.ParseCIDR("10.0.0.0/24")
|
||||||
|
discoveryAddresses := DefaultAddresses{DefaultAddress: "ExternalAddress"}
|
||||||
|
discoveryAddresses.CIDRRules = append(discoveryAddresses.CIDRRules,
|
||||||
|
CIDRRule{IPRange: *ipRange, Address: "serviceIP"})
|
||||||
|
|
||||||
|
for i, test := range testCases {
|
||||||
|
if a, e := discoveryAddresses.ServerAddressByClientCIDRs(utilnet.GetClientIP(&test.Request)), test.ExpectedMap; reflect.DeepEqual(e, a) != true {
|
||||||
|
t.Fatalf("test case %d failed. expected: %v, actual: %v", i+1, e, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 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 discovery
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/emicklei/go-restful"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||||
|
)
|
||||||
|
|
||||||
|
// apiGroupHandler creates a webservice serving the supported versions, preferred version, and name
|
||||||
|
// of a group. E.g., such a web service will be registered at /apis/extensions.
|
||||||
|
type apiGroupHandler struct {
|
||||||
|
serializer runtime.NegotiatedSerializer
|
||||||
|
|
||||||
|
group metav1.APIGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAPIGroupHandler(serializer runtime.NegotiatedSerializer, group metav1.APIGroup) *apiGroupHandler {
|
||||||
|
if keepUnversioned(group.Name) {
|
||||||
|
// Because in release 1.1, /apis/extensions returns response with empty
|
||||||
|
// APIVersion, we use stripVersionNegotiatedSerializer to keep the
|
||||||
|
// response backwards compatible.
|
||||||
|
serializer = stripVersionNegotiatedSerializer{serializer}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &apiGroupHandler{
|
||||||
|
serializer: serializer,
|
||||||
|
group: group,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *apiGroupHandler) WebService() *restful.WebService {
|
||||||
|
mediaTypes, _ := negotiation.MediaTypesForSerializer(s.serializer)
|
||||||
|
ws := new(restful.WebService)
|
||||||
|
ws.Path(APIGroupPrefix + "/" + s.group.Name)
|
||||||
|
ws.Doc("get information of a group")
|
||||||
|
ws.Route(ws.GET("/").To(s.handle).
|
||||||
|
Doc("get information of a group").
|
||||||
|
Operation("getAPIGroup").
|
||||||
|
Produces(mediaTypes...).
|
||||||
|
Consumes(mediaTypes...).
|
||||||
|
Writes(metav1.APIGroup{}))
|
||||||
|
return ws
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle returns a handler which will return the api.GroupAndVersion of the group.
|
||||||
|
func (s *apiGroupHandler) handle(req *restful.Request, resp *restful.Response) {
|
||||||
|
responsewriters.WriteObjectNegotiated(s.serializer, schema.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, &s.group)
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 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 discovery
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/emicklei/go-restful"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||||
|
)
|
||||||
|
|
||||||
|
// legacyRootAPIHandler creates a webservice serving api group discovery.
|
||||||
|
type legacyRootAPIHandler struct {
|
||||||
|
// addresses is used to build cluster IPs for discovery.
|
||||||
|
addresses Addresses
|
||||||
|
apiPrefix string
|
||||||
|
serializer runtime.NegotiatedSerializer
|
||||||
|
apiVersions []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLegacyRootAPIHandler(addresses Addresses, serializer runtime.NegotiatedSerializer, apiPrefix string, apiVersions []string) *legacyRootAPIHandler {
|
||||||
|
// Because in release 1.1, /apis returns response with empty APIVersion, we
|
||||||
|
// use stripVersionNegotiatedSerializer to keep the response backwards
|
||||||
|
// compatible.
|
||||||
|
serializer = stripVersionNegotiatedSerializer{serializer}
|
||||||
|
|
||||||
|
return &legacyRootAPIHandler{
|
||||||
|
addresses: addresses,
|
||||||
|
apiPrefix: apiPrefix,
|
||||||
|
serializer: serializer,
|
||||||
|
apiVersions: apiVersions,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddApiWebService adds a service to return the supported api versions at the legacy /api.
|
||||||
|
func (s *legacyRootAPIHandler) WebService() *restful.WebService {
|
||||||
|
mediaTypes, _ := negotiation.MediaTypesForSerializer(s.serializer)
|
||||||
|
ws := new(restful.WebService)
|
||||||
|
ws.Path(s.apiPrefix)
|
||||||
|
ws.Doc("get available API versions")
|
||||||
|
ws.Route(ws.GET("/").To(s.handle).
|
||||||
|
Doc("get available API versions").
|
||||||
|
Operation("getAPIVersions").
|
||||||
|
Produces(mediaTypes...).
|
||||||
|
Consumes(mediaTypes...).
|
||||||
|
Writes(metav1.APIVersions{}))
|
||||||
|
return ws
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *legacyRootAPIHandler) handle(req *restful.Request, resp *restful.Response) {
|
||||||
|
clientIP := utilnet.GetClientIP(req.Request)
|
||||||
|
apiVersions := &metav1.APIVersions{
|
||||||
|
ServerAddressByClientCIDRs: s.addresses.ServerAddressByClientCIDRs(clientIP),
|
||||||
|
Versions: s.apiVersions,
|
||||||
|
}
|
||||||
|
|
||||||
|
responsewriters.WriteObjectNegotiated(s.serializer, schema.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, apiVersions)
|
||||||
|
}
|
|
@ -0,0 +1,135 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 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 discovery
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/emicklei/go-restful"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GroupManager is an interface that allows dynamic mutation of the existing webservice to handle
|
||||||
|
// API groups being added or removed.
|
||||||
|
type GroupManager interface {
|
||||||
|
AddGroup(apiGroup metav1.APIGroup)
|
||||||
|
RemoveGroup(groupName string)
|
||||||
|
|
||||||
|
WebService() *restful.WebService
|
||||||
|
}
|
||||||
|
|
||||||
|
// rootAPIsHandler creates a webservice serving api group discovery.
|
||||||
|
// The list of APIGroups may change while the server is running because additional resources
|
||||||
|
// are registered or removed. It is not safe to cache the values.
|
||||||
|
type rootAPIsHandler struct {
|
||||||
|
// addresses is used to build cluster IPs for discovery.
|
||||||
|
addresses Addresses
|
||||||
|
|
||||||
|
serializer runtime.NegotiatedSerializer
|
||||||
|
|
||||||
|
// Map storing information about all groups to be exposed in discovery response.
|
||||||
|
// The map is from name to the group.
|
||||||
|
lock sync.RWMutex
|
||||||
|
apiGroups map[string]metav1.APIGroup
|
||||||
|
// apiGroupNames preserves insertion order
|
||||||
|
apiGroupNames []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRootAPIsHandler(addresses Addresses, serializer runtime.NegotiatedSerializer) *rootAPIsHandler {
|
||||||
|
// Because in release 1.1, /apis returns response with empty APIVersion, we
|
||||||
|
// use stripVersionNegotiatedSerializer to keep the response backwards
|
||||||
|
// compatible.
|
||||||
|
serializer = stripVersionNegotiatedSerializer{serializer}
|
||||||
|
|
||||||
|
return &rootAPIsHandler{
|
||||||
|
addresses: addresses,
|
||||||
|
serializer: serializer,
|
||||||
|
apiGroups: map[string]metav1.APIGroup{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *rootAPIsHandler) AddGroup(apiGroup metav1.APIGroup) {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
_, alreadyExists := s.apiGroups[apiGroup.Name]
|
||||||
|
|
||||||
|
s.apiGroups[apiGroup.Name] = apiGroup
|
||||||
|
if !alreadyExists {
|
||||||
|
s.apiGroupNames = append(s.apiGroupNames, apiGroup.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *rootAPIsHandler) RemoveGroup(groupName string) {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
delete(s.apiGroups, groupName)
|
||||||
|
for i := range s.apiGroupNames {
|
||||||
|
if s.apiGroupNames[i] == groupName {
|
||||||
|
s.apiGroupNames = append(s.apiGroupNames[:i], s.apiGroupNames[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *rootAPIsHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
|
||||||
|
s.lock.RLock()
|
||||||
|
defer s.lock.RUnlock()
|
||||||
|
|
||||||
|
orderedGroups := []metav1.APIGroup{}
|
||||||
|
for _, groupName := range s.apiGroupNames {
|
||||||
|
orderedGroups = append(orderedGroups, s.apiGroups[groupName])
|
||||||
|
}
|
||||||
|
|
||||||
|
clientIP := utilnet.GetClientIP(req)
|
||||||
|
serverCIDR := s.addresses.ServerAddressByClientCIDRs(clientIP)
|
||||||
|
groups := make([]metav1.APIGroup, len(orderedGroups))
|
||||||
|
for i := range orderedGroups {
|
||||||
|
groups[i] = orderedGroups[i]
|
||||||
|
groups[i].ServerAddressByClientCIDRs = serverCIDR
|
||||||
|
}
|
||||||
|
|
||||||
|
responsewriters.WriteObjectNegotiated(s.serializer, schema.GroupVersion{}, resp, req, http.StatusOK, &metav1.APIGroupList{Groups: groups})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *rootAPIsHandler) restfulHandle(req *restful.Request, resp *restful.Response) {
|
||||||
|
s.ServeHTTP(resp.ResponseWriter, req.Request)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebService returns a webservice serving api group discovery.
|
||||||
|
// Note: during the server runtime apiGroups might change.
|
||||||
|
func (s *rootAPIsHandler) WebService() *restful.WebService {
|
||||||
|
mediaTypes, _ := negotiation.MediaTypesForSerializer(s.serializer)
|
||||||
|
ws := new(restful.WebService)
|
||||||
|
ws.Path(APIGroupPrefix)
|
||||||
|
ws.Doc("get available API versions")
|
||||||
|
ws.Route(ws.GET("/").To(s.restfulHandle).
|
||||||
|
Doc("get available API versions").
|
||||||
|
Operation("getAPIVersions").
|
||||||
|
Produces(mediaTypes...).
|
||||||
|
Consumes(mediaTypes...).
|
||||||
|
Writes(metav1.APIGroupList{}))
|
||||||
|
return ws
|
||||||
|
}
|
|
@ -0,0 +1,190 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 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 discovery
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/apimachinery/announced"
|
||||||
|
"k8s.io/apimachinery/pkg/apimachinery/registered"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||||
|
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
groupFactoryRegistry = make(announced.APIGroupFactoryRegistry)
|
||||||
|
registry = registered.NewOrDie("")
|
||||||
|
scheme = runtime.NewScheme()
|
||||||
|
codecs = serializer.NewCodecFactory(scheme)
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Register Unversioned types under their own special group
|
||||||
|
scheme.AddUnversionedTypes(schema.GroupVersion{Group: "", Version: "v1"},
|
||||||
|
&metav1.Status{},
|
||||||
|
&metav1.APIVersions{},
|
||||||
|
&metav1.APIGroupList{},
|
||||||
|
&metav1.APIGroup{},
|
||||||
|
&metav1.APIResourceList{},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeResponse(t *testing.T, resp *http.Response, obj interface{}) error {
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
|
t.Log(string(data))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, obj); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getGroupList(t *testing.T, server *httptest.Server) (*metav1.APIGroupList, error) {
|
||||||
|
resp, err := http.Get(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("unexpected server response, expected %d, actual: %d", http.StatusOK, resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
groupList := metav1.APIGroupList{}
|
||||||
|
err = decodeResponse(t, resp, &groupList)
|
||||||
|
return &groupList, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDiscoveryAtAPIS(t *testing.T) {
|
||||||
|
handler := NewRootAPIsHandler(DefaultAddresses{DefaultAddress: "192.168.1.1"}, codecs)
|
||||||
|
|
||||||
|
server := httptest.NewServer(handler)
|
||||||
|
groupList, err := getGroupList(t, server)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, 0, len(groupList.Groups))
|
||||||
|
|
||||||
|
// Add a Group.
|
||||||
|
extensionsGroupName := "extensions"
|
||||||
|
extensionsVersions := []metav1.GroupVersionForDiscovery{
|
||||||
|
{
|
||||||
|
GroupVersion: extensionsGroupName + "/v1",
|
||||||
|
Version: "v1",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
extensionsPreferredVersion := metav1.GroupVersionForDiscovery{
|
||||||
|
GroupVersion: extensionsGroupName + "/preferred",
|
||||||
|
Version: "preferred",
|
||||||
|
}
|
||||||
|
handler.AddGroup(metav1.APIGroup{
|
||||||
|
Name: extensionsGroupName,
|
||||||
|
Versions: extensionsVersions,
|
||||||
|
PreferredVersion: extensionsPreferredVersion,
|
||||||
|
})
|
||||||
|
|
||||||
|
groupList, err = getGroupList(t, server)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, 1, len(groupList.Groups))
|
||||||
|
groupListGroup := groupList.Groups[0]
|
||||||
|
assert.Equal(t, extensionsGroupName, groupListGroup.Name)
|
||||||
|
assert.Equal(t, extensionsVersions, groupListGroup.Versions)
|
||||||
|
assert.Equal(t, extensionsPreferredVersion, groupListGroup.PreferredVersion)
|
||||||
|
assert.Equal(t, handler.addresses.ServerAddressByClientCIDRs(utilnet.GetClientIP(&http.Request{})), groupListGroup.ServerAddressByClientCIDRs)
|
||||||
|
|
||||||
|
// Remove the group.
|
||||||
|
handler.RemoveGroup(extensionsGroupName)
|
||||||
|
groupList, err = getGroupList(t, server)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, 0, len(groupList.Groups))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDiscoveryOrdering(t *testing.T) {
|
||||||
|
handler := NewRootAPIsHandler(DefaultAddresses{DefaultAddress: "192.168.1.1"}, codecs)
|
||||||
|
|
||||||
|
server := httptest.NewServer(handler)
|
||||||
|
groupList, err := getGroupList(t, server)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, 0, len(groupList.Groups))
|
||||||
|
|
||||||
|
// Register three groups
|
||||||
|
handler.AddGroup(metav1.APIGroup{Name: "x"})
|
||||||
|
handler.AddGroup(metav1.APIGroup{Name: "y"})
|
||||||
|
handler.AddGroup(metav1.APIGroup{Name: "z"})
|
||||||
|
// Register three additional groups that come earlier alphabetically
|
||||||
|
handler.AddGroup(metav1.APIGroup{Name: "a"})
|
||||||
|
handler.AddGroup(metav1.APIGroup{Name: "b"})
|
||||||
|
handler.AddGroup(metav1.APIGroup{Name: "c"})
|
||||||
|
// Make sure re-adding doesn't double-register or make a group lose its place
|
||||||
|
handler.AddGroup(metav1.APIGroup{Name: "x"})
|
||||||
|
|
||||||
|
groupList, err = getGroupList(t, server)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, 6, len(groupList.Groups))
|
||||||
|
assert.Equal(t, "x", groupList.Groups[0].Name)
|
||||||
|
assert.Equal(t, "y", groupList.Groups[1].Name)
|
||||||
|
assert.Equal(t, "z", groupList.Groups[2].Name)
|
||||||
|
assert.Equal(t, "a", groupList.Groups[3].Name)
|
||||||
|
assert.Equal(t, "b", groupList.Groups[4].Name)
|
||||||
|
assert.Equal(t, "c", groupList.Groups[5].Name)
|
||||||
|
|
||||||
|
// Remove a group.
|
||||||
|
handler.RemoveGroup("a")
|
||||||
|
groupList, err = getGroupList(t, server)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, 5, len(groupList.Groups))
|
||||||
|
|
||||||
|
// Re-adding should move to the end.
|
||||||
|
handler.AddGroup(metav1.APIGroup{Name: "a"})
|
||||||
|
groupList, err = getGroupList(t, server)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, 6, len(groupList.Groups))
|
||||||
|
assert.Equal(t, "x", groupList.Groups[0].Name)
|
||||||
|
assert.Equal(t, "y", groupList.Groups[1].Name)
|
||||||
|
assert.Equal(t, "z", groupList.Groups[2].Name)
|
||||||
|
assert.Equal(t, "b", groupList.Groups[3].Name)
|
||||||
|
assert.Equal(t, "c", groupList.Groups[4].Name)
|
||||||
|
assert.Equal(t, "a", groupList.Groups[5].Name)
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 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 discovery
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
const APIGroupPrefix = "/apis"
|
||||||
|
|
||||||
|
func keepUnversioned(group string) bool {
|
||||||
|
return group == "" || group == "extensions"
|
||||||
|
}
|
||||||
|
|
||||||
|
// stripVersionEncoder strips APIVersion field from the encoding output. It's
|
||||||
|
// used to keep the responses at the discovery endpoints backward compatible
|
||||||
|
// with release-1.1, when the responses have empty APIVersion.
|
||||||
|
type stripVersionEncoder struct {
|
||||||
|
encoder runtime.Encoder
|
||||||
|
serializer runtime.Serializer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c stripVersionEncoder) Encode(obj runtime.Object, w io.Writer) error {
|
||||||
|
buf := bytes.NewBuffer([]byte{})
|
||||||
|
err := c.encoder.Encode(obj, buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
roundTrippedObj, gvk, err := c.serializer.Decode(buf.Bytes(), nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
gvk.Group = ""
|
||||||
|
gvk.Version = ""
|
||||||
|
roundTrippedObj.GetObjectKind().SetGroupVersionKind(*gvk)
|
||||||
|
return c.serializer.Encode(roundTrippedObj, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stripVersionNegotiatedSerializer will return stripVersionEncoder when
|
||||||
|
// EncoderForVersion is called. See comments for stripVersionEncoder.
|
||||||
|
type stripVersionNegotiatedSerializer struct {
|
||||||
|
runtime.NegotiatedSerializer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n stripVersionNegotiatedSerializer) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
|
||||||
|
serializer, ok := encoder.(runtime.Serializer)
|
||||||
|
if !ok {
|
||||||
|
// The stripVersionEncoder needs both an encoder and decoder, but is called from a context that doesn't have access to the
|
||||||
|
// decoder. We do a best effort cast here (since this code path is only for backwards compatibility) to get access to the caller's
|
||||||
|
// decoder.
|
||||||
|
panic(fmt.Sprintf("Unable to extract serializer from %#v", encoder))
|
||||||
|
}
|
||||||
|
versioned := n.NegotiatedSerializer.EncoderForVersion(encoder, gv)
|
||||||
|
return stripVersionEncoder{versioned, serializer}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 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 discovery
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/emicklei/go-restful"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||||
|
)
|
||||||
|
|
||||||
|
type APIResourceLister interface {
|
||||||
|
ListAPIResources() []metav1.APIResource
|
||||||
|
}
|
||||||
|
|
||||||
|
// apiVersionHandler creates a webservice serving the supported resources for the version
|
||||||
|
// E.g., such a web service will be registered at /apis/extensions/v1beta1.
|
||||||
|
type apiVersionHandler struct {
|
||||||
|
serializer runtime.NegotiatedSerializer
|
||||||
|
|
||||||
|
groupVersion schema.GroupVersion
|
||||||
|
apiResourceLister APIResourceLister
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAPIVersionHandler(serializer runtime.NegotiatedSerializer, groupVersion schema.GroupVersion, apiResourceLister APIResourceLister) *apiVersionHandler {
|
||||||
|
if keepUnversioned(groupVersion.Group) {
|
||||||
|
// Because in release 1.1, /apis/extensions returns response with empty
|
||||||
|
// APIVersion, we use stripVersionNegotiatedSerializer to keep the
|
||||||
|
// response backwards compatible.
|
||||||
|
serializer = stripVersionNegotiatedSerializer{serializer}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &apiVersionHandler{
|
||||||
|
serializer: serializer,
|
||||||
|
groupVersion: groupVersion,
|
||||||
|
apiResourceLister: apiResourceLister,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *apiVersionHandler) AddToWebService(ws *restful.WebService) {
|
||||||
|
mediaTypes, _ := negotiation.MediaTypesForSerializer(s.serializer)
|
||||||
|
ws.Route(ws.GET("/").To(s.handle).
|
||||||
|
Doc("get available resources").
|
||||||
|
Operation("getAPIResources").
|
||||||
|
Produces(mediaTypes...).
|
||||||
|
Consumes(mediaTypes...).
|
||||||
|
Writes(metav1.APIResourceList{}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle returns a handler which will return the api.VersionAndVersion of the group.
|
||||||
|
func (s *apiVersionHandler) handle(req *restful.Request, resp *restful.Response) {
|
||||||
|
responsewriters.WriteObjectNegotiated(s.serializer, schema.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK,
|
||||||
|
&metav1.APIResourceList{GroupVersion: s.groupVersion.String(), APIResources: s.apiResourceLister.ListAPIResources()})
|
||||||
|
}
|
|
@ -30,7 +30,7 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||||
"k8s.io/apiserver/pkg/admission"
|
"k8s.io/apiserver/pkg/admission"
|
||||||
"k8s.io/apiserver/pkg/endpoints/handlers"
|
"k8s.io/apiserver/pkg/endpoints/discovery"
|
||||||
"k8s.io/apiserver/pkg/endpoints/request"
|
"k8s.io/apiserver/pkg/endpoints/request"
|
||||||
"k8s.io/apiserver/pkg/registry/rest"
|
"k8s.io/apiserver/pkg/registry/rest"
|
||||||
)
|
)
|
||||||
|
@ -86,7 +86,7 @@ type APIGroupVersion struct {
|
||||||
|
|
||||||
// ResourceLister is an interface that knows how to list resources
|
// ResourceLister is an interface that knows how to list resources
|
||||||
// for this API Group.
|
// for this API Group.
|
||||||
ResourceLister handlers.APIResourceLister
|
ResourceLister discovery.APIResourceLister
|
||||||
}
|
}
|
||||||
|
|
||||||
// InstallREST registers the REST handlers (storage, watch, proxy and redirect) into a restful Container.
|
// InstallREST registers the REST handlers (storage, watch, proxy and redirect) into a restful Container.
|
||||||
|
@ -100,7 +100,8 @@ func (g *APIGroupVersion) InstallREST(container *restful.Container) error {
|
||||||
if lister == nil {
|
if lister == nil {
|
||||||
lister = staticLister{apiResources}
|
lister = staticLister{apiResources}
|
||||||
}
|
}
|
||||||
AddSupportedResourcesWebService(g.Serializer, ws, g.GroupVersion, lister)
|
versionDiscoveryHandler := discovery.NewAPIVersionHandler(g.Serializer, g.GroupVersion, lister)
|
||||||
|
versionDiscoveryHandler.AddToWebService(ws)
|
||||||
container.Add(ws)
|
container.Add(ws)
|
||||||
return utilerrors.NewAggregate(registrationErrors)
|
return utilerrors.NewAggregate(registrationErrors)
|
||||||
}
|
}
|
||||||
|
@ -128,7 +129,8 @@ func (g *APIGroupVersion) UpdateREST(container *restful.Container) error {
|
||||||
if lister == nil {
|
if lister == nil {
|
||||||
lister = staticLister{apiResources}
|
lister = staticLister{apiResources}
|
||||||
}
|
}
|
||||||
AddSupportedResourcesWebService(g.Serializer, ws, g.GroupVersion, lister)
|
versionDiscoveryHandler := discovery.NewAPIVersionHandler(g.Serializer, g.GroupVersion, lister)
|
||||||
|
versionDiscoveryHandler.AddToWebService(ws)
|
||||||
return utilerrors.NewAggregate(registrationErrors)
|
return utilerrors.NewAggregate(registrationErrors)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,4 +154,4 @@ func (s staticLister) ListAPIResources() []metav1.APIResource {
|
||||||
return s.list
|
return s.list
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ handlers.APIResourceLister = &staticLister{}
|
var _ discovery.APIResourceLister = &staticLister{}
|
||||||
|
|
|
@ -34,7 +34,6 @@ go_test(
|
||||||
go_library(
|
go_library(
|
||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
srcs = [
|
srcs = [
|
||||||
"discovery.go",
|
|
||||||
"doc.go",
|
"doc.go",
|
||||||
"namer.go",
|
"namer.go",
|
||||||
"patch.go",
|
"patch.go",
|
||||||
|
@ -44,7 +43,6 @@ go_library(
|
||||||
],
|
],
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
deps = [
|
deps = [
|
||||||
"//vendor/github.com/emicklei/go-restful:go_default_library",
|
|
||||||
"//vendor/github.com/evanphx/json-patch:go_default_library",
|
"//vendor/github.com/evanphx/json-patch:go_default_library",
|
||||||
"//vendor/github.com/golang/glog:go_default_library",
|
"//vendor/github.com/golang/glog:go_default_library",
|
||||||
"//vendor/golang.org/x/net/websocket:go_default_library",
|
"//vendor/golang.org/x/net/websocket:go_default_library",
|
||||||
|
|
|
@ -1,54 +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 handlers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
|
||||||
|
|
||||||
"github.com/emicklei/go-restful"
|
|
||||||
)
|
|
||||||
|
|
||||||
type APIResourceLister interface {
|
|
||||||
ListAPIResources() []metav1.APIResource
|
|
||||||
}
|
|
||||||
|
|
||||||
// RootAPIHandler returns a handler which will list the provided groups and versions as available.
|
|
||||||
func RootAPIHandler(s runtime.NegotiatedSerializer, f func(req *restful.Request) []metav1.APIGroup) restful.RouteFunction {
|
|
||||||
return func(req *restful.Request, resp *restful.Response) {
|
|
||||||
responsewriters.WriteObjectNegotiated(s, schema.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, &metav1.APIGroupList{Groups: f(req)})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GroupHandler returns a handler which will return the api.GroupAndVersion of
|
|
||||||
// the group.
|
|
||||||
func GroupHandler(s runtime.NegotiatedSerializer, group metav1.APIGroup) restful.RouteFunction {
|
|
||||||
return func(req *restful.Request, resp *restful.Response) {
|
|
||||||
responsewriters.WriteObjectNegotiated(s, schema.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, &group)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SupportedResourcesHandler returns a handler which will list the provided resources as available.
|
|
||||||
func SupportedResourcesHandler(s runtime.NegotiatedSerializer, groupVersion schema.GroupVersion, lister APIResourceLister) restful.RouteFunction {
|
|
||||||
return func(req *restful.Request, resp *restful.Response) {
|
|
||||||
responsewriters.WriteObjectNegotiated(s, schema.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, &metav1.APIResourceList{GroupVersion: groupVersion.String(), APIResources: lister.ListAPIResources()})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -25,13 +25,13 @@ go_test(
|
||||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/version:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/version:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/apis/example:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/apis/example:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/apis/example/v1:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/apis/example/v1:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
|
||||||
|
"//vendor/k8s.io/apiserver/pkg/endpoints/discovery:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/server/healthz:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/server/healthz:go_default_library",
|
||||||
|
@ -46,7 +46,6 @@ go_library(
|
||||||
srcs = [
|
srcs = [
|
||||||
"config.go",
|
"config.go",
|
||||||
"config_selfclient.go",
|
"config_selfclient.go",
|
||||||
"discovery.go",
|
|
||||||
"doc.go",
|
"doc.go",
|
||||||
"genericapiserver.go",
|
"genericapiserver.go",
|
||||||
"healthz.go",
|
"healthz.go",
|
||||||
|
@ -56,7 +55,6 @@ go_library(
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
deps = [
|
deps = [
|
||||||
"//vendor/github.com/coreos/go-systemd/daemon:go_default_library",
|
"//vendor/github.com/coreos/go-systemd/daemon:go_default_library",
|
||||||
"//vendor/github.com/emicklei/go-restful:go_default_library",
|
|
||||||
"//vendor/github.com/emicklei/go-restful/swagger:go_default_library",
|
"//vendor/github.com/emicklei/go-restful/swagger:go_default_library",
|
||||||
"//vendor/github.com/go-openapi/spec:go_default_library",
|
"//vendor/github.com/go-openapi/spec:go_default_library",
|
||||||
"//vendor/github.com/golang/glog:go_default_library",
|
"//vendor/github.com/golang/glog:go_default_library",
|
||||||
|
@ -69,7 +67,6 @@ go_library(
|
||||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library",
|
||||||
|
@ -84,6 +81,7 @@ go_library(
|
||||||
"//vendor/k8s.io/apiserver/pkg/authorization/authorizerfactory:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/authorization/authorizerfactory:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/authorization/union:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/authorization/union:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/endpoints:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/endpoints:go_default_library",
|
||||||
|
"//vendor/k8s.io/apiserver/pkg/endpoints/discovery:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/endpoints/filters:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/endpoints/filters:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/endpoints/openapi:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/endpoints/openapi:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
|
||||||
|
|
|
@ -33,7 +33,6 @@ import (
|
||||||
"github.com/go-openapi/spec"
|
"github.com/go-openapi/spec"
|
||||||
"github.com/pborman/uuid"
|
"github.com/pborman/uuid"
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
openapicommon "k8s.io/apimachinery/pkg/openapi"
|
openapicommon "k8s.io/apimachinery/pkg/openapi"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||||
|
@ -47,6 +46,7 @@ import (
|
||||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||||
"k8s.io/apiserver/pkg/authorization/authorizerfactory"
|
"k8s.io/apiserver/pkg/authorization/authorizerfactory"
|
||||||
authorizerunion "k8s.io/apiserver/pkg/authorization/union"
|
authorizerunion "k8s.io/apiserver/pkg/authorization/union"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/discovery"
|
||||||
genericapifilters "k8s.io/apiserver/pkg/endpoints/filters"
|
genericapifilters "k8s.io/apiserver/pkg/endpoints/filters"
|
||||||
apiopenapi "k8s.io/apiserver/pkg/endpoints/openapi"
|
apiopenapi "k8s.io/apiserver/pkg/endpoints/openapi"
|
||||||
apirequest "k8s.io/apiserver/pkg/endpoints/request"
|
apirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||||
|
@ -122,7 +122,7 @@ type Config struct {
|
||||||
BuildHandlerChainFunc func(apiHandler http.Handler, c *Config) (secure http.Handler)
|
BuildHandlerChainFunc func(apiHandler http.Handler, c *Config) (secure http.Handler)
|
||||||
// DiscoveryAddresses is used to build the IPs pass to discovery. If nil, the ExternalAddress is
|
// DiscoveryAddresses is used to build the IPs pass to discovery. If nil, the ExternalAddress is
|
||||||
// always reported
|
// always reported
|
||||||
DiscoveryAddresses DiscoveryAddresses
|
DiscoveryAddresses discovery.Addresses
|
||||||
// The default set of healthz checks. There might be more added via AddHealthzChecks dynamically.
|
// The default set of healthz checks. There might be more added via AddHealthzChecks dynamically.
|
||||||
HealthzChecks []healthz.HealthzChecker
|
HealthzChecks []healthz.HealthzChecker
|
||||||
// LegacyAPIGroupPrefixes is used to set up URL parsing for authorization and for validating requests
|
// LegacyAPIGroupPrefixes is used to set up URL parsing for authorization and for validating requests
|
||||||
|
@ -321,7 +321,7 @@ func (c *Config) Complete() completedConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if c.DiscoveryAddresses == nil {
|
if c.DiscoveryAddresses == nil {
|
||||||
c.DiscoveryAddresses = DefaultDiscoveryAddresses{DefaultAddress: c.ExternalAddress}
|
c.DiscoveryAddresses = discovery.DefaultAddresses{DefaultAddress: c.ExternalAddress}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the loopbackclientconfig is specified AND it has a token for use against the API server
|
// If the loopbackclientconfig is specified AND it has a token for use against the API server
|
||||||
|
@ -408,8 +408,6 @@ func (c completedConfig) constructServer() (*GenericAPIServer, error) {
|
||||||
SecureServingInfo: c.SecureServingInfo,
|
SecureServingInfo: c.SecureServingInfo,
|
||||||
ExternalAddress: c.ExternalAddress,
|
ExternalAddress: c.ExternalAddress,
|
||||||
|
|
||||||
apiGroupsForDiscovery: map[string]metav1.APIGroup{},
|
|
||||||
|
|
||||||
HandlerContainer: handlerContainer,
|
HandlerContainer: handlerContainer,
|
||||||
FallThroughHandler: c.FallThroughHandler,
|
FallThroughHandler: c.FallThroughHandler,
|
||||||
|
|
||||||
|
@ -422,6 +420,8 @@ func (c completedConfig) constructServer() (*GenericAPIServer, error) {
|
||||||
disabledPostStartHooks: c.DisabledPostStartHooks,
|
disabledPostStartHooks: c.DisabledPostStartHooks,
|
||||||
|
|
||||||
healthzChecks: c.HealthzChecks,
|
healthzChecks: c.HealthzChecks,
|
||||||
|
|
||||||
|
DiscoveryGroupManager: discovery.NewRootAPIsHandler(c.DiscoveryAddresses, c.Serializer),
|
||||||
}
|
}
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
|
@ -529,7 +529,7 @@ func installAPI(s *GenericAPIServer, c *Config, delegate http.Handler) {
|
||||||
routes.Version{Version: c.Version}.Install(s.HandlerContainer)
|
routes.Version{Version: c.Version}.Install(s.HandlerContainer)
|
||||||
|
|
||||||
if c.EnableDiscovery {
|
if c.EnableDiscovery {
|
||||||
s.HandlerContainer.Add(s.DynamicApisDiscovery())
|
s.HandlerContainer.Add(s.DiscoveryGroupManager.WebService())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
systemd "github.com/coreos/go-systemd/daemon"
|
systemd "github.com/coreos/go-systemd/daemon"
|
||||||
"github.com/emicklei/go-restful"
|
|
||||||
"github.com/emicklei/go-restful/swagger"
|
"github.com/emicklei/go-restful/swagger"
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
|
||||||
|
@ -36,10 +35,10 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
"k8s.io/apiserver/pkg/admission"
|
"k8s.io/apiserver/pkg/admission"
|
||||||
genericapi "k8s.io/apiserver/pkg/endpoints"
|
genericapi "k8s.io/apiserver/pkg/endpoints"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/discovery"
|
||||||
apirequest "k8s.io/apiserver/pkg/endpoints/request"
|
apirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||||
"k8s.io/apiserver/pkg/registry/rest"
|
"k8s.io/apiserver/pkg/registry/rest"
|
||||||
"k8s.io/apiserver/pkg/server/healthz"
|
"k8s.io/apiserver/pkg/server/healthz"
|
||||||
|
@ -84,7 +83,7 @@ type APIGroupInfo struct {
|
||||||
// GenericAPIServer contains state for a Kubernetes cluster api server.
|
// GenericAPIServer contains state for a Kubernetes cluster api server.
|
||||||
type GenericAPIServer struct {
|
type GenericAPIServer struct {
|
||||||
// discoveryAddresses is used to build cluster IPs for discovery.
|
// discoveryAddresses is used to build cluster IPs for discovery.
|
||||||
discoveryAddresses DiscoveryAddresses
|
discoveryAddresses discovery.Addresses
|
||||||
|
|
||||||
// LoopbackClientConfig is a config for a privileged loopback connection to the API server
|
// LoopbackClientConfig is a config for a privileged loopback connection to the API server
|
||||||
LoopbackClientConfig *restclient.Config
|
LoopbackClientConfig *restclient.Config
|
||||||
|
@ -130,12 +129,8 @@ type GenericAPIServer struct {
|
||||||
// listedPathProvider is a lister which provides the set of paths to show at /
|
// listedPathProvider is a lister which provides the set of paths to show at /
|
||||||
listedPathProvider routes.ListedPathProvider
|
listedPathProvider routes.ListedPathProvider
|
||||||
|
|
||||||
// Map storing information about all groups to be exposed in discovery response.
|
// DiscoveryGroupManager serves /apis
|
||||||
// The map is from name to the group.
|
DiscoveryGroupManager discovery.GroupManager
|
||||||
// The slice preserves group name insertion order.
|
|
||||||
apiGroupsForDiscoveryLock sync.RWMutex
|
|
||||||
apiGroupsForDiscovery map[string]metav1.APIGroup
|
|
||||||
apiGroupNamesForDiscovery []string
|
|
||||||
|
|
||||||
// Enable swagger and/or OpenAPI if these configs are non-nil.
|
// Enable swagger and/or OpenAPI if these configs are non-nil.
|
||||||
swaggerConfig *swagger.Config
|
swaggerConfig *swagger.Config
|
||||||
|
@ -334,15 +329,7 @@ func (s *GenericAPIServer) InstallLegacyAPIGroup(apiPrefix string, apiGroupInfo
|
||||||
}
|
}
|
||||||
// Install the version handler.
|
// Install the version handler.
|
||||||
// Add a handler at /<apiPrefix> to enumerate the supported api versions.
|
// Add a handler at /<apiPrefix> to enumerate the supported api versions.
|
||||||
genericapi.AddApiWebService(s.Serializer, s.HandlerContainer.Container, apiPrefix, func(req *restful.Request) *metav1.APIVersions {
|
s.HandlerContainer.Add(discovery.NewLegacyRootAPIHandler(s.discoveryAddresses, s.Serializer, apiPrefix, apiVersions).WebService())
|
||||||
clientIP := utilnet.GetClientIP(req.Request)
|
|
||||||
|
|
||||||
apiVersionsForDiscovery := metav1.APIVersions{
|
|
||||||
ServerAddressByClientCIDRs: s.discoveryAddresses.ServerAddressByClientCIDRs(clientIP),
|
|
||||||
Versions: apiVersions,
|
|
||||||
}
|
|
||||||
return &apiVersionsForDiscovery
|
|
||||||
})
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -385,43 +372,12 @@ func (s *GenericAPIServer) InstallAPIGroup(apiGroupInfo *APIGroupInfo) error {
|
||||||
PreferredVersion: preferedVersionForDiscovery,
|
PreferredVersion: preferedVersionForDiscovery,
|
||||||
}
|
}
|
||||||
|
|
||||||
s.AddAPIGroupForDiscovery(apiGroup)
|
s.DiscoveryGroupManager.AddGroup(apiGroup)
|
||||||
s.HandlerContainer.Add(genericapi.NewGroupWebService(s.Serializer, APIGroupPrefix+"/"+apiGroup.Name, apiGroup))
|
s.HandlerContainer.Add(discovery.NewAPIGroupHandler(s.Serializer, apiGroup).WebService())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddAPIGroupForDiscovery adds the specified group to the list served to discovery queries.
|
|
||||||
// Groups are listed in the order they are added.
|
|
||||||
func (s *GenericAPIServer) AddAPIGroupForDiscovery(apiGroup metav1.APIGroup) {
|
|
||||||
s.apiGroupsForDiscoveryLock.Lock()
|
|
||||||
defer s.apiGroupsForDiscoveryLock.Unlock()
|
|
||||||
|
|
||||||
// Insert the group into the ordered list if it is not already present
|
|
||||||
if _, exists := s.apiGroupsForDiscovery[apiGroup.Name]; !exists {
|
|
||||||
s.apiGroupNamesForDiscovery = append(s.apiGroupNamesForDiscovery, apiGroup.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.apiGroupsForDiscovery[apiGroup.Name] = apiGroup
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *GenericAPIServer) RemoveAPIGroupForDiscovery(groupName string) {
|
|
||||||
s.apiGroupsForDiscoveryLock.Lock()
|
|
||||||
defer s.apiGroupsForDiscoveryLock.Unlock()
|
|
||||||
|
|
||||||
// Remove the group from the ordered list
|
|
||||||
// https://github.com/golang/go/wiki/SliceTricks#filtering-without-allocating
|
|
||||||
newOrder := s.apiGroupNamesForDiscovery[:0]
|
|
||||||
for _, orderedGroupName := range s.apiGroupNamesForDiscovery {
|
|
||||||
if orderedGroupName != groupName {
|
|
||||||
newOrder = append(newOrder, orderedGroupName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.apiGroupNamesForDiscovery = newOrder
|
|
||||||
|
|
||||||
delete(s.apiGroupsForDiscovery, groupName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *GenericAPIServer) getAPIGroupVersion(apiGroupInfo *APIGroupInfo, groupVersion schema.GroupVersion, apiPrefix string) *genericapi.APIGroupVersion {
|
func (s *GenericAPIServer) getAPIGroupVersion(apiGroupInfo *APIGroupInfo, groupVersion schema.GroupVersion, apiPrefix string) *genericapi.APIGroupVersion {
|
||||||
storage := make(map[string]rest.Storage)
|
storage := make(map[string]rest.Storage)
|
||||||
for k, v := range apiGroupInfo.VersionedResourcesStorageMap[groupVersion.Version] {
|
for k, v := range apiGroupInfo.VersionedResourcesStorageMap[groupVersion.Version] {
|
||||||
|
@ -456,31 +412,6 @@ func (s *GenericAPIServer) newAPIGroupVersion(apiGroupInfo *APIGroupInfo, groupV
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DynamicApisDiscovery returns a webservice serving api group discovery.
|
|
||||||
// Note: during the server runtime apiGroupsForDiscovery might change.
|
|
||||||
func (s *GenericAPIServer) DynamicApisDiscovery() *restful.WebService {
|
|
||||||
return genericapi.NewApisWebService(s.Serializer, APIGroupPrefix, func(req *restful.Request) []metav1.APIGroup {
|
|
||||||
s.apiGroupsForDiscoveryLock.RLock()
|
|
||||||
defer s.apiGroupsForDiscoveryLock.RUnlock()
|
|
||||||
|
|
||||||
sortedGroups := []metav1.APIGroup{}
|
|
||||||
|
|
||||||
// ranging over apiGroupNamesForDiscovery preserves the registration order
|
|
||||||
for _, groupName := range s.apiGroupNamesForDiscovery {
|
|
||||||
sortedGroups = append(sortedGroups, s.apiGroupsForDiscovery[groupName])
|
|
||||||
}
|
|
||||||
|
|
||||||
clientIP := utilnet.GetClientIP(req.Request)
|
|
||||||
serverCIDR := s.discoveryAddresses.ServerAddressByClientCIDRs(clientIP)
|
|
||||||
groups := make([]metav1.APIGroup, len(sortedGroups))
|
|
||||||
for i := range sortedGroups {
|
|
||||||
groups[i] = sortedGroups[i]
|
|
||||||
groups[i].ServerAddressByClientCIDRs = serverCIDR
|
|
||||||
}
|
|
||||||
return groups
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDefaultAPIGroupInfo returns an APIGroupInfo stubbed with "normal" values
|
// NewDefaultAPIGroupInfo returns an APIGroupInfo stubbed with "normal" values
|
||||||
// exposed for easier composition from other packages
|
// exposed for easier composition from other packages
|
||||||
func NewDefaultAPIGroupInfo(group string, registry *registered.APIRegistrationManager, scheme *runtime.Scheme, parameterCodec runtime.ParameterCodec, codecs serializer.CodecFactory) APIGroupInfo {
|
func NewDefaultAPIGroupInfo(group string, registry *registered.APIRegistrationManager, scheme *runtime.Scheme, parameterCodec runtime.ParameterCodec, codecs serializer.CodecFactory) APIGroupInfo {
|
||||||
|
|
|
@ -24,7 +24,6 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"reflect"
|
|
||||||
goruntime "runtime"
|
goruntime "runtime"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -39,13 +38,13 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
"k8s.io/apimachinery/pkg/version"
|
"k8s.io/apimachinery/pkg/version"
|
||||||
"k8s.io/apiserver/pkg/apis/example"
|
"k8s.io/apiserver/pkg/apis/example"
|
||||||
examplev1 "k8s.io/apiserver/pkg/apis/example/v1"
|
examplev1 "k8s.io/apiserver/pkg/apis/example/v1"
|
||||||
"k8s.io/apiserver/pkg/authentication/user"
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/discovery"
|
||||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||||
"k8s.io/apiserver/pkg/registry/rest"
|
"k8s.io/apiserver/pkg/registry/rest"
|
||||||
"k8s.io/apiserver/pkg/server/mux"
|
"k8s.io/apiserver/pkg/server/mux"
|
||||||
|
@ -139,7 +138,7 @@ func TestInstallAPIGroups(t *testing.T) {
|
||||||
defer etcdserver.Terminate(t)
|
defer etcdserver.Terminate(t)
|
||||||
|
|
||||||
config.LegacyAPIGroupPrefixes = sets.NewString("/apiPrefix")
|
config.LegacyAPIGroupPrefixes = sets.NewString("/apiPrefix")
|
||||||
config.DiscoveryAddresses = DefaultDiscoveryAddresses{DefaultAddress: "ExternalAddress"}
|
config.DiscoveryAddresses = discovery.DefaultAddresses{DefaultAddress: "ExternalAddress"}
|
||||||
|
|
||||||
s, err := config.SkipComplete().New()
|
s, err := config.SkipComplete().New()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -461,218 +460,6 @@ func decodeResponse(resp *http.Response, obj interface{}) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getGroupList(server *httptest.Server) (*metav1.APIGroupList, error) {
|
|
||||||
resp, err := http.Get(server.URL + "/apis")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return nil, fmt.Errorf("unexpected server response, expected %d, actual: %d", http.StatusOK, resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
groupList := metav1.APIGroupList{}
|
|
||||||
err = decodeResponse(resp, &groupList)
|
|
||||||
return &groupList, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDiscoveryAtAPIS(t *testing.T) {
|
|
||||||
master, etcdserver, _, assert := newMaster(t)
|
|
||||||
defer etcdserver.Terminate(t)
|
|
||||||
|
|
||||||
server := httptest.NewServer(master.Handler)
|
|
||||||
groupList, err := getGroupList(server)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
assert.Equal(0, len(groupList.Groups))
|
|
||||||
|
|
||||||
// Add a Group.
|
|
||||||
extensionsVersions := []metav1.GroupVersionForDiscovery{
|
|
||||||
{
|
|
||||||
GroupVersion: examplev1.SchemeGroupVersion.String(),
|
|
||||||
Version: examplev1.SchemeGroupVersion.Version,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
extensionsPreferredVersion := metav1.GroupVersionForDiscovery{
|
|
||||||
GroupVersion: extensionsGroupName + "/preferred",
|
|
||||||
Version: "preferred",
|
|
||||||
}
|
|
||||||
master.AddAPIGroupForDiscovery(metav1.APIGroup{
|
|
||||||
Name: extensionsGroupName,
|
|
||||||
Versions: extensionsVersions,
|
|
||||||
PreferredVersion: extensionsPreferredVersion,
|
|
||||||
})
|
|
||||||
|
|
||||||
groupList, err = getGroupList(server)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(1, len(groupList.Groups))
|
|
||||||
groupListGroup := groupList.Groups[0]
|
|
||||||
assert.Equal(extensionsGroupName, groupListGroup.Name)
|
|
||||||
assert.Equal(extensionsVersions, groupListGroup.Versions)
|
|
||||||
assert.Equal(extensionsPreferredVersion, groupListGroup.PreferredVersion)
|
|
||||||
assert.Equal(master.discoveryAddresses.ServerAddressByClientCIDRs(utilnet.GetClientIP(&http.Request{})), groupListGroup.ServerAddressByClientCIDRs)
|
|
||||||
|
|
||||||
// Remove the group.
|
|
||||||
master.RemoveAPIGroupForDiscovery(extensionsGroupName)
|
|
||||||
groupList, err = getGroupList(server)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(0, len(groupList.Groups))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDiscoveryOrdering(t *testing.T) {
|
|
||||||
master, etcdserver, _, assert := newMaster(t)
|
|
||||||
defer etcdserver.Terminate(t)
|
|
||||||
|
|
||||||
server := httptest.NewServer(master.Handler)
|
|
||||||
groupList, err := getGroupList(server)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
assert.Equal(0, len(groupList.Groups))
|
|
||||||
|
|
||||||
// Register three groups
|
|
||||||
master.AddAPIGroupForDiscovery(metav1.APIGroup{Name: "x"})
|
|
||||||
master.AddAPIGroupForDiscovery(metav1.APIGroup{Name: "y"})
|
|
||||||
master.AddAPIGroupForDiscovery(metav1.APIGroup{Name: "z"})
|
|
||||||
// Register three additional groups that come earlier alphabetically
|
|
||||||
master.AddAPIGroupForDiscovery(metav1.APIGroup{Name: "a"})
|
|
||||||
master.AddAPIGroupForDiscovery(metav1.APIGroup{Name: "b"})
|
|
||||||
master.AddAPIGroupForDiscovery(metav1.APIGroup{Name: "c"})
|
|
||||||
// Make sure re-adding doesn't double-register or make a group lose its place
|
|
||||||
master.AddAPIGroupForDiscovery(metav1.APIGroup{Name: "x"})
|
|
||||||
|
|
||||||
groupList, err = getGroupList(server)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(6, len(groupList.Groups))
|
|
||||||
assert.Equal("x", groupList.Groups[0].Name)
|
|
||||||
assert.Equal("y", groupList.Groups[1].Name)
|
|
||||||
assert.Equal("z", groupList.Groups[2].Name)
|
|
||||||
assert.Equal("a", groupList.Groups[3].Name)
|
|
||||||
assert.Equal("b", groupList.Groups[4].Name)
|
|
||||||
assert.Equal("c", groupList.Groups[5].Name)
|
|
||||||
|
|
||||||
// Remove a group.
|
|
||||||
master.RemoveAPIGroupForDiscovery("a")
|
|
||||||
groupList, err = getGroupList(server)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
assert.Equal(5, len(groupList.Groups))
|
|
||||||
|
|
||||||
// Re-adding should move to the end.
|
|
||||||
master.AddAPIGroupForDiscovery(metav1.APIGroup{Name: "a"})
|
|
||||||
groupList, err = getGroupList(server)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
assert.Equal(6, len(groupList.Groups))
|
|
||||||
assert.Equal("x", groupList.Groups[0].Name)
|
|
||||||
assert.Equal("y", groupList.Groups[1].Name)
|
|
||||||
assert.Equal("z", groupList.Groups[2].Name)
|
|
||||||
assert.Equal("b", groupList.Groups[3].Name)
|
|
||||||
assert.Equal("c", groupList.Groups[4].Name)
|
|
||||||
assert.Equal("a", groupList.Groups[5].Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetServerAddressByClientCIDRs(t *testing.T) {
|
|
||||||
publicAddressCIDRMap := []metav1.ServerAddressByClientCIDR{
|
|
||||||
{
|
|
||||||
ClientCIDR: "0.0.0.0/0",
|
|
||||||
ServerAddress: "ExternalAddress",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
internalAddressCIDRMap := []metav1.ServerAddressByClientCIDR{
|
|
||||||
publicAddressCIDRMap[0],
|
|
||||||
{
|
|
||||||
ClientCIDR: "10.0.0.0/24",
|
|
||||||
ServerAddress: "serviceIP",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
internalIP := "10.0.0.1"
|
|
||||||
publicIP := "1.1.1.1"
|
|
||||||
testCases := []struct {
|
|
||||||
Request http.Request
|
|
||||||
ExpectedMap []metav1.ServerAddressByClientCIDR
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
Request: http.Request{},
|
|
||||||
ExpectedMap: publicAddressCIDRMap,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Request: http.Request{
|
|
||||||
Header: map[string][]string{
|
|
||||||
"X-Real-Ip": {internalIP},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ExpectedMap: internalAddressCIDRMap,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Request: http.Request{
|
|
||||||
Header: map[string][]string{
|
|
||||||
"X-Real-Ip": {publicIP},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ExpectedMap: publicAddressCIDRMap,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Request: http.Request{
|
|
||||||
Header: map[string][]string{
|
|
||||||
"X-Forwarded-For": {internalIP},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ExpectedMap: internalAddressCIDRMap,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Request: http.Request{
|
|
||||||
Header: map[string][]string{
|
|
||||||
"X-Forwarded-For": {publicIP},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ExpectedMap: publicAddressCIDRMap,
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
Request: http.Request{
|
|
||||||
RemoteAddr: internalIP,
|
|
||||||
},
|
|
||||||
ExpectedMap: internalAddressCIDRMap,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Request: http.Request{
|
|
||||||
RemoteAddr: publicIP,
|
|
||||||
},
|
|
||||||
ExpectedMap: publicAddressCIDRMap,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Request: http.Request{
|
|
||||||
RemoteAddr: "invalidIP",
|
|
||||||
},
|
|
||||||
ExpectedMap: publicAddressCIDRMap,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
_, ipRange, _ := net.ParseCIDR("10.0.0.0/24")
|
|
||||||
discoveryAddresses := DefaultDiscoveryAddresses{DefaultAddress: "ExternalAddress"}
|
|
||||||
discoveryAddresses.DiscoveryCIDRRules = append(discoveryAddresses.DiscoveryCIDRRules,
|
|
||||||
DiscoveryCIDRRule{IPRange: *ipRange, Address: "serviceIP"})
|
|
||||||
|
|
||||||
for i, test := range testCases {
|
|
||||||
if a, e := discoveryAddresses.ServerAddressByClientCIDRs(utilnet.GetClientIP(&test.Request)), test.ExpectedMap; reflect.DeepEqual(e, a) != true {
|
|
||||||
t.Fatalf("test case %d failed. expected: %v, actual: %v", i+1, e, a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type testGetterStorage struct {
|
type testGetterStorage struct {
|
||||||
Version string
|
Version string
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue