Merge pull request #54683 from liggitt/subresource-gvk

Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.

Fix subresource discovery and versioning

Fixes https://github.com/kubernetes/kubernetes/issues/54684
Related to https://github.com/kubernetes/kubernetes/pull/54586

Allows distinct subresource group/version/kind to be used for each version (gives us a path to move to autoscaling/v1 for apps, or policy/v1 for eviction, etc)

Added tests to ensure scale subresources have expected discovery info, and that the object returned matches discovery, and that the endpoint accepts the advertised version

```release-note
Fixes discovery information for scale subresources in the apps API group
```
This commit is contained in:
Kubernetes Submit Queue 2017-10-30 11:57:18 -07:00 committed by GitHub
commit 0c1f25fc1b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 365 additions and 47 deletions

View File

@ -35,6 +35,9 @@ go_library(
importpath = "k8s.io/kubernetes/pkg/registry/apps/statefulset/storage",
deps = [
"//pkg/apis/apps:go_default_library",
"//pkg/apis/apps/v1beta1:go_default_library",
"//pkg/apis/apps/v1beta2:go_default_library",
"//pkg/apis/autoscaling/v1:go_default_library",
"//pkg/apis/extensions:go_default_library",
"//pkg/apis/extensions/validation:go_default_library",
"//pkg/printers:go_default_library",

View File

@ -28,6 +28,9 @@ import (
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/kubernetes/pkg/apis/apps"
appsv1beta1 "k8s.io/kubernetes/pkg/apis/apps/v1beta1"
appsv1beta2 "k8s.io/kubernetes/pkg/apis/apps/v1beta2"
autoscalingv1 "k8s.io/kubernetes/pkg/apis/autoscaling/v1"
"k8s.io/kubernetes/pkg/apis/extensions"
extvalidation "k8s.io/kubernetes/pkg/apis/extensions/validation"
"k8s.io/kubernetes/pkg/printers"
@ -125,8 +128,15 @@ type ScaleREST struct {
var _ = rest.Patcher(&ScaleREST{})
var _ = rest.GroupVersionKindProvider(&ScaleREST{})
func (r *ScaleREST) GroupVersionKind() schema.GroupVersionKind {
return schema.GroupVersionKind{Group: "extensions", Version: "v1beta1", Kind: "Scale"}
func (r *ScaleREST) GroupVersionKind(containingGV schema.GroupVersion) schema.GroupVersionKind {
switch containingGV {
case appsv1beta1.SchemeGroupVersion:
return appsv1beta1.SchemeGroupVersion.WithKind("Scale")
case appsv1beta2.SchemeGroupVersion:
return appsv1beta2.SchemeGroupVersion.WithKind("Scale")
default:
return autoscalingv1.SchemeGroupVersion.WithKind("Scale")
}
}
// New creates a new Scale object

View File

@ -60,6 +60,7 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/labels: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/wait:go_default_library",
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
"//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library",

View File

@ -24,6 +24,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/wait"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
@ -64,6 +65,12 @@ type EvictionREST struct {
}
var _ = rest.Creater(&EvictionREST{})
var _ = rest.GroupVersionKindProvider(&EvictionREST{})
// GroupVersionKind specifies a particular GroupVersionKind to discovery
func (r *EvictionREST) GroupVersionKind(containingGV schema.GroupVersion) schema.GroupVersionKind {
return schema.GroupVersionKind{Group: "policy", Version: "v1beta1", Kind: "Eviction"}
}
// New creates a new eviction resource
func (r *EvictionREST) New() runtime.Object {

View File

@ -36,7 +36,9 @@ go_library(
deps = [
"//pkg/api:go_default_library",
"//pkg/apis/autoscaling:go_default_library",
"//pkg/apis/autoscaling/v1:go_default_library",
"//pkg/apis/autoscaling/validation:go_default_library",
"//pkg/apis/extensions/v1beta1:go_default_library",
"//pkg/printers:go_default_library",
"//pkg/printers/internalversion:go_default_library",
"//pkg/printers/storage:go_default_library",

View File

@ -32,7 +32,9 @@ import (
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/autoscaling"
autoscalingv1 "k8s.io/kubernetes/pkg/apis/autoscaling/v1"
"k8s.io/kubernetes/pkg/apis/autoscaling/validation"
extensionsv1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
"k8s.io/kubernetes/pkg/printers"
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
@ -129,8 +131,13 @@ type ScaleREST struct {
var _ = rest.Patcher(&ScaleREST{})
var _ = rest.GroupVersionKindProvider(&ScaleREST{})
func (r *ScaleREST) GroupVersionKind() schema.GroupVersionKind {
return schema.GroupVersionKind{Group: "autoscaling", Version: "v1", Kind: "Scale"}
func (r *ScaleREST) GroupVersionKind(containingGV schema.GroupVersion) schema.GroupVersionKind {
switch containingGV {
case extensionsv1beta1.SchemeGroupVersion:
return extensionsv1beta1.SchemeGroupVersion.WithKind("Scale")
default:
return autoscalingv1.SchemeGroupVersion.WithKind("Scale")
}
}
// New creates a new Scale object

View File

@ -93,19 +93,13 @@ func (c LegacyRESTStorageProvider) NewLegacyRESTStorage(restOptionsGetter generi
apiGroupInfo := genericapiserver.APIGroupInfo{
GroupMeta: *legacyscheme.Registry.GroupOrDie(api.GroupName),
VersionedResourcesStorageMap: map[string]map[string]rest.Storage{},
Scheme: legacyscheme.Scheme,
ParameterCodec: legacyscheme.ParameterCodec,
NegotiatedSerializer: legacyscheme.Codecs,
SubresourceGroupVersionKind: map[string]schema.GroupVersionKind{},
}
if autoscalingGroupVersion := (schema.GroupVersion{Group: "autoscaling", Version: "v1"}); legacyscheme.Registry.IsEnabledVersion(autoscalingGroupVersion) {
apiGroupInfo.SubresourceGroupVersionKind["replicationcontrollers/scale"] = autoscalingGroupVersion.WithKind("Scale")
Scheme: legacyscheme.Scheme,
ParameterCodec: legacyscheme.ParameterCodec,
NegotiatedSerializer: legacyscheme.Codecs,
}
var podDisruptionClient policyclient.PodDisruptionBudgetsGetter
if policyGroupVersion := (schema.GroupVersion{Group: "policy", Version: "v1beta1"}); legacyscheme.Registry.IsEnabledVersion(policyGroupVersion) {
apiGroupInfo.SubresourceGroupVersionKind["pods/eviction"] = policyGroupVersion.WithKind("Eviction")
var err error
podDisruptionClient, err = policyclient.NewForConfig(c.LoopbackClientConfig)
if err != nil {

View File

@ -36,7 +36,11 @@ go_library(
srcs = ["storage.go"],
importpath = "k8s.io/kubernetes/pkg/registry/extensions/deployment/storage",
deps = [
"//pkg/apis/apps/v1beta1:go_default_library",
"//pkg/apis/apps/v1beta2:go_default_library",
"//pkg/apis/autoscaling/v1:go_default_library",
"//pkg/apis/extensions:go_default_library",
"//pkg/apis/extensions/v1beta1:go_default_library",
"//pkg/apis/extensions/validation:go_default_library",
"//pkg/registry/extensions/deployment:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",

View File

@ -30,7 +30,11 @@ import (
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/apiserver/pkg/storage"
storeerr "k8s.io/apiserver/pkg/storage/errors"
appsv1beta1 "k8s.io/kubernetes/pkg/apis/apps/v1beta1"
appsv1beta2 "k8s.io/kubernetes/pkg/apis/apps/v1beta2"
autoscalingv1 "k8s.io/kubernetes/pkg/apis/autoscaling/v1"
"k8s.io/kubernetes/pkg/apis/extensions"
extensionsv1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
extvalidation "k8s.io/kubernetes/pkg/apis/extensions/validation"
"k8s.io/kubernetes/pkg/registry/extensions/deployment"
)
@ -193,8 +197,17 @@ type ScaleREST struct {
var _ = rest.Patcher(&ScaleREST{})
var _ = rest.GroupVersionKindProvider(&ScaleREST{})
func (r *ScaleREST) GroupVersionKind() schema.GroupVersionKind {
return schema.GroupVersionKind{Group: "extensions", Version: "v1beta1", Kind: "Scale"}
func (r *ScaleREST) GroupVersionKind(containingGV schema.GroupVersion) schema.GroupVersionKind {
switch containingGV {
case extensionsv1beta1.SchemeGroupVersion:
return extensionsv1beta1.SchemeGroupVersion.WithKind("Scale")
case appsv1beta1.SchemeGroupVersion:
return appsv1beta1.SchemeGroupVersion.WithKind("Scale")
case appsv1beta2.SchemeGroupVersion:
return appsv1beta2.SchemeGroupVersion.WithKind("Scale")
default:
return autoscalingv1.SchemeGroupVersion.WithKind("Scale")
}
}
// New creates a new Scale object

View File

@ -34,7 +34,11 @@ go_library(
srcs = ["storage.go"],
importpath = "k8s.io/kubernetes/pkg/registry/extensions/replicaset/storage",
deps = [
"//pkg/apis/apps/v1beta1:go_default_library",
"//pkg/apis/apps/v1beta2:go_default_library",
"//pkg/apis/autoscaling/v1:go_default_library",
"//pkg/apis/extensions:go_default_library",
"//pkg/apis/extensions/v1beta1:go_default_library",
"//pkg/apis/extensions/validation:go_default_library",
"//pkg/printers:go_default_library",
"//pkg/printers/internalversion:go_default_library",

View File

@ -29,7 +29,11 @@ import (
"k8s.io/apiserver/pkg/registry/generic"
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
"k8s.io/apiserver/pkg/registry/rest"
appsv1beta1 "k8s.io/kubernetes/pkg/apis/apps/v1beta1"
appsv1beta2 "k8s.io/kubernetes/pkg/apis/apps/v1beta2"
autoscalingv1 "k8s.io/kubernetes/pkg/apis/autoscaling/v1"
"k8s.io/kubernetes/pkg/apis/extensions"
extensionsv1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
extvalidation "k8s.io/kubernetes/pkg/apis/extensions/validation"
"k8s.io/kubernetes/pkg/printers"
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
@ -127,8 +131,17 @@ type ScaleREST struct {
var _ = rest.Patcher(&ScaleREST{})
var _ = rest.GroupVersionKindProvider(&ScaleREST{})
func (r *ScaleREST) GroupVersionKind() schema.GroupVersionKind {
return schema.GroupVersionKind{Group: "extensions", Version: "v1beta1", Kind: "Scale"}
func (r *ScaleREST) GroupVersionKind(containingGV schema.GroupVersion) schema.GroupVersionKind {
switch containingGV {
case extensionsv1beta1.SchemeGroupVersion:
return extensionsv1beta1.SchemeGroupVersion.WithKind("Scale")
case appsv1beta1.SchemeGroupVersion:
return appsv1beta1.SchemeGroupVersion.WithKind("Scale")
case appsv1beta2.SchemeGroupVersion:
return appsv1beta2.SchemeGroupVersion.WithKind("Scale")
default:
return autoscalingv1.SchemeGroupVersion.WithKind("Scale")
}
}
// New creates a new Scale object

View File

@ -3867,7 +3867,8 @@ func TestUpdateChecksAPIVersion(t *testing.T) {
}
type SimpleXGSubresourceRESTStorage struct {
item genericapitesting.SimpleXGSubresource
item genericapitesting.SimpleXGSubresource
itemGVK schema.GroupVersionKind
}
func (storage *SimpleXGSubresourceRESTStorage) New() runtime.Object {
@ -3878,6 +3879,12 @@ func (storage *SimpleXGSubresourceRESTStorage) Get(ctx request.Context, id strin
return storage.item.DeepCopyObject(), nil
}
var _ = rest.GroupVersionKindProvider(&SimpleXGSubresourceRESTStorage{})
func (storage *SimpleXGSubresourceRESTStorage) GroupVersionKind(containingGV schema.GroupVersion) schema.GroupVersionKind {
return storage.itemGVK
}
func TestXGSubresource(t *testing.T) {
container := restful.NewContainer()
container.Router(restful.CurlyRouter{})
@ -3888,6 +3895,7 @@ func TestXGSubresource(t *testing.T) {
item: genericapitesting.SimpleXGSubresource{
SubresourceInfo: "foo",
},
itemGVK: testGroup2Version.WithKind("SimpleXGSubresource"),
}
storage := map[string]rest.Storage{
"simple": &SimpleRESTStorage{},
@ -3913,10 +3921,6 @@ func TestXGSubresource(t *testing.T) {
GroupVersion: testGroupVersion,
OptionsExternalVersion: &testGroupVersion,
Serializer: codecs,
SubresourceGroupVersionKind: map[string]schema.GroupVersionKind{
"simple/subsimple": testGroup2Version.WithKind("SimpleXGSubresource"),
},
}
if err := (&group).InstallREST(container); err != nil {

View File

@ -75,12 +75,6 @@ type APIGroupVersion struct {
MinRequestTimeout time.Duration
// SubresourceGroupVersionKind contains the GroupVersionKind overrides for each subresource that is
// accessible from this API group version. The GroupVersionKind is that of the external version of
// the subresource. The key of this map should be the path of the subresource. The keys here should
// match the keys in the Storage map above for subresources.
SubresourceGroupVersionKind map[string]schema.GroupVersionKind
// EnableAPIResponseCompression indicates whether API Responses should support compression
// if the client requests it via Accept-Encoding
EnableAPIResponseCompression bool

View File

@ -144,8 +144,9 @@ func (a *APIInstaller) newWebService() *restful.WebService {
// object. If the storage object is a subresource and has an override supplied for it, it returns
// the group version kind supplied in the override.
func (a *APIInstaller) getResourceKind(path string, storage rest.Storage) (schema.GroupVersionKind, error) {
if fqKindToRegister, ok := a.group.SubresourceGroupVersionKind[path]; ok {
return fqKindToRegister, nil
// Let the storage tell us exactly what GVK it has
if gvkProvider, ok := storage.(rest.GroupVersionKindProvider); ok {
return gvkProvider.GroupVersionKind(a.group.GroupVersion), nil
}
object := storage.New()
@ -162,12 +163,6 @@ func (a *APIInstaller) getResourceKind(path string, storage rest.Storage) (schem
fqKindToRegister = a.group.GroupVersion.WithKind(fqKind.Kind)
break
}
// TODO: keep rid of extensions api group dependency here
// This keeps it doing what it was doing before, but it doesn't feel right.
if fqKind.Group == "extensions" && fqKind.Kind == "ThirdPartyResourceData" {
fqKindToRegister = a.group.GroupVersion.WithKind(fqKind.Kind)
}
}
if fqKindToRegister.Empty() {
return schema.GroupVersionKind{}, fmt.Errorf("unable to locate fully qualified kind for %v: found %v when registering for %v", reflect.TypeOf(object), fqKinds, a.group.GroupVersion)
@ -878,7 +873,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
apiResource.Categories = categoriesProvider.Categories()
}
if gvkProvider, ok := storage.(rest.GroupVersionKindProvider); ok {
gvk := gvkProvider.GroupVersionKind()
gvk := gvkProvider.GroupVersionKind(a.group.GroupVersion)
apiResource.Group = gvk.Group
apiResource.Version = gvk.Version
apiResource.Kind = gvk.Kind

View File

@ -82,7 +82,7 @@ type CategoriesProvider interface {
// This trumps KindProvider since it is capable of providing the information required.
// TODO KindProvider (only used by federation) should be removed and replaced with this, but that presents greater risk late in 1.8.
type GroupVersionKindProvider interface {
GroupVersionKind() schema.GroupVersionKind
GroupVersionKind(containingGV schema.GroupVersion) schema.GroupVersionKind
}
// Lister is an object that can retrieve resources that match the provided field and label criteria.

View File

@ -70,12 +70,6 @@ type APIGroupInfo struct {
NegotiatedSerializer runtime.NegotiatedSerializer
// ParameterCodec performs conversions for query parameters passed to API calls
ParameterCodec runtime.ParameterCodec
// SubresourceGroupVersionKind contains the GroupVersionKind overrides for each subresource that is
// accessible from this API group version. The GroupVersionKind is that of the external version of
// the subresource. The key of this map should be the path of the subresource. The keys here should
// match the keys in the Storage map above for subresources.
SubresourceGroupVersionKind map[string]schema.GroupVersionKind
}
// GenericAPIServer contains state for a Kubernetes cluster api server.
@ -435,9 +429,8 @@ func (s *GenericAPIServer) newAPIGroupVersion(apiGroupInfo *APIGroupInfo, groupV
UnsafeConvertor: runtime.UnsafeObjectConvertor(apiGroupInfo.Scheme),
Defaulter: apiGroupInfo.Scheme,
Typer: apiGroupInfo.Scheme,
SubresourceGroupVersionKind: apiGroupInfo.SubresourceGroupVersionKind,
Linker: apiGroupInfo.GroupMeta.SelfLinker,
Mapper: apiGroupInfo.GroupMeta.RESTMapper,
Linker: apiGroupInfo.GroupMeta.SelfLinker,
Mapper: apiGroupInfo.GroupMeta.RESTMapper,
Admit: s.admissionControl,
Context: s.RequestContextMapper(),

View File

@ -51,6 +51,7 @@ filegroup(
"//test/integration/quota:all-srcs",
"//test/integration/replicaset:all-srcs",
"//test/integration/replicationcontroller:all-srcs",
"//test/integration/scale:all-srcs",
"//test/integration/scheduler:all-srcs",
"//test/integration/scheduler_perf:all-srcs",
"//test/integration/secrets:all-srcs",

View File

@ -0,0 +1,37 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
)
go_test(
name = "go_default_test",
size = "large",
srcs = ["scale_test.go"],
importpath = "k8s.io/kubernetes/test/integration/scale",
tags = ["integration"],
deps = [
"//cmd/kube-apiserver/app/testing:go_default_library",
"//vendor/github.com/coreos/pkg/capnslog:go_default_library",
"//vendor/k8s.io/api/apps/v1beta2:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -0,0 +1,236 @@
/*
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 scale
import (
"encoding/json"
"path"
"strings"
"testing"
"github.com/coreos/pkg/capnslog"
appsv1beta2 "k8s.io/api/apps/v1beta2"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes"
apitesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
)
type subresourceTest struct {
resource schema.GroupVersionResource
kind schema.GroupVersionKind
}
func makeGVR(group, version, resource string) schema.GroupVersionResource {
return schema.GroupVersionResource{Group: group, Version: version, Resource: resource}
}
func makeGVK(group, version, kind string) schema.GroupVersionKind {
return schema.GroupVersionKind{Group: group, Version: version, Kind: kind}
}
func TestScaleSubresources(t *testing.T) {
clientSet, tearDown := setup(t)
defer tearDown()
resourceLists, err := clientSet.Discovery().ServerResources()
if err != nil {
t.Fatal(err)
}
expectedScaleSubresources := map[schema.GroupVersionResource]schema.GroupVersionKind{
makeGVR("", "v1", "replicationcontrollers/scale"): makeGVK("autoscaling", "v1", "Scale"),
makeGVR("extensions", "v1beta1", "deployments/scale"): makeGVK("extensions", "v1beta1", "Scale"),
makeGVR("extensions", "v1beta1", "replicationcontrollers/scale"): makeGVK("extensions", "v1beta1", "Scale"),
makeGVR("extensions", "v1beta1", "replicasets/scale"): makeGVK("extensions", "v1beta1", "Scale"),
makeGVR("apps", "v1beta1", "deployments/scale"): makeGVK("apps", "v1beta1", "Scale"),
makeGVR("apps", "v1beta1", "statefulsets/scale"): makeGVK("apps", "v1beta1", "Scale"),
makeGVR("apps", "v1beta2", "deployments/scale"): makeGVK("apps", "v1beta2", "Scale"),
makeGVR("apps", "v1beta2", "replicasets/scale"): makeGVK("apps", "v1beta2", "Scale"),
makeGVR("apps", "v1beta2", "statefulsets/scale"): makeGVK("apps", "v1beta2", "Scale"),
// makeGVR("apps", "v1", "deployments/scale"): makeGVK("autoscaling", "v1", "Scale"),
// makeGVR("apps", "v1", "replicasets/scale"): makeGVK("autoscaling", "v1", "Scale"),
// makeGVR("apps", "v1", "statefulsets/scale"): makeGVK("autoscaling", "v1", "Scale"),
}
autoscalingGVK := schema.GroupVersionKind{Group: "autoscaling", Version: "v1", Kind: "Scale"}
discoveredScaleSubresources := map[schema.GroupVersionResource]schema.GroupVersionKind{}
for _, resourceList := range resourceLists {
containingGV, err := schema.ParseGroupVersion(resourceList.GroupVersion)
if err != nil {
t.Fatalf("error getting group version for %#v: %v", resourceList, err)
}
for _, resource := range resourceList.APIResources {
if !strings.HasSuffix(resource.Name, "/scale") {
continue
}
gvr := containingGV.WithResource(resource.Name)
if _, exists := discoveredScaleSubresources[gvr]; exists {
t.Errorf("scale subresource %#v listed multiple times in discovery", gvr)
continue
}
gvk := containingGV.WithKind(resource.Kind)
if resource.Group != "" {
gvk.Group = resource.Group
}
if resource.Version != "" {
gvk.Version = resource.Version
}
discoveredScaleSubresources[gvr] = gvk
}
}
// Ensure nothing is missing
for gvr, gvk := range expectedScaleSubresources {
if _, ok := discoveredScaleSubresources[gvr]; !ok {
t.Errorf("expected scale subresource %#v of kind %#v was missing from discovery", gvr, gvk)
}
}
// Ensure discovery lists expected types
for gvr, gvk := range discoveredScaleSubresources {
if expectedGVK, expected := expectedScaleSubresources[gvr]; !expected {
if gvk == autoscalingGVK {
t.Errorf("unexpected scale subresource %#v of kind %#v. new scale subresource should be added to expectedScaleSubresources", gvr, gvk)
} else {
t.Errorf("unexpected scale subresource %#v of kind %#v. new scale resources are expected to use Scale from the autoscaling/v1 API group", gvr, gvk)
}
continue
} else if expectedGVK != gvk {
t.Errorf("scale subresource %#v should be of kind %#v, but %#v was listed in discovery", gvr, expectedGVK, gvk)
continue
}
}
// Create objects required to exercise scale subresources
if _, err := clientSet.CoreV1().ReplicationControllers("default").Create(rcStub); err != nil {
t.Fatal(err)
}
if _, err := clientSet.AppsV1beta2().ReplicaSets("default").Create(rsStub); err != nil {
t.Fatal(err)
}
if _, err := clientSet.AppsV1beta2().Deployments("default").Create(deploymentStub); err != nil {
t.Fatal(err)
}
if _, err := clientSet.AppsV1beta2().StatefulSets("default").Create(ssStub); err != nil {
t.Fatal(err)
}
// Ensure scale subresources return and accept expected kinds
for gvr, gvk := range discoveredScaleSubresources {
prefix := "/apis"
if gvr.Group == corev1.GroupName {
prefix = "/api"
}
resourceParts := strings.SplitN(gvr.Resource, "/", 2)
urlPath := path.Join(prefix, gvr.Group, gvr.Version, "namespaces", "default", resourceParts[0], "test", resourceParts[1])
obj := &unstructured.Unstructured{}
getData, err := clientSet.CoreV1().RESTClient().Get().AbsPath(urlPath).DoRaw()
if err != nil {
t.Errorf("error fetching %s: %v", urlPath, err)
continue
}
if err := json.Unmarshal(getData, obj); err != nil {
t.Errorf("error decoding %s: %v", urlPath, err)
t.Log(string(getData))
continue
}
if obj.GetObjectKind().GroupVersionKind() != gvk {
t.Errorf("expected %#v, got %#v from %s", gvk, obj.GetObjectKind().GroupVersionKind(), urlPath)
t.Log(string(getData))
continue
}
updateData, err := clientSet.CoreV1().RESTClient().Put().AbsPath(urlPath).Body(getData).DoRaw()
if err != nil {
t.Errorf("error putting to %s: %v", urlPath, err)
t.Log(string(getData))
t.Log(string(updateData))
continue
}
}
}
var (
replicas = int32(1)
podStub = corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}},
Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "test", Image: "busybox"}}},
}
rcStub = &corev1.ReplicationController{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Spec: corev1.ReplicationControllerSpec{Selector: podStub.Labels, Replicas: &replicas, Template: &podStub},
}
rsStub = &appsv1beta2.ReplicaSet{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Spec: appsv1beta2.ReplicaSetSpec{Selector: &metav1.LabelSelector{MatchLabels: podStub.Labels}, Replicas: &replicas, Template: podStub},
}
deploymentStub = &appsv1beta2.Deployment{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Spec: appsv1beta2.DeploymentSpec{Selector: &metav1.LabelSelector{MatchLabels: podStub.Labels}, Replicas: &replicas, Template: podStub},
}
ssStub = &appsv1beta2.StatefulSet{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Spec: appsv1beta2.StatefulSetSpec{Selector: &metav1.LabelSelector{MatchLabels: podStub.Labels}, Replicas: &replicas, Template: podStub},
}
)
func setup(t *testing.T) (client kubernetes.Interface, tearDown func()) {
masterConfig, tearDownMaster := apitesting.StartTestServerOrDie(t)
// TODO: Disable logging here until we resolve teardown issues which result in
// massive log spam. Another path forward would be to refactor
// StartTestServerOrDie to work with the etcd instance already started by the
// integration test scripts.
// See https://github.com/kubernetes/kubernetes/issues/49489.
repo, err := capnslog.GetRepoLogger("github.com/coreos/etcd")
if err != nil {
t.Fatalf("couldn't configure logging: %v", err)
}
repo.SetLogLevel(map[string]capnslog.LogLevel{
"etcdserver/api/v3rpc": capnslog.CRITICAL,
})
masterConfig.AcceptContentTypes = ""
masterConfig.ContentType = ""
masterConfig.NegotiatedSerializer = nil
clientSet, err := kubernetes.NewForConfig(masterConfig)
if err != nil {
t.Fatalf("error creating clientset: %v", err)
}
return clientSet, tearDownMaster
}