Merge pull request #13955 from caesarxuchao/API-discovery
Auto commit by PR queue bot
This commit is contained in:
commit
6c30a0e170
@ -9,6 +9,10 @@
|
||||
"path": "/api",
|
||||
"description": "get available API versions"
|
||||
},
|
||||
{
|
||||
"path": "/apis",
|
||||
"description": "get available API versions"
|
||||
},
|
||||
{
|
||||
"path": "/version",
|
||||
"description": "git code version from which this is built"
|
||||
|
@ -10950,6 +10950,25 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/api/v1",
|
||||
"description": "API at /api/v1 version v1",
|
||||
"operations": [
|
||||
{
|
||||
"type": "void",
|
||||
"method": "GET",
|
||||
"summary": "get available resources",
|
||||
"nickname": "getAPIResources",
|
||||
"parameters": [],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"consumes": [
|
||||
"application/json"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"models": {
|
||||
|
@ -132,11 +132,14 @@ func startComponents(firstManifestURL, secondManifestURL string) (string, string
|
||||
// We will fix this by supporting multiple group versions in Config
|
||||
cl.ExperimentalClient = client.NewExperimentalOrDie(&client.Config{Host: apiServer.URL, Version: testapi.Experimental.Version()})
|
||||
|
||||
storageVersions := make(map[string]string)
|
||||
etcdStorage, err := master.NewEtcdStorage(etcdClient, latest.GroupOrDie("").InterfacesFor, testapi.Default.Version(), etcdtest.PathPrefix())
|
||||
storageVersions[""] = testapi.Default.Version()
|
||||
if err != nil {
|
||||
glog.Fatalf("Unable to get etcd storage: %v", err)
|
||||
}
|
||||
expEtcdStorage, err := master.NewEtcdStorage(etcdClient, latest.GroupOrDie("experimental").InterfacesFor, testapi.Experimental.Version(), etcdtest.PathPrefix())
|
||||
storageVersions["experimental"] = testapi.Experimental.Version()
|
||||
if err != nil {
|
||||
glog.Fatalf("Unable to get etcd storage for experimental: %v", err)
|
||||
}
|
||||
@ -171,6 +174,7 @@ func startComponents(firstManifestURL, secondManifestURL string) (string, string
|
||||
ReadWritePort: portNumber,
|
||||
PublicAddress: publicAddress,
|
||||
CacheTimeout: 2 * time.Second,
|
||||
StorageVersions: storageVersions,
|
||||
})
|
||||
handler.delegate = m.Handler
|
||||
|
||||
|
@ -21,6 +21,7 @@ package app
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
@ -245,7 +246,10 @@ func (s *APIServer) verifyClusterIPFlags() {
|
||||
}
|
||||
}
|
||||
|
||||
func newEtcd(etcdConfigFile string, etcdServerList []string, interfacesFunc meta.VersionInterfacesFunc, defaultVersion, storageVersion, pathPrefix string) (etcdStorage storage.Interface, err error) {
|
||||
func newEtcd(etcdConfigFile string, etcdServerList []string, interfacesFunc meta.VersionInterfacesFunc, storageVersion, pathPrefix string) (etcdStorage storage.Interface, err error) {
|
||||
if storageVersion == "" {
|
||||
return etcdStorage, fmt.Errorf("storageVersion is required to create a etcd storage")
|
||||
}
|
||||
var client tools.EtcdClient
|
||||
if etcdConfigFile != "" {
|
||||
client, err = etcd.NewClientFromFile(etcdConfigFile)
|
||||
@ -264,11 +268,8 @@ func newEtcd(etcdConfigFile string, etcdServerList []string, interfacesFunc meta
|
||||
etcdClient.SetTransport(transport)
|
||||
client = etcdClient
|
||||
}
|
||||
|
||||
if storageVersion == "" {
|
||||
storageVersion = defaultVersion
|
||||
}
|
||||
return master.NewEtcdStorage(client, interfacesFunc, storageVersion, pathPrefix)
|
||||
etcdStorage, err = master.NewEtcdStorage(client, interfacesFunc, storageVersion, pathPrefix)
|
||||
return etcdStorage, err
|
||||
}
|
||||
|
||||
// Run runs the specified APIServer. This should never exit.
|
||||
@ -341,7 +342,16 @@ func (s *APIServer) Run(_ []string) error {
|
||||
glog.Fatalf("Invalid server address: %v", err)
|
||||
}
|
||||
|
||||
etcdStorage, err := newEtcd(s.EtcdConfigFile, s.EtcdServerList, latest.GroupOrDie("").InterfacesFor, latest.GroupOrDie("").Version, s.StorageVersion, s.EtcdPathPrefix)
|
||||
g, err := latest.Group("")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
storageVersions := make(map[string]string)
|
||||
if s.StorageVersion == "" {
|
||||
s.StorageVersion = g.Version
|
||||
}
|
||||
etcdStorage, err := newEtcd(s.EtcdConfigFile, s.EtcdServerList, g.InterfacesFor, s.StorageVersion, s.EtcdPathPrefix)
|
||||
storageVersions[""] = s.StorageVersion
|
||||
if err != nil {
|
||||
glog.Fatalf("Invalid storage version or misconfigured etcd: %v", err)
|
||||
}
|
||||
@ -352,10 +362,14 @@ func (s *APIServer) Run(_ []string) error {
|
||||
if err != nil {
|
||||
glog.Fatalf("experimental API is enabled in runtime config, but not enabled in the environment variable KUBE_API_VERSIONS. Error: %v", err)
|
||||
}
|
||||
expEtcdStorage, err = newEtcd(s.EtcdConfigFile, s.EtcdServerList, g.InterfacesFor, g.Version, s.ExpStorageVersion, s.EtcdPathPrefix)
|
||||
if s.ExpStorageVersion == "" {
|
||||
s.ExpStorageVersion = g.Version
|
||||
}
|
||||
expEtcdStorage, err = newEtcd(s.EtcdConfigFile, s.EtcdServerList, g.InterfacesFor, s.ExpStorageVersion, s.EtcdPathPrefix)
|
||||
if err != nil {
|
||||
glog.Fatalf("Invalid experimental storage version or misconfigured etcd: %v", err)
|
||||
}
|
||||
storageVersions["experimental"] = s.StorageVersion
|
||||
}
|
||||
|
||||
n := s.ServiceClusterIPRange
|
||||
@ -427,6 +441,7 @@ func (s *APIServer) Run(_ []string) error {
|
||||
config := &master.Config{
|
||||
DatabaseStorage: etcdStorage,
|
||||
ExpDatabaseStorage: expEtcdStorage,
|
||||
StorageVersions: storageVersions,
|
||||
|
||||
EventTTL: s.EventTTL,
|
||||
KubeletClient: kubeletClient,
|
||||
|
@ -22,16 +22,64 @@ import (
|
||||
|
||||
// This file contains API types that are unversioned.
|
||||
|
||||
// APIVersions lists the api versions that are available, to allow
|
||||
// version negotiation. APIVersions isn't just an unnamed array of
|
||||
// strings in order to allow for future evolution, though unversioned
|
||||
// APIVersions lists the versions that are available, to allow clients to
|
||||
// discover the API at /api, which is the root path of the legacy v1 API.
|
||||
type APIVersions struct {
|
||||
// versions are the api versions that are available.
|
||||
Versions []string `json:"versions"`
|
||||
}
|
||||
|
||||
// APIGroupList is a list of APIGroup, to allow clients to discover the API at
|
||||
// /apis.
|
||||
type APIGroupList struct {
|
||||
// groups is a list of APIGroup.
|
||||
Groups []APIGroup `json:"groups"`
|
||||
}
|
||||
|
||||
// APIGroup contains the name, the supported versions, and the preferred version
|
||||
// of a group.
|
||||
type APIGroup struct {
|
||||
// name is the name of the group.
|
||||
Name string `json:"name"`
|
||||
// versions are the versions supported in this group.
|
||||
Versions []GroupVersion `json:"versions"`
|
||||
// preferredVersion is the version preferred by the API server, which
|
||||
// probably is the storage version.
|
||||
PreferredVersion GroupVersion `json:"preferredVersion,omitempty"`
|
||||
}
|
||||
|
||||
// GroupVersion contains the "group/version" and "version" string of a version.
|
||||
// It is made a struct to keep extensiblity.
|
||||
type GroupVersion struct {
|
||||
// groupVersion specifies the API group and version in the form "group/version"
|
||||
GroupVersion string `json:"groupVersion"`
|
||||
// version specifies the version in the form of "version". This is to save
|
||||
// the clients the trouble of splitting the GroupVersion.
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// APIResource specifies the name of a resource and whether it is namespaced.
|
||||
type APIResource struct {
|
||||
// name is the name of the resource.
|
||||
Name string `json:"name"`
|
||||
// namespaced indicates if a resource is namespaced or not.
|
||||
Namespaced bool `json:"namespaced"`
|
||||
}
|
||||
|
||||
// APIResourceList is a list of APIResource, it is used to expose the name of the
|
||||
// resources supported in a specific group and version, and if the resource
|
||||
// is namespaced.
|
||||
type APIResourceList struct {
|
||||
// groupVersion is the group and version this APIResourceList is for.
|
||||
GroupVersion string `json:"groupVersion"`
|
||||
// resources contains the name of the resources and if they are namespaced.
|
||||
APIResources []APIResource `json:"resources"`
|
||||
}
|
||||
|
||||
// RootPaths lists the paths available at root.
|
||||
// For example: "/healthz", "/api".
|
||||
// For example: "/healthz", "/apis".
|
||||
type RootPaths struct {
|
||||
// paths are the paths available at root.
|
||||
Paths []string `json:"paths"`
|
||||
}
|
||||
|
||||
|
@ -168,6 +168,7 @@ type ThirdPartyResourceList struct {
|
||||
}
|
||||
|
||||
// An APIVersion represents a single concrete version of an object model.
|
||||
// TODO: we should consider merge this struct with GroupVersion in unversioned.go
|
||||
type APIVersion struct {
|
||||
// Name of this version (e.g. 'v1').
|
||||
Name string `json:"name,omitempty"`
|
||||
|
@ -61,8 +61,8 @@ type documentable interface {
|
||||
var errEmptyName = errors.NewBadRequest("name must be provided")
|
||||
|
||||
// Installs handlers for API resources.
|
||||
func (a *APIInstaller) Install(ws *restful.WebService) []error {
|
||||
errors := make([]error, 0)
|
||||
func (a *APIInstaller) Install(ws *restful.WebService) (apiResources []api.APIResource, errors []error) {
|
||||
errors = make([]error, 0)
|
||||
|
||||
proxyHandler := (&ProxyHandler{a.prefix + "/proxy/", a.group.Storage, a.group.Codec, a.group.Context, a.info, a.proxyDialerFn})
|
||||
|
||||
@ -75,11 +75,15 @@ func (a *APIInstaller) Install(ws *restful.WebService) []error {
|
||||
}
|
||||
sort.Strings(paths)
|
||||
for _, path := range paths {
|
||||
if err := a.registerResourceHandlers(path, a.group.Storage[path], ws, proxyHandler); err != nil {
|
||||
apiResource, err := a.registerResourceHandlers(path, a.group.Storage[path], ws, proxyHandler)
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
if apiResource != nil {
|
||||
apiResources = append(apiResources, *apiResource)
|
||||
}
|
||||
return errors
|
||||
}
|
||||
return apiResources, errors
|
||||
}
|
||||
|
||||
// NewWebService creates a new restful webservice with the api installer's prefix and version.
|
||||
@ -95,7 +99,7 @@ func (a *APIInstaller) NewWebService() *restful.WebService {
|
||||
return ws
|
||||
}
|
||||
|
||||
func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storage, ws *restful.WebService, proxyHandler http.Handler) error {
|
||||
func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storage, ws *restful.WebService, proxyHandler http.Handler) (*api.APIResource, error) {
|
||||
admit := a.group.Admit
|
||||
context := a.group.Context
|
||||
|
||||
@ -112,40 +116,40 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
resource = parts[0]
|
||||
default:
|
||||
// TODO: support deeper paths
|
||||
return fmt.Errorf("api_installer allows only one or two segment paths (resource or resource/subresource)")
|
||||
return nil, fmt.Errorf("api_installer allows only one or two segment paths (resource or resource/subresource)")
|
||||
}
|
||||
hasSubresource := len(subresource) > 0
|
||||
|
||||
object := storage.New()
|
||||
_, kind, err := a.group.Typer.ObjectVersionAndKind(object)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
versionedPtr, err := a.group.Creater.New(a.group.Version, kind)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
versionedObject := indirectArbitraryPointer(versionedPtr)
|
||||
|
||||
mapping, err := a.group.Mapper.RESTMapping(kind, a.group.Version)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// subresources must have parent resources, and follow the namespacing rules of their parent
|
||||
if hasSubresource {
|
||||
parentStorage, ok := a.group.Storage[resource]
|
||||
if !ok {
|
||||
return fmt.Errorf("subresources can only be declared when the parent is also registered: %s needs %s", path, resource)
|
||||
return nil, fmt.Errorf("subresources can only be declared when the parent is also registered: %s needs %s", path, resource)
|
||||
}
|
||||
parentObject := parentStorage.New()
|
||||
_, parentKind, err := a.group.Typer.ObjectVersionAndKind(parentObject)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
parentMapping, err := a.group.Mapper.RESTMapping(parentKind, a.group.Version)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
mapping.Scope = parentMapping.Scope
|
||||
}
|
||||
@ -178,14 +182,14 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
_, listKind, err := a.group.Typer.ObjectVersionAndKind(list)
|
||||
versionedListPtr, err := a.group.Creater.New(a.group.Version, listKind)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
versionedList = indirectArbitraryPointer(versionedListPtr)
|
||||
}
|
||||
|
||||
versionedListOptions, err := a.group.Creater.New(serverVersion, "ListOptions")
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var versionedDeleterObject interface{}
|
||||
@ -193,7 +197,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
case isGracefulDeleter:
|
||||
objectPtr, err := a.group.Creater.New(serverVersion, "DeleteOptions")
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
versionedDeleterObject = indirectArbitraryPointer(objectPtr)
|
||||
isDeleter = true
|
||||
@ -203,7 +207,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
|
||||
versionedStatusPtr, err := a.group.Creater.New(serverVersion, "Status")
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
versionedStatus := indirectArbitraryPointer(versionedStatusPtr)
|
||||
var (
|
||||
@ -217,11 +221,11 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
getOptions, getSubpath, getSubpathKey = getterWithOptions.NewGetOptions()
|
||||
_, getOptionsKind, err = a.group.Typer.ObjectVersionAndKind(getOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
versionedGetOptions, err = a.group.Creater.New(serverVersion, getOptionsKind)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
isGetter = true
|
||||
}
|
||||
@ -238,7 +242,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
if connectOptions != nil {
|
||||
_, connectOptionsKind, err = a.group.Typer.ObjectVersionAndKind(connectOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
versionedConnectOptions, err = a.group.Creater.New(serverVersion, connectOptionsKind)
|
||||
}
|
||||
@ -262,6 +266,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
params := []*restful.Parameter{}
|
||||
actions := []action{}
|
||||
|
||||
var apiResource api.APIResource
|
||||
// Get the list of actions for the given scope.
|
||||
switch scope.Name() {
|
||||
case meta.RESTScopeNameRoot:
|
||||
@ -276,6 +281,8 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
resourcePath = itemPath
|
||||
resourceParams = nameParams
|
||||
}
|
||||
apiResource.Name = path
|
||||
apiResource.Namespaced = false
|
||||
namer := rootScopeNaming{scope, a.group.Linker, gpath.Join(a.prefix, itemPath)}
|
||||
|
||||
// Handler for standard REST verbs (GET, PUT, POST and DELETE).
|
||||
@ -314,6 +321,8 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
resourcePath = itemPath
|
||||
resourceParams = nameParams
|
||||
}
|
||||
apiResource.Name = path
|
||||
apiResource.Namespaced = true
|
||||
namer := scopeNaming{scope, a.group.Linker, gpath.Join(a.prefix, itemPath), false}
|
||||
|
||||
actions = appendIf(actions, action{"LIST", resourcePath, resourceParams, namer}, isLister)
|
||||
@ -344,7 +353,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
}
|
||||
break
|
||||
default:
|
||||
return fmt.Errorf("unsupported restscope: %s", scope.Name())
|
||||
return nil, fmt.Errorf("unsupported restscope: %s", scope.Name())
|
||||
}
|
||||
|
||||
// Create Routes for the actions.
|
||||
@ -404,7 +413,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
Writes(versionedObject)
|
||||
if isGetterWithOptions {
|
||||
if err := addObjectParams(ws, route, versionedGetOptions); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
addParams(route, action.Params)
|
||||
@ -423,7 +432,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
Returns(http.StatusOK, "OK", versionedList).
|
||||
Writes(versionedList)
|
||||
if err := addObjectParams(ws, route, versionedListOptions); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
switch {
|
||||
case isLister && isWatcher:
|
||||
@ -529,7 +538,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
Returns(http.StatusOK, "OK", watchjson.WatchEvent{}).
|
||||
Writes(watchjson.WatchEvent{})
|
||||
if err := addObjectParams(ws, route, versionedListOptions); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
addParams(route, action.Params)
|
||||
ws.Route(route)
|
||||
@ -548,7 +557,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
Returns(http.StatusOK, "OK", watchjson.WatchEvent{}).
|
||||
Writes(watchjson.WatchEvent{})
|
||||
if err := addObjectParams(ws, route, versionedListOptions); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
addParams(route, action.Params)
|
||||
ws.Route(route)
|
||||
@ -576,18 +585,18 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
Writes("string")
|
||||
if versionedConnectOptions != nil {
|
||||
if err := addObjectParams(ws, route, versionedConnectOptions); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
addParams(route, action.Params)
|
||||
ws.Route(route)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unrecognized action verb: %s", action.Verb)
|
||||
return nil, fmt.Errorf("unrecognized action verb: %s", action.Verb)
|
||||
}
|
||||
// Note: update GetAttribs() when adding a custom handler.
|
||||
}
|
||||
return nil
|
||||
return &apiResource, nil
|
||||
}
|
||||
|
||||
// rootScopeNaming reads only names from a request and ignores namespaces. It implements ScopeNamer
|
||||
|
@ -118,7 +118,9 @@ const (
|
||||
func (g *APIGroupVersion) InstallREST(container *restful.Container) error {
|
||||
installer := g.newInstaller()
|
||||
ws := installer.NewWebService()
|
||||
registrationErrors := installer.Install(ws)
|
||||
apiResources, registrationErrors := installer.Install(ws)
|
||||
// TODO: g.Version only contains "version" now, it will contain "group/version" in the near future.
|
||||
AddSupportedResourcesWebService(ws, g.Version, apiResources)
|
||||
container.Add(ws)
|
||||
return errors.NewAggregate(registrationErrors)
|
||||
}
|
||||
@ -141,8 +143,10 @@ func (g *APIGroupVersion) UpdateREST(container *restful.Container) error {
|
||||
if ws == nil {
|
||||
return apierrors.NewInternalError(fmt.Errorf("unable to find an existing webservice for prefix %s", installer.prefix))
|
||||
}
|
||||
|
||||
return errors.NewAggregate(installer.Install(ws))
|
||||
apiResources, registrationErrors := installer.Install(ws)
|
||||
// TODO: g.Version only contains "version" now, it will contain "group/version" in the near future.
|
||||
AddSupportedResourcesWebService(ws, g.Version, apiResources)
|
||||
return errors.NewAggregate(registrationErrors)
|
||||
}
|
||||
|
||||
// newInstaller is a helper to create the installer. Used by InstallREST and UpdateREST.
|
||||
@ -232,7 +236,7 @@ func serviceErrorHandler(requestResolver *APIRequestInfoResolver, apiVersions []
|
||||
errorJSON(apierrors.NewGenericServerResponse(serviceErr.Code, "", "", "", "", 0, false), codec, response.ResponseWriter)
|
||||
}
|
||||
|
||||
// Adds a service to return the supported api versions.
|
||||
// Adds a service to return the supported api versions at the legacy /api.
|
||||
func AddApiWebService(container *restful.Container, apiPrefix string, versions []string) {
|
||||
// TODO: InstallREST should register each version automatically
|
||||
|
||||
@ -248,6 +252,46 @@ func AddApiWebService(container *restful.Container, apiPrefix string, versions [
|
||||
container.Add(ws)
|
||||
}
|
||||
|
||||
// Adds a service to return the supported api versions at /apis.
|
||||
func AddApisWebService(container *restful.Container, apiPrefix string, groups []api.APIGroup) {
|
||||
rootAPIHandler := RootAPIHandler(groups)
|
||||
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(restful.MIME_JSON).
|
||||
Consumes(restful.MIME_JSON))
|
||||
container.Add(ws)
|
||||
}
|
||||
|
||||
// Adds a service to return the supported versions, preferred version, and name
|
||||
// of a group. E.g., a such web service will be registered at /apis/experimental.
|
||||
func AddGroupWebService(container *restful.Container, path string, group api.APIGroup) {
|
||||
groupHandler := GroupHandler(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(restful.MIME_JSON).
|
||||
Consumes(restful.MIME_JSON))
|
||||
container.Add(ws)
|
||||
}
|
||||
|
||||
// Adds a service to return the supported resources, E.g., a such web service
|
||||
// will be registered at /apis/experimental/v1.
|
||||
func AddSupportedResourcesWebService(ws *restful.WebService, groupVersion string, apiResources []api.APIResource) {
|
||||
resourceHandler := SupportedResourcesHandler(groupVersion, apiResources)
|
||||
ws.Route(ws.GET("/").To(resourceHandler).
|
||||
Doc("get available resources").
|
||||
Operation("getAPIResources").
|
||||
Produces(restful.MIME_JSON).
|
||||
Consumes(restful.MIME_JSON))
|
||||
}
|
||||
|
||||
// handleVersion writes the server's version information.
|
||||
func handleVersion(req *restful.Request, resp *restful.Response) {
|
||||
// TODO: use restful's Response methods
|
||||
@ -262,6 +306,31 @@ func APIVersionHandler(versions ...string) restful.RouteFunction {
|
||||
}
|
||||
}
|
||||
|
||||
// RootAPIHandler returns a handler which will list the provided groups and versions as available.
|
||||
func RootAPIHandler(groups []api.APIGroup) restful.RouteFunction {
|
||||
return func(req *restful.Request, resp *restful.Response) {
|
||||
// TODO: use restful's Response methods
|
||||
writeRawJSON(http.StatusOK, api.APIGroupList{Groups: groups}, resp.ResponseWriter)
|
||||
}
|
||||
}
|
||||
|
||||
// GroupHandler returns a handler which will return the api.GroupAndVersion of
|
||||
// the group.
|
||||
func GroupHandler(group api.APIGroup) restful.RouteFunction {
|
||||
return func(req *restful.Request, resp *restful.Response) {
|
||||
// TODO: use restful's Response methods
|
||||
writeRawJSON(http.StatusOK, group, resp.ResponseWriter)
|
||||
}
|
||||
}
|
||||
|
||||
// SupportedResourcesHandler returns a handler which will list the provided resources as available.
|
||||
func SupportedResourcesHandler(groupVersion string, apiResources []api.APIResource) restful.RouteFunction {
|
||||
return func(req *restful.Request, resp *restful.Response) {
|
||||
// TODO: use restful's Response methods
|
||||
writeRawJSON(http.StatusOK, api.APIResourceList{GroupVersion: groupVersion, APIResources: apiResources}, resp.ResponseWriter)
|
||||
}
|
||||
}
|
||||
|
||||
// write renders a returned runtime.Object to the response as a stream or an encoded object. If the object
|
||||
// returned by the response implements rest.ResourceStreamer that interface will be used to render the
|
||||
// response. The Accept header and current API version will be passed in, and the output will be copied
|
||||
|
@ -100,6 +100,8 @@ const (
|
||||
type Config struct {
|
||||
DatabaseStorage storage.Interface
|
||||
ExpDatabaseStorage storage.Interface
|
||||
// StorageVersions is a map between groups and their storage versions
|
||||
StorageVersions map[string]string
|
||||
EventTTL time.Duration
|
||||
NodeRegexp string
|
||||
KubeletClient client.KubeletClient
|
||||
@ -570,16 +572,42 @@ func (m *Master) init(c *Config) {
|
||||
requestInfoResolver := &apiserver.APIRequestInfoResolver{APIPrefixes: sets.NewString(strings.TrimPrefix(defaultVersion.Root, "/")), RestMapper: defaultVersion.Mapper}
|
||||
apiserver.InstallServiceErrorHandler(m.handlerContainer, requestInfoResolver, apiVersions)
|
||||
|
||||
// allGroups records all supported groups at /apis
|
||||
allGroups := []api.APIGroup{}
|
||||
if m.exp {
|
||||
expVersion := m.experimental(c)
|
||||
if err := expVersion.InstallREST(m.handlerContainer); err != nil {
|
||||
glog.Fatalf("Unable to setup experimental api: %v", err)
|
||||
}
|
||||
apiserver.AddApiWebService(m.handlerContainer, c.APIGroupPrefix+"/"+latest.GroupOrDie("experimental").Group+"/", []string{expVersion.Version})
|
||||
g, err := latest.Group("experimental")
|
||||
if err != nil {
|
||||
glog.Fatalf("Unable to setup experimental api: %v", err)
|
||||
}
|
||||
expAPIVersions := []api.GroupVersion{
|
||||
{
|
||||
GroupVersion: g.Group + "/" + expVersion.Version,
|
||||
Version: expVersion.Version,
|
||||
},
|
||||
}
|
||||
storageVersion, found := c.StorageVersions[g.Group]
|
||||
if !found {
|
||||
glog.Fatalf("Couldn't find storage version of group %v", g.Group)
|
||||
}
|
||||
group := api.APIGroup{
|
||||
Name: g.Group,
|
||||
Versions: expAPIVersions,
|
||||
PreferredVersion: api.GroupVersion{GroupVersion: g.Group + "/" + storageVersion, Version: storageVersion},
|
||||
}
|
||||
apiserver.AddGroupWebService(m.handlerContainer, c.APIGroupPrefix+"/"+latest.GroupOrDie("experimental").Group+"/", group)
|
||||
allGroups = append(allGroups, group)
|
||||
expRequestInfoResolver := &apiserver.APIRequestInfoResolver{APIPrefixes: sets.NewString(strings.TrimPrefix(expVersion.Root, "/")), RestMapper: expVersion.Mapper}
|
||||
apiserver.InstallServiceErrorHandler(m.handlerContainer, expRequestInfoResolver, []string{expVersion.Version})
|
||||
}
|
||||
|
||||
// This should be done after all groups are registered
|
||||
// TODO: replace the hardcoded "apis".
|
||||
apiserver.AddApisWebService(m.handlerContainer, "/apis", allGroups)
|
||||
|
||||
// Register root handler.
|
||||
// We do not register this using restful Webservice since we do not want to surface this in api docs.
|
||||
// Allow master to be embedded in contexts which already have something registered at the root
|
||||
@ -784,7 +812,15 @@ func (m *Master) InstallThirdPartyAPI(rsrc *experimental.ThirdPartyResource) err
|
||||
glog.Fatalf("Unable to setup thirdparty api: %v", err)
|
||||
}
|
||||
thirdPartyAPIPrefix := makeThirdPartyPath(group) + "/"
|
||||
apiserver.AddApiWebService(m.handlerContainer, thirdPartyAPIPrefix, []string{rsrc.Versions[0].Name})
|
||||
groupVersion := api.GroupVersion{
|
||||
GroupVersion: group + "/" + rsrc.Versions[0].Name,
|
||||
Version: rsrc.Versions[0].Name,
|
||||
}
|
||||
apiGroup := api.APIGroup{
|
||||
Name: group,
|
||||
Versions: []api.GroupVersion{groupVersion},
|
||||
}
|
||||
apiserver.AddGroupWebService(m.handlerContainer, thirdPartyAPIPrefix, apiGroup)
|
||||
thirdPartyRequestInfoResolver := &apiserver.APIRequestInfoResolver{APIPrefixes: sets.NewString(strings.TrimPrefix(group, "/")), RestMapper: thirdparty.Mapper}
|
||||
apiserver.InstallServiceErrorHandler(m.handlerContainer, thirdPartyRequestInfoResolver, []string{thirdparty.Version})
|
||||
return nil
|
||||
|
@ -59,9 +59,12 @@ func setUp(t *testing.T) (Master, Config, *assert.Assertions) {
|
||||
config := Config{}
|
||||
fakeClient := tools.NewFakeEtcdClient(t)
|
||||
fakeClient.Machines = []string{"http://machine1:4001", "http://machine2", "http://machine3:4003"}
|
||||
storageVersions := make(map[string]string)
|
||||
config.DatabaseStorage = etcdstorage.NewEtcdStorage(fakeClient, testapi.Default.Codec(), etcdtest.PathPrefix())
|
||||
storageVersions[""] = testapi.Default.Version()
|
||||
config.ExpDatabaseStorage = etcdstorage.NewEtcdStorage(fakeClient, testapi.Experimental.Codec(), etcdtest.PathPrefix())
|
||||
|
||||
storageVersions["experimental"] = testapi.Experimental.Version()
|
||||
config.StorageVersions = storageVersions
|
||||
master.nodeRegistry = registrytest.NewNodeRegistry([]string{"node1", "node2"}, api.NodeResources{})
|
||||
|
||||
return master, config, assert.New(t)
|
||||
|
@ -406,6 +406,7 @@ func TestAuthModeAlwaysAllow(t *testing.T) {
|
||||
APIPrefix: "/api",
|
||||
Authorizer: apiserver.NewAlwaysAllowAuthorizer(),
|
||||
AdmissionControl: admit.NewAlwaysAdmit(),
|
||||
StorageVersions: map[string]string{"": testapi.Default.Version()},
|
||||
})
|
||||
|
||||
transport := http.DefaultTransport
|
||||
@ -522,6 +523,7 @@ func TestAuthModeAlwaysDeny(t *testing.T) {
|
||||
APIPrefix: "/api",
|
||||
Authorizer: apiserver.NewAlwaysDenyAuthorizer(),
|
||||
AdmissionControl: admit.NewAlwaysAdmit(),
|
||||
StorageVersions: map[string]string{"": testapi.Default.Version()},
|
||||
})
|
||||
|
||||
transport := http.DefaultTransport
|
||||
@ -590,6 +592,7 @@ func TestAliceNotForbiddenOrUnauthorized(t *testing.T) {
|
||||
Authenticator: getTestTokenAuth(),
|
||||
Authorizer: allowAliceAuthorizer{},
|
||||
AdmissionControl: admit.NewAlwaysAdmit(),
|
||||
StorageVersions: map[string]string{"": testapi.Default.Version()},
|
||||
})
|
||||
|
||||
previousResourceVersion := make(map[string]float64)
|
||||
@ -677,6 +680,7 @@ func TestBobIsForbidden(t *testing.T) {
|
||||
Authenticator: getTestTokenAuth(),
|
||||
Authorizer: allowAliceAuthorizer{},
|
||||
AdmissionControl: admit.NewAlwaysAdmit(),
|
||||
StorageVersions: map[string]string{"": testapi.Default.Version()},
|
||||
})
|
||||
|
||||
transport := http.DefaultTransport
|
||||
@ -738,6 +742,7 @@ func TestUnknownUserIsUnauthorized(t *testing.T) {
|
||||
Authenticator: getTestTokenAuth(),
|
||||
Authorizer: allowAliceAuthorizer{},
|
||||
AdmissionControl: admit.NewAlwaysAdmit(),
|
||||
StorageVersions: map[string]string{"": testapi.Default.Version()},
|
||||
})
|
||||
|
||||
transport := http.DefaultTransport
|
||||
@ -818,6 +823,7 @@ func TestNamespaceAuthorization(t *testing.T) {
|
||||
Authenticator: getTestTokenAuth(),
|
||||
Authorizer: a,
|
||||
AdmissionControl: admit.NewAlwaysAdmit(),
|
||||
StorageVersions: map[string]string{"": testapi.Default.Version()},
|
||||
})
|
||||
|
||||
previousResourceVersion := make(map[string]float64)
|
||||
@ -933,6 +939,7 @@ func TestKindAuthorization(t *testing.T) {
|
||||
Authenticator: getTestTokenAuth(),
|
||||
Authorizer: a,
|
||||
AdmissionControl: admit.NewAlwaysAdmit(),
|
||||
StorageVersions: map[string]string{"": testapi.Default.Version()},
|
||||
})
|
||||
|
||||
previousResourceVersion := make(map[string]float64)
|
||||
@ -1035,6 +1042,7 @@ func TestReadOnlyAuthorization(t *testing.T) {
|
||||
Authenticator: getTestTokenAuth(),
|
||||
Authorizer: a,
|
||||
AdmissionControl: admit.NewAlwaysAdmit(),
|
||||
StorageVersions: map[string]string{"": testapi.Default.Version()},
|
||||
})
|
||||
|
||||
transport := http.DefaultTransport
|
||||
|
@ -130,11 +130,14 @@ func startMasterOrDie(masterConfig *master.Config) (*master.Master, *httptest.Se
|
||||
var err error
|
||||
if masterConfig == nil {
|
||||
etcdClient := NewEtcdClient()
|
||||
storageVersions := make(map[string]string)
|
||||
etcdStorage, err = master.NewEtcdStorage(etcdClient, latest.GroupOrDie("").InterfacesFor, latest.GroupOrDie("").Version, etcdtest.PathPrefix())
|
||||
storageVersions[""] = latest.GroupOrDie("").Version
|
||||
if err != nil {
|
||||
glog.Fatalf("Failed to create etcd storage for master %v", err)
|
||||
}
|
||||
expEtcdStorage, err := master.NewEtcdStorage(etcdClient, latest.GroupOrDie("experimental").InterfacesFor, latest.GroupOrDie("experimental").Version, etcdtest.PathPrefix())
|
||||
storageVersions["experimental"] = latest.GroupOrDie("experimental").Version
|
||||
if err != nil {
|
||||
glog.Fatalf("Failed to create etcd storage for master %v", err)
|
||||
}
|
||||
@ -142,6 +145,7 @@ func startMasterOrDie(masterConfig *master.Config) (*master.Master, *httptest.Se
|
||||
masterConfig = &master.Config{
|
||||
DatabaseStorage: etcdStorage,
|
||||
ExpDatabaseStorage: expEtcdStorage,
|
||||
StorageVersions: storageVersions,
|
||||
KubeletClient: client.FakeKubeletClient{},
|
||||
EnableExp: true,
|
||||
EnableLogsSupport: false,
|
||||
@ -270,11 +274,14 @@ func StartPods(numPods int, host string, restClient *client.Client) error {
|
||||
// TODO: Merge this into startMasterOrDie.
|
||||
func RunAMaster(t *testing.T) (*master.Master, *httptest.Server) {
|
||||
etcdClient := NewEtcdClient()
|
||||
storageVersions := make(map[string]string)
|
||||
etcdStorage, err := master.NewEtcdStorage(etcdClient, latest.GroupOrDie("").InterfacesFor, testapi.Default.Version(), etcdtest.PathPrefix())
|
||||
storageVersions[""] = testapi.Default.Version()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
expEtcdStorage, err := master.NewEtcdStorage(etcdClient, latest.GroupOrDie("experimental").InterfacesFor, latest.GroupOrDie("experimental").Version, etcdtest.PathPrefix())
|
||||
storageVersions["experimental"] = testapi.Experimental.Version()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
@ -291,6 +298,7 @@ func RunAMaster(t *testing.T) (*master.Master, *httptest.Server) {
|
||||
EnableExp: true,
|
||||
Authorizer: apiserver.NewAlwaysAllowAuthorizer(),
|
||||
AdmissionControl: admit.NewAlwaysAdmit(),
|
||||
StorageVersions: storageVersions,
|
||||
})
|
||||
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
|
@ -75,6 +75,7 @@ func TestUnschedulableNodes(t *testing.T) {
|
||||
APIPrefix: "/api",
|
||||
Authorizer: apiserver.NewAlwaysAllowAuthorizer(),
|
||||
AdmissionControl: admit.NewAlwaysAdmit(),
|
||||
StorageVersions: map[string]string{"": testapi.Default.Version()},
|
||||
})
|
||||
|
||||
restClient := client.NewOrDie(&client.Config{Host: s.URL, Version: testapi.Default.Version()})
|
||||
|
@ -68,6 +68,7 @@ func TestSecrets(t *testing.T) {
|
||||
APIPrefix: "/api",
|
||||
Authorizer: apiserver.NewAlwaysAllowAuthorizer(),
|
||||
AdmissionControl: admit.NewAlwaysAdmit(),
|
||||
StorageVersions: map[string]string{"": testapi.Default.Version()},
|
||||
})
|
||||
|
||||
framework.DeleteAllEtcdKeys()
|
||||
|
@ -420,6 +420,7 @@ func startServiceAccountTestServer(t *testing.T) (*client.Client, client.Config,
|
||||
Authenticator: authenticator,
|
||||
Authorizer: authorizer,
|
||||
AdmissionControl: serviceAccountAdmission,
|
||||
StorageVersions: map[string]string{"": testapi.Default.Version()},
|
||||
})
|
||||
|
||||
// Start the service account and service account token controllers
|
||||
|
@ -81,6 +81,7 @@ func runAMaster(t *testing.T) (*master.Master, *httptest.Server) {
|
||||
APIPrefix: "/api",
|
||||
Authorizer: apiserver.NewAlwaysAllowAuthorizer(),
|
||||
AdmissionControl: admit.NewAlwaysAdmit(),
|
||||
StorageVersions: map[string]string{"": testapi.Default.Version()},
|
||||
})
|
||||
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
|
Loading…
Reference in New Issue
Block a user