Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change legacy API resource registration #34551

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 1 addition & 2 deletions federation/cmd/federation-apiserver/app/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,11 @@ func installCoreAPIs(s *options.ServerRunOptions, g *genericapiserver.GenericAPI
v1.SchemeGroupVersion.Version: coreResources,
},
OptionsExternalVersion: &registered.GroupOrDie(core.GroupName).GroupVersion,
IsLegacyGroup: true,
Scheme: core.Scheme,
ParameterCodec: core.ParameterCodec,
NegotiatedSerializer: core.Codecs,
}
if err := g.InstallAPIGroup(&apiGroupInfo); err != nil {
if err := g.InstallLegacyAPIGroup(genericapiserver.LegacyAPIPrefix, &apiGroupInfo); err != nil {
glog.Fatalf("Error in registering group version: %+v.\n Error: %v\n", apiGroupInfo, err)
}
}
41 changes: 28 additions & 13 deletions pkg/genericapiserver/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ import (
"k8s.io/kubernetes/pkg/util/sets"
)

const (
// LegacyAPIPrefix is where the the legacy APIs will be located
LegacyAPIPrefix = "/api"
)

// Config is a structure used to configure a GenericAPIServer.
type Config struct {
// Destination for audit logs
Expand All @@ -73,7 +78,6 @@ type Config struct {
EnableProfiling bool
EnableVersion bool
EnableGarbageCollection bool
APIPrefix string
APIGroupPrefix string
CorsAllowedOriginList []string
Authenticator authenticator.Request
Expand Down Expand Up @@ -175,6 +179,10 @@ type Config struct {

// Build the handler chains by decorating the apiHandler.
BuildHandlerChainsFunc func(apiHandler http.Handler, c *Config) (secure, insecure http.Handler)

// LegacyAPIGroupPrefixes is used to set up URL parsing for authorization and for validating requests
// to InstallLegacyAPIGroup
LegacyAPIGroupPrefixes sets.String
}

type ServingInfo struct {
Expand Down Expand Up @@ -234,7 +242,6 @@ func NewConfig(options *options.ServerRunOptions) *Config {

return &Config{
APIGroupPrefix: options.APIGroupPrefix,
APIPrefix: options.APIPrefix,
CorsAllowedOriginList: options.CorsAllowedOriginList,
AuditWriter: auditWriter,
EnableGarbageCollection: options.EnableGarbageCollection,
Expand Down Expand Up @@ -262,8 +269,9 @@ func NewConfig(options *options.ServerRunOptions) *Config {
Version: "unversioned",
},
},
MaxRequestsInFlight: options.MaxRequestsInFlight,
LongRunningFunc: genericfilters.BasicLongRunningRequestCheck(longRunningRE, map[string]string{"watch": "true"}),
MaxRequestsInFlight: options.MaxRequestsInFlight,
LongRunningFunc: genericfilters.BasicLongRunningRequestCheck(longRunningRE, map[string]string{"watch": "true"}),
LegacyAPIGroupPrefixes: sets.NewString(LegacyAPIPrefix),
}
}

Expand Down Expand Up @@ -364,13 +372,13 @@ func (c completedConfig) New() (*GenericAPIServer, error) {
}

s := &GenericAPIServer{
ServiceClusterIPRange: c.ServiceClusterIPRange,
LoopbackClientConfig: c.LoopbackClientConfig,
legacyAPIPrefix: c.APIPrefix,
apiPrefix: c.APIGroupPrefix,
admissionControl: c.AdmissionControl,
requestContextMapper: c.RequestContextMapper,
Serializer: c.Serializer,
ServiceClusterIPRange: c.ServiceClusterIPRange,
LoopbackClientConfig: c.LoopbackClientConfig,
apiPrefix: c.APIGroupPrefix,
legacyAPIGroupPrefixes: c.LegacyAPIGroupPrefixes,
admissionControl: c.AdmissionControl,
requestContextMapper: c.RequestContextMapper,
Serializer: c.Serializer,

minRequestTimeout: time.Duration(c.MinRequestTimeout) * time.Second,
enableSwaggerSupport: c.EnableSwaggerSupport,
Expand Down Expand Up @@ -501,8 +509,15 @@ func DefaultAndValidateRunOptions(options *options.ServerRunOptions) {
}

func NewRequestInfoResolver(c *Config) *request.RequestInfoFactory {
apiPrefixes := sets.NewString(strings.Trim(c.APIGroupPrefix, "/")) // all possible API prefixes
legacyAPIPrefixes := sets.String{} // APIPrefixes that won't have groups (legacy)
for legacyAPIPrefix := range c.LegacyAPIGroupPrefixes {
apiPrefixes.Insert(strings.Trim(legacyAPIPrefix, "/"))
legacyAPIPrefixes.Insert(strings.Trim(legacyAPIPrefix, "/"))
}

return &request.RequestInfoFactory{
APIPrefixes: sets.NewString(strings.Trim(c.APIPrefix, "/"), strings.Trim(c.APIGroupPrefix, "/")), // all possible API prefixes
GrouplessAPIPrefixes: sets.NewString(strings.Trim(c.APIPrefix, "/")), // APIPrefixes that won't have groups (legacy)
APIPrefixes: apiPrefixes,
GrouplessAPIPrefixes: legacyAPIPrefixes,
}
}
130 changes: 73 additions & 57 deletions pkg/genericapiserver/genericapiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,14 @@ import (
certutil "k8s.io/kubernetes/pkg/util/cert"
utilnet "k8s.io/kubernetes/pkg/util/net"
utilruntime "k8s.io/kubernetes/pkg/util/runtime"
"k8s.io/kubernetes/pkg/util/sets"
)

// Info about an API group.
type APIGroupInfo struct {
GroupMeta apimachinery.GroupMeta
// Info about the resources in this group. Its a map from version to resource to the storage.
VersionedResourcesStorageMap map[string]map[string]rest.Storage
// True, if this is the legacy group ("/v1").
IsLegacyGroup bool
// OptionsExternalVersion controls the APIVersion used for common objects in the
// schema like api.Status, api.DeleteOptions, and api.ListOptions. Other implementors may
// define a version "v1beta1" but want to use the Kubernetes "v1" internal objects.
Expand Down Expand Up @@ -102,13 +101,13 @@ type GenericAPIServer struct {
// TODO eventually we should be able to factor this out to take place during initialization.
enableSwaggerSupport bool

// legacyAPIPrefix is the prefix used for legacy API groups that existed before we had API groups
// usuallly /api
legacyAPIPrefix string

// apiPrefix is the prefix where API groups live, usually /apis
apiPrefix string

// legacyAPIGroupPrefixes is used to set up URL parsing for authorization and for validating requests
// to InstallLegacyAPIGroup
legacyAPIGroupPrefixes sets.String

// admissionControl is used to build the RESTStorage that backs an API Group.
admissionControl admission.Interface

Expand Down Expand Up @@ -290,18 +289,9 @@ func (s *GenericAPIServer) Run() {
select {}
}

// Exposes the given api group in the API.
func (s *GenericAPIServer) InstallAPIGroup(apiGroupInfo *APIGroupInfo) error {
apiPrefix := s.apiPrefix
if apiGroupInfo.IsLegacyGroup {
apiPrefix = s.legacyAPIPrefix
}

// Install REST handlers for all the versions in this group.
apiVersions := []string{}
// installAPIResources is a private method for installing the REST storage backing each api groupversionresource
func (s *GenericAPIServer) installAPIResources(apiPrefix string, apiGroupInfo *APIGroupInfo) error {
for _, groupVersion := range apiGroupInfo.GroupMeta.GroupVersions {
apiVersions = append(apiVersions, groupVersion.Version)

apiGroupVersion, err := s.getAPIGroupVersion(apiGroupInfo, groupVersion, apiPrefix)
if err != nil {
return err
Expand All @@ -314,51 +304,77 @@ func (s *GenericAPIServer) InstallAPIGroup(apiGroupInfo *APIGroupInfo) error {
return fmt.Errorf("Unable to setup API %v: %v", apiGroupInfo, err)
}
}

return nil
}

func (s *GenericAPIServer) InstallLegacyAPIGroup(apiPrefix string, apiGroupInfo *APIGroupInfo) error {
if !s.legacyAPIGroupPrefixes.Has(apiPrefix) {
return fmt.Errorf("%q is not in the allowed legacy API prefixes: %v", apiPrefix, s.legacyAPIGroupPrefixes.List())
}
if err := s.installAPIResources(apiPrefix, apiGroupInfo); err != nil {
return err
}

// setup discovery
apiVersions := []string{}
for _, groupVersion := range apiGroupInfo.GroupMeta.GroupVersions {
apiVersions = append(apiVersions, groupVersion.Version)
}
// Install the version handler.
if apiGroupInfo.IsLegacyGroup {
// Add a handler at /api to enumerate the supported api versions.
apiserver.AddApiWebService(s.Serializer, s.HandlerContainer.Container, apiPrefix, func(req *restful.Request) *unversioned.APIVersions {
apiVersionsForDiscovery := unversioned.APIVersions{
ServerAddressByClientCIDRs: s.getServerAddressByClientCIDRs(req.Request),
Versions: apiVersions,
}
return &apiVersionsForDiscovery
})
} else {
// Do not register empty group or empty version. Doing so claims /apis/ for the wrong entity to be returned.
// Catching these here places the error much closer to its origin
if len(apiGroupInfo.GroupMeta.GroupVersion.Group) == 0 {
return fmt.Errorf("cannot register handler with an empty group for %#v", *apiGroupInfo)
}
if len(apiGroupInfo.GroupMeta.GroupVersion.Version) == 0 {
return fmt.Errorf("cannot register handler with an empty version for %#v", *apiGroupInfo)
// Add a handler at /<apiPrefix> to enumerate the supported api versions.
apiserver.AddApiWebService(s.Serializer, s.HandlerContainer.Container, apiPrefix, func(req *restful.Request) *unversioned.APIVersions {
apiVersionsForDiscovery := unversioned.APIVersions{
ServerAddressByClientCIDRs: s.getServerAddressByClientCIDRs(req.Request),
Versions: apiVersions,
}
return &apiVersionsForDiscovery
})
return nil
}

// Add a handler at /apis/<groupName> to enumerate all versions supported by this group.
apiVersionsForDiscovery := []unversioned.GroupVersionForDiscovery{}
for _, groupVersion := range apiGroupInfo.GroupMeta.GroupVersions {
// Check the config to make sure that we elide versions that don't have any resources
if len(apiGroupInfo.VersionedResourcesStorageMap[groupVersion.Version]) == 0 {
continue
}
apiVersionsForDiscovery = append(apiVersionsForDiscovery, unversioned.GroupVersionForDiscovery{
GroupVersion: groupVersion.String(),
Version: groupVersion.Version,
})
}
preferedVersionForDiscovery := unversioned.GroupVersionForDiscovery{
GroupVersion: apiGroupInfo.GroupMeta.GroupVersion.String(),
Version: apiGroupInfo.GroupMeta.GroupVersion.Version,
}
apiGroup := unversioned.APIGroup{
Name: apiGroupInfo.GroupMeta.GroupVersion.Group,
Versions: apiVersionsForDiscovery,
PreferredVersion: preferedVersionForDiscovery,
}
// Exposes the given api group in the API.
func (s *GenericAPIServer) InstallAPIGroup(apiGroupInfo *APIGroupInfo) error {
// Do not register empty group or empty version. Doing so claims /apis/ for the wrong entity to be returned.
// Catching these here places the error much closer to its origin
if len(apiGroupInfo.GroupMeta.GroupVersion.Group) == 0 {
return fmt.Errorf("cannot register handler with an empty group for %#v", *apiGroupInfo)
}
if len(apiGroupInfo.GroupMeta.GroupVersion.Version) == 0 {
return fmt.Errorf("cannot register handler with an empty version for %#v", *apiGroupInfo)
}

s.AddAPIGroupForDiscovery(apiGroup)
s.HandlerContainer.Add(apiserver.NewGroupWebService(s.Serializer, apiPrefix+"/"+apiGroup.Name, apiGroup))
if err := s.installAPIResources(s.apiPrefix, apiGroupInfo); err != nil {
return err
}

// setup discovery
// Install the version handler.
// Add a handler at /apis/<groupName> to enumerate all versions supported by this group.
apiVersionsForDiscovery := []unversioned.GroupVersionForDiscovery{}
for _, groupVersion := range apiGroupInfo.GroupMeta.GroupVersions {
// Check the config to make sure that we elide versions that don't have any resources
if len(apiGroupInfo.VersionedResourcesStorageMap[groupVersion.Version]) == 0 {
continue
}
apiVersionsForDiscovery = append(apiVersionsForDiscovery, unversioned.GroupVersionForDiscovery{
GroupVersion: groupVersion.String(),
Version: groupVersion.Version,
})
}
preferedVersionForDiscovery := unversioned.GroupVersionForDiscovery{
GroupVersion: apiGroupInfo.GroupMeta.GroupVersion.String(),
Version: apiGroupInfo.GroupMeta.GroupVersion.Version,
}
apiGroup := unversioned.APIGroup{
Name: apiGroupInfo.GroupMeta.GroupVersion.Group,
Versions: apiVersionsForDiscovery,
PreferredVersion: preferedVersionForDiscovery,
}

s.AddAPIGroupForDiscovery(apiGroup)
s.HandlerContainer.Add(apiserver.NewGroupWebService(s.Serializer, s.apiPrefix+"/"+apiGroup.Name, apiGroup))

return nil
}

Expand Down
29 changes: 15 additions & 14 deletions pkg/genericapiserver/genericapiserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import (
ipallocator "k8s.io/kubernetes/pkg/registry/core/service/ipallocator"
etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing"
utilnet "k8s.io/kubernetes/pkg/util/net"
"k8s.io/kubernetes/pkg/util/sets"

"github.com/stretchr/testify/assert"
)
Expand All @@ -55,7 +56,7 @@ func setUp(t *testing.T) (*etcdtesting.EtcdTestServer, Config, *assert.Assertion
config.ProxyDialer = func(network, addr string) (net.Conn, error) { return nil, nil }
config.ProxyTLSClientConfig = &tls.Config{}
config.Serializer = api.Codecs
config.APIPrefix = "/api"
config.LegacyAPIGroupPrefixes = sets.NewString("/api")
config.APIGroupPrefix = "/apis"

return etcdServer, config, assert.New(t)
Expand All @@ -79,7 +80,7 @@ func TestNew(t *testing.T) {

// Verify many of the variables match their config counterparts
assert.Equal(s.enableSwaggerSupport, config.EnableSwaggerSupport)
assert.Equal(s.legacyAPIPrefix, config.APIPrefix)
assert.Equal(s.legacyAPIGroupPrefixes, config.LegacyAPIGroupPrefixes)
assert.Equal(s.apiPrefix, config.APIGroupPrefix)
assert.Equal(s.admissionControl, config.AdmissionControl)
assert.Equal(s.RequestContextMapper(), config.RequestContextMapper)
Expand All @@ -105,7 +106,7 @@ func TestInstallAPIGroups(t *testing.T) {
etcdserver, config, assert := setUp(t)
defer etcdserver.Terminate(t)

config.APIPrefix = "/apiPrefix"
config.LegacyAPIGroupPrefixes = sets.NewString("/apiPrefix")
config.APIGroupPrefix = "/apiGroupPrefix"

s, err := config.Complete().New()
Expand All @@ -115,15 +116,15 @@ func TestInstallAPIGroups(t *testing.T) {

apiGroupMeta := registered.GroupOrDie(api.GroupName)
extensionsGroupMeta := registered.GroupOrDie(extensions.GroupName)
s.InstallLegacyAPIGroup("/apiPrefix", &APIGroupInfo{
// legacy group version
GroupMeta: *apiGroupMeta,
VersionedResourcesStorageMap: map[string]map[string]rest.Storage{},
ParameterCodec: api.ParameterCodec,
NegotiatedSerializer: api.Codecs,
})

apiGroupsInfo := []APIGroupInfo{
{
// legacy group version
GroupMeta: *apiGroupMeta,
VersionedResourcesStorageMap: map[string]map[string]rest.Storage{},
IsLegacyGroup: true,
ParameterCodec: api.ParameterCodec,
NegotiatedSerializer: api.Codecs,
},
{
// extensions group version
GroupMeta: *extensionsGroupMeta,
Expand All @@ -141,9 +142,9 @@ func TestInstallAPIGroups(t *testing.T) {
defer server.Close()
validPaths := []string{
// "/api"
config.APIPrefix,
config.LegacyAPIGroupPrefixes.List()[0],
// "/api/v1"
config.APIPrefix + "/" + apiGroupMeta.GroupVersion.Version,
config.LegacyAPIGroupPrefixes.List()[0] + "/" + apiGroupMeta.GroupVersion.Version,
// "/apis/extensions"
config.APIGroupPrefix + "/" + extensionsGroupMeta.GroupVersion.Group,
// "/apis/extensions/v1beta1"
Expand Down Expand Up @@ -224,7 +225,7 @@ func TestNotRestRoutesHaveAuth(t *testing.T) {

authz := mockAuthorizer{}

config.APIPrefix = "/apiPrefix"
config.LegacyAPIGroupPrefixes = sets.NewString("/apiPrefix")
config.APIGroupPrefix = "/apiGroupPrefix"
config.Authorizer = &authz

Expand Down
2 changes: 0 additions & 2 deletions pkg/genericapiserver/options/server_run_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ var AuthorizationModeChoices = []string{ModeAlwaysAllow, ModeAlwaysDeny, ModeABA
// ServerRunOptions contains the options while running a generic api server.
type ServerRunOptions struct {
APIGroupPrefix string
APIPrefix string
AdmissionControl string
AdmissionControlConfigFile string
AdvertiseAddress net.IP
Expand Down Expand Up @@ -125,7 +124,6 @@ type ServerRunOptions struct {
func NewServerRunOptions() *ServerRunOptions {
return &ServerRunOptions{
APIGroupPrefix: "/apis",
APIPrefix: "/api",
AdmissionControl: "AlwaysAdmit",
AnonymousAuth: true,
AuthorizationMode: "AlwaysAllow",
Expand Down