Merge pull request #19040 from nikhiljindal/serverLibrary

api server library: moving API registration logic to generic api server
pull/6/head
Nikhil Jindal 2016-01-04 14:31:33 -08:00
commit af9834ea75
6 changed files with 232 additions and 170 deletions

View File

@ -1,14 +1,14 @@
{
"swaggerVersion": "1.2",
"apis": [
{
"path": "/api/v1",
"description": "API at /api/v1"
},
{
"path": "/version",
"description": "git code version from which this is built"
},
{
"path": "/api/v1",
"description": "API at /api/v1"
},
{
"path": "/api",
"description": "get available API versions"

View File

@ -78,7 +78,7 @@ func (a *APIInstaller) Install(ws *restful.WebService) (apiResources []unversion
for _, path := range paths {
apiResource, err := a.registerResourceHandlers(path, a.group.Storage[path], ws, proxyHandler)
if err != nil {
errors = append(errors, err)
errors = append(errors, fmt.Errorf("error in registering resource: %s, %v", path, err))
}
if apiResource != nil {
apiResources = append(apiResources, *apiResource)

View File

@ -18,6 +18,7 @@ package genericapiserver
import (
"crypto/tls"
"fmt"
"net"
"net/http"
"net/http/pprof"
@ -27,7 +28,9 @@ import (
"k8s.io/kubernetes/pkg/admission"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/latest"
"k8s.io/kubernetes/pkg/api/rest"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apiserver"
"k8s.io/kubernetes/pkg/auth/authenticator"
"k8s.io/kubernetes/pkg/auth/authorizer"
@ -128,6 +131,21 @@ type APIGroupVersionOverride struct {
ResourceOverrides map[string]bool
}
// Info about an API group.
type APIGroupInfo struct {
GroupMeta latest.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.
// If nil, defaults to groupMeta.GroupVersion.
// TODO: Remove this when https://github.com/kubernetes/kubernetes/issues/19018 is fixed.
OptionsExternalVersion *unversioned.GroupVersion
}
// Config is a structure used to configure a GenericAPIServer.
type Config struct {
StorageDestinations StorageDestinations
@ -229,8 +247,8 @@ type GenericAPIServer struct {
enableSwaggerSupport bool
enableProfiling bool
enableWatchCache bool
ApiPrefix string
ApiGroupPrefix string
APIPrefix string
APIGroupPrefix string
corsAllowedOriginList []string
authenticator authenticator.Request
authorizer authorizer.Authorizer
@ -351,8 +369,8 @@ func New(c *Config) *GenericAPIServer {
enableSwaggerSupport: c.EnableSwaggerSupport,
enableProfiling: c.EnableProfiling,
enableWatchCache: c.EnableWatchCache,
ApiPrefix: c.APIPrefix,
ApiGroupPrefix: c.APIGroupPrefix,
APIPrefix: c.APIPrefix,
APIGroupPrefix: c.APIGroupPrefix,
corsAllowedOriginList: c.CorsAllowedOriginList,
authenticator: c.Authenticator,
authorizer: c.Authorizer,
@ -397,8 +415,8 @@ func New(c *Config) *GenericAPIServer {
func (s *GenericAPIServer) NewRequestInfoResolver() *apiserver.RequestInfoResolver {
return &apiserver.RequestInfoResolver{
sets.NewString(strings.Trim(s.ApiPrefix, "/"), strings.Trim(s.ApiGroupPrefix, "/")), // all possible API prefixes
sets.NewString(strings.Trim(s.ApiPrefix, "/")), // APIPrefixes that won't have groups (legacy)
sets.NewString(strings.Trim(s.APIPrefix, "/"), strings.Trim(s.APIGroupPrefix, "/")), // all possible API prefixes
sets.NewString(strings.Trim(s.APIPrefix, "/")), // APIPrefixes that won't have groups (legacy)
}
}
@ -504,6 +522,102 @@ func (s *GenericAPIServer) init(c *Config) {
}
}
// Exposes the given group versions in API.
func (s *GenericAPIServer) InstallAPIGroups(groupsInfo []APIGroupInfo) error {
for _, apiGroupInfo := range groupsInfo {
if err := s.installAPIGroup(&apiGroupInfo); err != nil {
return err
}
}
return nil
}
func (s *GenericAPIServer) installAPIGroup(apiGroupInfo *APIGroupInfo) error {
apiPrefix := s.APIGroupPrefix
if apiGroupInfo.IsLegacyGroup {
apiPrefix = s.APIPrefix
}
// Install REST handlers for all the versions in this group.
apiVersions := []string{}
for _, groupVersion := range apiGroupInfo.GroupMeta.GroupVersions {
apiVersions = append(apiVersions, groupVersion.Version)
apiGroupVersion, err := s.getAPIGroupVersion(apiGroupInfo, groupVersion, apiPrefix)
if err != nil {
return err
}
if apiGroupInfo.OptionsExternalVersion != nil {
apiGroupVersion.OptionsExternalVersion = apiGroupInfo.OptionsExternalVersion
}
if err := apiGroupVersion.InstallREST(s.HandlerContainer); err != nil {
return fmt.Errorf("Unable to setup API %v: %v", apiGroupInfo, err)
}
}
// Install the version handler.
if apiGroupInfo.IsLegacyGroup {
// Add a handler at /api to enumerate the supported api versions.
apiserver.AddApiWebService(s.HandlerContainer, apiPrefix, apiVersions)
} else {
// Add a handler at /apis/<groupName> to enumerate all versions supported by this group.
apiVersionsForDiscovery := []unversioned.GroupVersionForDiscovery{}
for _, groupVersion := range apiGroupInfo.GroupMeta.GroupVersions {
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,
}
apiserver.AddGroupWebService(s.HandlerContainer, apiPrefix+"/"+apiGroup.Name, apiGroup)
}
apiserver.InstallServiceErrorHandler(s.HandlerContainer, s.NewRequestInfoResolver(), apiVersions)
return nil
}
func (s *GenericAPIServer) getAPIGroupVersion(apiGroupInfo *APIGroupInfo, groupVersion unversioned.GroupVersion, apiPrefix string) (*apiserver.APIGroupVersion, error) {
storage := make(map[string]rest.Storage)
for k, v := range apiGroupInfo.VersionedResourcesStorageMap[groupVersion.Version] {
storage[strings.ToLower(k)] = v
}
version, err := s.newAPIGroupVersion(apiGroupInfo.GroupMeta, groupVersion)
version.Root = apiPrefix
version.Storage = storage
return version, err
}
func (s *GenericAPIServer) newAPIGroupVersion(groupMeta latest.GroupMeta, groupVersion unversioned.GroupVersion) (*apiserver.APIGroupVersion, error) {
versionInterface, err := groupMeta.InterfacesFor(groupVersion)
if err != nil {
return nil, err
}
return &apiserver.APIGroupVersion{
RequestInfoResolver: s.NewRequestInfoResolver(),
Creater: api.Scheme,
Convertor: api.Scheme,
Typer: api.Scheme,
GroupVersion: groupVersion,
Linker: groupMeta.SelfLinker,
Mapper: groupMeta.RESTMapper,
Codec: versionInterface.Codec,
Admit: s.AdmissionControl,
Context: s.RequestContextMapper,
MinRequestTimeout: s.MinRequestTimeout,
}, nil
}
// InstallSwaggerAPI installs the /swaggerapi/ endpoint to allow schema discovery
// and traversal. It is optional to allow consumers of the Kubernetes GenericAPIServer to
// register their own web services into the Kubernetes mux prior to initialization

View File

@ -21,8 +21,13 @@ import (
"fmt"
"net"
"net/http"
"net/http/httptest"
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/latest"
"k8s.io/kubernetes/pkg/api/rest"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/apiserver"
etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing"
"k8s.io/kubernetes/pkg/util"
@ -57,8 +62,8 @@ func TestNew(t *testing.T) {
assert.Equal(s.enableUISupport, config.EnableUISupport)
assert.Equal(s.enableSwaggerSupport, config.EnableSwaggerSupport)
assert.Equal(s.enableProfiling, config.EnableProfiling)
assert.Equal(s.ApiPrefix, config.APIPrefix)
assert.Equal(s.ApiGroupPrefix, config.APIGroupPrefix)
assert.Equal(s.APIPrefix, config.APIPrefix)
assert.Equal(s.APIGroupPrefix, config.APIGroupPrefix)
assert.Equal(s.corsAllowedOriginList, config.CorsAllowedOriginList)
assert.Equal(s.authenticator, config.Authenticator)
assert.Equal(s.authorizer, config.Authorizer)
@ -80,6 +85,54 @@ func TestNew(t *testing.T) {
assert.Equal(s.ProxyTransport.(*http.Transport).TLSClientConfig, config.ProxyTLSClientConfig)
}
// Verifies that AddGroupVersions works as expected.
func TestInstallAPIGroups(t *testing.T) {
_, etcdserver, config, assert := setUp(t)
defer etcdserver.Terminate(t)
config.ProxyDialer = func(network, addr string) (net.Conn, error) { return nil, nil }
config.ProxyTLSClientConfig = &tls.Config{}
config.APIPrefix = "/apiPrefix"
config.APIGroupPrefix = "/apiGroupPrefix"
s := New(&config)
apiGroupMeta := latest.GroupOrDie(api.GroupName)
extensionsGroupMeta := latest.GroupOrDie(extensions.GroupName)
apiGroupsInfo := []APIGroupInfo{
{
// legacy group version
GroupMeta: *apiGroupMeta,
VersionedResourcesStorageMap: map[string]map[string]rest.Storage{},
IsLegacyGroup: true,
},
{
// extensions group version
GroupMeta: *extensionsGroupMeta,
VersionedResourcesStorageMap: map[string]map[string]rest.Storage{},
OptionsExternalVersion: &apiGroupMeta.GroupVersion,
},
}
s.InstallAPIGroups(apiGroupsInfo)
server := httptest.NewServer(s.HandlerContainer.ServeMux)
validPaths := []string{
// "/api"
config.APIPrefix,
// "/api/v1"
config.APIPrefix + "/" + apiGroupMeta.GroupVersion.Version,
// "/apis/extensions"
config.APIGroupPrefix + "/" + extensionsGroupMeta.GroupVersion.Group,
// "/apis/extensions/v1beta1"
config.APIGroupPrefix + "/" + extensionsGroupMeta.GroupVersion.String(),
}
for _, path := range validPaths {
_, err := http.Get(server.URL + path)
if !assert.NoError(err) {
t.Errorf("unexpected error: %v, for path: %s", err, path)
}
}
}
// TestNewHandlerContainer verifies that NewHandlerContainer uses the
// mux provided
func TestNewHandlerContainer(t *testing.T) {

View File

@ -30,8 +30,6 @@ import (
"k8s.io/kubernetes/pkg/api/latest"
"k8s.io/kubernetes/pkg/api/rest"
"k8s.io/kubernetes/pkg/api/unversioned"
apiutil "k8s.io/kubernetes/pkg/api/util"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/apiserver"
"k8s.io/kubernetes/pkg/genericapiserver"
@ -159,14 +157,8 @@ func New(c *Config) *Master {
}
func (m *Master) InstallAPIs(c *Config) {
apiVersions := []string{}
// Install v1 unless disabled.
if !m.ApiGroupVersionOverrides["api/v1"].Disable {
if err := m.api_v1(c).InstallREST(m.HandlerContainer); err != nil {
glog.Fatalf("Unable to setup API v1: %v", err)
}
apiVersions = append(apiVersions, "v1")
}
apiGroupsInfo := []genericapiserver.APIGroupInfo{}
// Run the tunnel.
healthzChecks := []healthz.HealthzChecker{}
if m.tunneler != nil {
@ -180,12 +172,24 @@ func (m *Master) InstallAPIs(c *Config) {
// TODO(nikhiljindal): Refactor generic parts of support services (like /versions) to genericapiserver.
apiserver.InstallSupport(m.MuxHelper, m.RootWebService, c.EnableProfiling, healthzChecks...)
// Install v1 unless disabled.
if !m.ApiGroupVersionOverrides["api/v1"].Disable {
// Install v1 API.
m.initV1ResourcesStorage(c)
apiGroupInfo := genericapiserver.APIGroupInfo{
GroupMeta: *latest.GroupOrDie(api.GroupName),
VersionedResourcesStorageMap: map[string]map[string]rest.Storage{
"v1": m.v1ResourcesStorage,
},
IsLegacyGroup: true,
}
apiGroupsInfo = append(apiGroupsInfo, apiGroupInfo)
}
// Install root web services
m.HandlerContainer.Add(m.RootWebService)
apiserver.AddApiWebService(m.HandlerContainer, c.APIPrefix, apiVersions)
apiserver.InstallServiceErrorHandler(m.HandlerContainer, m.NewRequestInfoResolver(), apiVersions)
// allGroups records all supported groups at /apis
allGroups := []unversioned.APIGroup{}
// Install extensions unless disabled.
@ -193,33 +197,41 @@ func (m *Master) InstallAPIs(c *Config) {
m.thirdPartyStorage = c.StorageDestinations.APIGroups[extensions.GroupName].Default
m.thirdPartyResources = map[string]thirdPartyEntry{}
expVersion := m.experimental(c)
if err := expVersion.InstallREST(m.HandlerContainer); err != nil {
glog.Fatalf("Unable to setup experimental api: %v", err)
}
g, err := latest.Group(extensions.GroupName)
if err != nil {
glog.Fatalf("Unable to setup experimental api: %v", err)
}
expAPIVersions := []unversioned.GroupVersionForDiscovery{
{
GroupVersion: expVersion.GroupVersion.String(),
Version: expVersion.GroupVersion.Version,
},
}
storageVersion, found := c.StorageVersions[g.GroupVersion.Group]
extensionResources := m.getExtensionResources(c)
extensionsGroupMeta := latest.GroupOrDie(extensions.GroupName)
// Update the prefered version as per StorageVersions in the config.
storageVersion, found := c.StorageVersions[extensionsGroupMeta.GroupVersion.Group]
if !found {
glog.Fatalf("Couldn't find storage version of group %v", g.GroupVersion.Group)
glog.Fatalf("Couldn't find storage version of group %v", extensionsGroupMeta.GroupVersion.Group)
}
preferedGroupVersion, err := unversioned.ParseGroupVersion(storageVersion)
if err != nil {
glog.Fatalf("Error in parsing group version %s: %v", storageVersion, err)
}
extensionsGroupMeta.GroupVersion = preferedGroupVersion
apiGroupInfo := genericapiserver.APIGroupInfo{
GroupMeta: *extensionsGroupMeta,
VersionedResourcesStorageMap: map[string]map[string]rest.Storage{
"v1beta1": extensionResources,
},
OptionsExternalVersion: &latest.GroupOrDie(api.GroupName).GroupVersion,
}
apiGroupsInfo = append(apiGroupsInfo, apiGroupInfo)
extensionsGVForDiscovery := unversioned.GroupVersionForDiscovery{
GroupVersion: extensionsGroupMeta.GroupVersion.String(),
Version: extensionsGroupMeta.GroupVersion.Version,
}
group := unversioned.APIGroup{
Name: g.GroupVersion.Group,
Versions: expAPIVersions,
PreferredVersion: unversioned.GroupVersionForDiscovery{GroupVersion: storageVersion, Version: apiutil.GetVersion(storageVersion)},
Name: extensionsGroupMeta.GroupVersion.Group,
Versions: []unversioned.GroupVersionForDiscovery{extensionsGVForDiscovery},
PreferredVersion: extensionsGVForDiscovery,
}
apiserver.AddGroupWebService(m.HandlerContainer, c.APIGroupPrefix+"/"+latest.GroupOrDie(extensions.GroupName).GroupVersion.Group, group)
allGroups = append(allGroups, group)
apiserver.InstallServiceErrorHandler(m.HandlerContainer, m.NewRequestInfoResolver(), []string{expVersion.GroupVersion.String()})
}
if err := m.InstallAPIGroups(apiGroupsInfo); err != nil {
glog.Fatalf("Error in registering group versions: %v", err)
}
// This should be done after all groups are registered
@ -398,39 +410,6 @@ func (m *Master) getServersToValidate(c *Config) map[string]apiserver.Server {
return serversToValidate
}
func (m *Master) defaultAPIGroupVersion() *apiserver.APIGroupVersion {
return &apiserver.APIGroupVersion{
Root: m.ApiPrefix,
RequestInfoResolver: m.NewRequestInfoResolver(),
Mapper: latest.GroupOrDie(api.GroupName).RESTMapper,
Creater: api.Scheme,
Convertor: api.Scheme,
Typer: api.Scheme,
Linker: latest.GroupOrDie(api.GroupName).SelfLinker,
Admit: m.AdmissionControl,
Context: m.RequestContextMapper,
MinRequestTimeout: m.MinRequestTimeout,
}
}
// api_v1 returns the resources and codec for API version v1.
func (m *Master) api_v1(c *Config) *apiserver.APIGroupVersion {
m.initV1ResourcesStorage(c)
storage := make(map[string]rest.Storage)
for k, v := range m.v1ResourcesStorage {
storage[strings.ToLower(k)] = v
}
version := m.defaultAPIGroupVersion()
version.Storage = storage
version.GroupVersion = unversioned.GroupVersion{Version: "v1"}
version.Codec = v1.Codec
return version
}
// HasThirdPartyResource returns true if a particular third party resource currently installed.
func (m *Master) HasThirdPartyResource(rsrc *extensions.ThirdPartyResource) (bool, error) {
_, group, err := thirdpartyresourcedata.ExtractApiGroupAndKind(rsrc)
@ -575,8 +554,8 @@ func (m *Master) thirdpartyapi(group, kind, version string) *apiserver.APIGroupV
}
}
// experimental returns the resources and codec for the experimental api
func (m *Master) experimental(c *Config) *apiserver.APIGroupVersion {
// getExperimentalResources returns the resources for extenstions api
func (m *Master) getExtensionResources(c *Config) map[string]rest.Storage {
// All resources except these are disabled by default.
enabledResources := sets.NewString("jobs", "horizontalpodautoscalers", "ingresses")
resourceOverrides := m.ApiGroupVersionOverrides["extensions/v1beta1"].ResourceOverrides
@ -640,30 +619,7 @@ func (m *Master) experimental(c *Config) *apiserver.APIGroupVersion {
storage["ingresses"] = ingressStorage
storage["ingresses/status"] = ingressStatusStorage
}
extensionsGroup := latest.GroupOrDie(extensions.GroupName)
optionsExternalVersion := latest.GroupOrDie(api.GroupName).GroupVersion
return &apiserver.APIGroupVersion{
Root: m.ApiGroupPrefix,
RequestInfoResolver: m.NewRequestInfoResolver(),
Creater: api.Scheme,
Convertor: api.Scheme,
Typer: api.Scheme,
Mapper: extensionsGroup.RESTMapper,
Codec: extensionsGroup.Codec,
Linker: extensionsGroup.SelfLinker,
Storage: storage,
GroupVersion: extensionsGroup.GroupVersion,
OptionsExternalVersion: &optionsExternalVersion,
Admit: m.AdmissionControl,
Context: m.RequestContextMapper,
MinRequestTimeout: m.MinRequestTimeout,
}
return storage
}
// findExternalAddress returns ExternalIP of provided node with fallback to LegacyHostIP.

View File

@ -30,11 +30,9 @@ import (
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/latest"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/api/unversioned"
apiutil "k8s.io/kubernetes/pkg/api/util"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/genericapiserver"
"k8s.io/kubernetes/pkg/kubelet/client"
@ -102,8 +100,8 @@ func TestNew(t *testing.T) {
// Verify many of the variables match their config counterparts
assert.Equal(master.enableCoreControllers, config.EnableCoreControllers)
assert.Equal(master.tunneler, config.Tunneler)
assert.Equal(master.ApiPrefix, config.APIPrefix)
assert.Equal(master.ApiGroupPrefix, config.APIGroupPrefix)
assert.Equal(master.APIPrefix, config.APIPrefix)
assert.Equal(master.APIGroupPrefix, config.APIGroupPrefix)
assert.Equal(master.ApiGroupVersionOverrides, config.APIGroupVersionOverrides)
assert.Equal(master.RequestContextMapper, config.RequestContextMapper)
assert.Equal(master.MasterCount, config.MasterCount)
@ -160,35 +158,6 @@ func TestFindExternalAddress(t *testing.T) {
assert.Error(err, "expected findExternalAddress to fail on a node with missing ip information")
}
// TestApi_v1 verifies that the unexported api_v1 function does indeed
// utilize the correct Version and Codec.
func TestApi_v1(t *testing.T) {
_, etcdserver, config, assert := setUp(t)
defer etcdserver.Terminate(t)
// config.KubeletClient = client.FakeKubeletClient{}
config.ProxyDialer = func(network, addr string) (net.Conn, error) { return nil, nil }
config.ProxyTLSClientConfig = &tls.Config{}
s := genericapiserver.New(config.Config)
master := &Master{
GenericAPIServer: s,
tunneler: config.Tunneler,
}
version := master.api_v1(&config)
assert.Equal(unversioned.GroupVersion{Version: "v1"}, version.GroupVersion, "Version was not v1: %s", version.GroupVersion)
assert.Equal(v1.Codec, version.Codec, "version.Codec was not for v1: %s", version.Codec)
// Verify that version storage has all the resources.
for k, v := range master.v1ResourcesStorage {
k = strings.ToLower(k)
val, ok := version.Storage[k]
assert.True(ok, "ok: %s", ok)
assert.Equal(val, v)
}
}
// TestNewBootstrapController verifies master fields are properly copied into controller
func TestNewBootstrapController(t *testing.T) {
// Tests a subset of inputs to ensure they are set properly in the controller
@ -248,36 +217,6 @@ func TestControllerServicePorts(t *testing.T) {
assert.Equal(1010, controller.ExtraServicePorts[1].Port)
}
// TestDefaultAPIGroupVersion verifies that the unexported defaultAPIGroupVersion
// creates the expected APIGroupVersion based off of master.
func TestDefaultAPIGroupVersion(t *testing.T) {
master, etcdserver, _, assert := setUp(t)
defer etcdserver.Terminate(t)
apiGroup := master.defaultAPIGroupVersion()
assert.Equal(apiGroup.Root, master.ApiPrefix)
assert.Equal(apiGroup.Admit, master.AdmissionControl)
assert.Equal(apiGroup.Context, master.RequestContextMapper)
assert.Equal(apiGroup.MinRequestTimeout, master.MinRequestTimeout)
}
// TestExpapi verifies that the unexported exapi creates
// the an experimental unversioned.APIGroupVersion.
func TestExpapi(t *testing.T) {
master, etcdserver, config, assert := setUp(t)
defer etcdserver.Terminate(t)
extensionsGroupMeta := latest.GroupOrDie(extensions.GroupName)
expAPIGroup := master.experimental(&config)
assert.Equal(expAPIGroup.Root, master.ApiGroupPrefix)
assert.Equal(expAPIGroup.Mapper, extensionsGroupMeta.RESTMapper)
assert.Equal(expAPIGroup.Codec, extensionsGroupMeta.Codec)
assert.Equal(expAPIGroup.Linker, extensionsGroupMeta.SelfLinker)
assert.Equal(expAPIGroup.GroupVersion, extensionsGroupMeta.GroupVersion)
}
// TestGetNodeAddresses verifies that proper results are returned
// when requesting node addresses.
func TestGetNodeAddresses(t *testing.T) {