From 4e3b79aa5908c4e4b1c814e5f2e7c7c3c10d444b Mon Sep 17 00:00:00 2001 From: Solly Ross Date: Wed, 4 Oct 2017 18:42:54 -0400 Subject: [PATCH 1/4] [client-go] avoid Registry in fake REST client Previously, the fake RESTClient in client-go required a Registry. It used the Registry to fetch the GroupVersion for the fake client. However, the way it did so was dubious in some cases (it hard-coded the default API group in places), and not strictly necssary. This updates the fake client to just recieve the GroupVersion and internal group name directly, instead of requiring a Registry, so that it can be consumed in unit tests where a Registry isn't necessarily readily available (e.g. elsewhere in client-go). Kubernetes-commit: eac2049fc9a151a7cbd6652e039506376574e0a9 --- rest/fake/BUILD | 1 - rest/fake/fake.go | 16 ++++++---------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/rest/fake/BUILD b/rest/fake/BUILD index 524701f1..e511b207 100644 --- a/rest/fake/BUILD +++ b/rest/fake/BUILD @@ -10,7 +10,6 @@ go_library( srcs = ["fake.go"], importpath = "k8s.io/client-go/rest/fake", deps = [ - "//vendor/k8s.io/apimachinery/pkg/apimachinery/registered: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/types:go_default_library", diff --git a/rest/fake/fake.go b/rest/fake/fake.go index 2e1bc55a..db2c01c7 100644 --- a/rest/fake/fake.go +++ b/rest/fake/fake.go @@ -22,7 +22,6 @@ import ( "net/http" "net/url" - "k8s.io/apimachinery/pkg/apimachinery/registered" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" @@ -46,8 +45,7 @@ func (f roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) { type RESTClient struct { Client *http.Client NegotiatedSerializer runtime.NegotiatedSerializer - GroupName string - APIRegistry *registered.APIRegistrationManager + GroupVersion schema.GroupVersion VersionedAPIPath string Req *http.Request @@ -80,7 +78,7 @@ func (c *RESTClient) Verb(verb string) *restclient.Request { } func (c *RESTClient) APIVersion() schema.GroupVersion { - return c.APIRegistry.GroupOrDie("").GroupVersion + return c.GroupVersion } func (c *RESTClient) GetRateLimiter() flowcontrol.RateLimiter { @@ -89,22 +87,20 @@ func (c *RESTClient) GetRateLimiter() flowcontrol.RateLimiter { func (c *RESTClient) request(verb string) *restclient.Request { config := restclient.ContentConfig{ - ContentType: runtime.ContentTypeJSON, - // TODO this was hardcoded before, but it doesn't look right - GroupVersion: &c.APIRegistry.GroupOrDie("").GroupVersion, + ContentType: runtime.ContentTypeJSON, + GroupVersion: &c.GroupVersion, NegotiatedSerializer: c.NegotiatedSerializer, } ns := c.NegotiatedSerializer info, _ := runtime.SerializerInfoForMediaType(ns.SupportedMediaTypes(), runtime.ContentTypeJSON) internalVersion := schema.GroupVersion{ - Group: c.APIRegistry.GroupOrDie(c.GroupName).GroupVersion.Group, + Group: c.GroupVersion.Group, Version: runtime.APIVersionInternal, } - internalVersion.Version = runtime.APIVersionInternal serializers := restclient.Serializers{ // TODO this was hardcoded before, but it doesn't look right - Encoder: ns.EncoderForVersion(info.Serializer, c.APIRegistry.GroupOrDie("").GroupVersion), + Encoder: ns.EncoderForVersion(info.Serializer, c.GroupVersion), Decoder: ns.DecoderToVersion(info.Serializer, internalVersion), } if info.StreamSerializer != nil { From 5e8cd580d294bbb7cab5d0d5c71cced17f581135 Mon Sep 17 00:00:00 2001 From: Solly Ross Date: Wed, 11 Oct 2017 14:21:29 -0400 Subject: [PATCH 2/4] [client-go] fake discovery returns server groups The fake discovery client currently returns `nil, nil` for several methods. Among them is the `ServerGroups` method, which is used by the discovery REST mapper implementations. This updates the fake discovery client to actually return server groups so that the discovery REST mapper can be used in tests. Kubernetes-commit: f83a19676c0b53b2c2240d11d58e9d35c31d9ff5 --- discovery/fake/discovery.go | 39 ++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/discovery/fake/discovery.go b/discovery/fake/discovery.go index 13ccebfa..984a0ba1 100644 --- a/discovery/fake/discovery.go +++ b/discovery/fake/discovery.go @@ -68,7 +68,44 @@ func (c *FakeDiscovery) ServerPreferredNamespacedResources() ([]*metav1.APIResou } func (c *FakeDiscovery) ServerGroups() (*metav1.APIGroupList, error) { - return nil, nil + action := testing.ActionImpl{ + Verb: "get", + Resource: schema.GroupVersionResource{Resource: "group"}, + } + c.Invokes(action, nil) + + groups := map[string]*metav1.APIGroup{} + + for _, res := range c.Resources { + gv, err := schema.ParseGroupVersion(res.GroupVersion) + if err != nil { + return nil, err + } + group := groups[gv.Group] + if group == nil { + group = &metav1.APIGroup{ + Name: gv.Group, + PreferredVersion: metav1.GroupVersionForDiscovery{ + GroupVersion: res.GroupVersion, + Version: gv.Version, + }, + } + groups[gv.Group] = group + } + + group.Versions = append(group.Versions, metav1.GroupVersionForDiscovery{ + GroupVersion: res.GroupVersion, + Version: gv.Version, + }) + } + + list := &metav1.APIGroupList{} + for _, apiGroup := range groups { + list.Groups = append(list.Groups, *apiGroup) + } + + return list, nil + } func (c *FakeDiscovery) ServerVersion() (*version.Info, error) { From bb89f2cbb88503b20d8234a4998a84ecf01f61d4 Mon Sep 17 00:00:00 2001 From: Solly Ross Date: Wed, 11 Oct 2017 14:23:48 -0400 Subject: [PATCH 3/4] [client-go] Polymorphic Scale Client This introduces a polymorphic scale client capable of operating against scale subresources which return different group-versions of Scale. The scale subresources may be in group-versions different than the scale itself, so that we no longer need a copy of every scalable resource in the extensions API group. To discovery which Scale group-versions go to which subresources, discovery is used. The scale client maintains its own internal versions and conversions to several external versions, with a "hub" version that's a copy of the autoscaling internal version. It currently supports the following group-versions for Scale subresources: - extensions/v1beta1.Scale - autoscaling/v1.Scale Kubernetes-commit: d61a2d90372c301dd11088df8941acf2bb01c38c --- Godeps/Godeps.json | 172 ++++++------ rest/url_utils.go | 11 +- scale/BUILD | 72 +++++ scale/client.go | 197 ++++++++++++++ scale/client_test.go | 246 ++++++++++++++++++ scale/doc.go | 21 ++ scale/interfaces.go | 39 +++ scale/roundtrip_test.go | 34 +++ scale/scheme/BUILD | 39 +++ scale/scheme/autoscalingv1/BUILD | 35 +++ scale/scheme/autoscalingv1/conversion.go | 69 +++++ scale/scheme/autoscalingv1/doc.go | 20 ++ scale/scheme/autoscalingv1/register.go | 45 ++++ .../autoscalingv1/zz_generated.conversion.go | 109 ++++++++ scale/scheme/doc.go | 22 ++ scale/scheme/extensionsint/BUILD | 31 +++ scale/scheme/extensionsint/doc.go | 22 ++ scale/scheme/extensionsint/register.go | 53 ++++ scale/scheme/extensionsv1beta1/BUILD | 35 +++ scale/scheme/extensionsv1beta1/conversion.go | 87 +++++++ scale/scheme/extensionsv1beta1/doc.go | 20 ++ scale/scheme/extensionsv1beta1/register.go | 45 ++++ .../zz_generated.conversion.go | 110 ++++++++ scale/scheme/register.go | 52 ++++ scale/scheme/types.go | 60 +++++ scale/scheme/zz_generated.deepcopy.go | 123 +++++++++ scale/util.go | 159 +++++++++++ 27 files changed, 1850 insertions(+), 78 deletions(-) create mode 100644 scale/BUILD create mode 100644 scale/client.go create mode 100644 scale/client_test.go create mode 100644 scale/doc.go create mode 100644 scale/interfaces.go create mode 100644 scale/roundtrip_test.go create mode 100644 scale/scheme/BUILD create mode 100644 scale/scheme/autoscalingv1/BUILD create mode 100644 scale/scheme/autoscalingv1/conversion.go create mode 100644 scale/scheme/autoscalingv1/doc.go create mode 100644 scale/scheme/autoscalingv1/register.go create mode 100644 scale/scheme/autoscalingv1/zz_generated.conversion.go create mode 100644 scale/scheme/doc.go create mode 100644 scale/scheme/extensionsint/BUILD create mode 100644 scale/scheme/extensionsint/doc.go create mode 100644 scale/scheme/extensionsint/register.go create mode 100644 scale/scheme/extensionsv1beta1/BUILD create mode 100644 scale/scheme/extensionsv1beta1/conversion.go create mode 100644 scale/scheme/extensionsv1beta1/doc.go create mode 100644 scale/scheme/extensionsv1beta1/register.go create mode 100644 scale/scheme/extensionsv1beta1/zz_generated.conversion.go create mode 100644 scale/scheme/register.go create mode 100644 scale/scheme/types.go create mode 100644 scale/scheme/zz_generated.deepcopy.go create mode 100644 scale/util.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index d10379c9..23da5fd2 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -372,307 +372,327 @@ }, { "ImportPath": "k8s.io/api/admissionregistration/v1alpha1", - "Rev": "858c4b937c6f0bd144f800f3fc659ee4e52790db" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/api/apps/v1", - "Rev": "858c4b937c6f0bd144f800f3fc659ee4e52790db" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/api/apps/v1beta1", - "Rev": "858c4b937c6f0bd144f800f3fc659ee4e52790db" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/api/apps/v1beta2", - "Rev": "858c4b937c6f0bd144f800f3fc659ee4e52790db" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/api/authentication/v1", - "Rev": "858c4b937c6f0bd144f800f3fc659ee4e52790db" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/api/authentication/v1beta1", - "Rev": "858c4b937c6f0bd144f800f3fc659ee4e52790db" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/api/authorization/v1", - "Rev": "858c4b937c6f0bd144f800f3fc659ee4e52790db" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/api/authorization/v1beta1", - "Rev": "858c4b937c6f0bd144f800f3fc659ee4e52790db" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/api/autoscaling/v1", - "Rev": "858c4b937c6f0bd144f800f3fc659ee4e52790db" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/api/autoscaling/v2beta1", - "Rev": "858c4b937c6f0bd144f800f3fc659ee4e52790db" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/api/batch/v1", - "Rev": "858c4b937c6f0bd144f800f3fc659ee4e52790db" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/api/batch/v1beta1", - "Rev": "858c4b937c6f0bd144f800f3fc659ee4e52790db" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/api/batch/v2alpha1", - "Rev": "858c4b937c6f0bd144f800f3fc659ee4e52790db" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/api/certificates/v1beta1", - "Rev": "858c4b937c6f0bd144f800f3fc659ee4e52790db" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/api/core/v1", - "Rev": "858c4b937c6f0bd144f800f3fc659ee4e52790db" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/api/extensions/v1beta1", - "Rev": "858c4b937c6f0bd144f800f3fc659ee4e52790db" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/api/imagepolicy/v1alpha1", - "Rev": "858c4b937c6f0bd144f800f3fc659ee4e52790db" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/api/networking/v1", - "Rev": "858c4b937c6f0bd144f800f3fc659ee4e52790db" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/api/policy/v1beta1", - "Rev": "858c4b937c6f0bd144f800f3fc659ee4e52790db" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/api/rbac/v1", - "Rev": "858c4b937c6f0bd144f800f3fc659ee4e52790db" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/api/rbac/v1alpha1", - "Rev": "858c4b937c6f0bd144f800f3fc659ee4e52790db" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/api/rbac/v1beta1", - "Rev": "858c4b937c6f0bd144f800f3fc659ee4e52790db" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/api/scheduling/v1alpha1", - "Rev": "858c4b937c6f0bd144f800f3fc659ee4e52790db" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/api/settings/v1alpha1", - "Rev": "858c4b937c6f0bd144f800f3fc659ee4e52790db" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/api/storage/v1", - "Rev": "858c4b937c6f0bd144f800f3fc659ee4e52790db" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/api/storage/v1beta1", - "Rev": "858c4b937c6f0bd144f800f3fc659ee4e52790db" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/api/equality", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/api/errors", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/api/meta", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/api/resource", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + { + "ImportPath": "k8s.io/apimachinery/pkg/api/testing", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + { + "ImportPath": "k8s.io/apimachinery/pkg/api/testing/fuzzer", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + { + "ImportPath": "k8s.io/apimachinery/pkg/api/testing/roundtrip", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/apimachinery", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + { + "ImportPath": "k8s.io/apimachinery/pkg/apimachinery/announced", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/apimachinery/registered", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + { + "ImportPath": "k8s.io/apimachinery/pkg/apis/meta/fuzzer", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/apis/meta/internalversion", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1alpha1", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/conversion", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/conversion/queryparams", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/conversion/unstructured", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/fields", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/labels", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/runtime", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/runtime/schema", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/json", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/protobuf", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/recognizer", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/streaming", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/versioning", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/selection", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/types", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/cache", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/clock", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/diff", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/errors", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/framer", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/httpstream", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/httpstream/spdy", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/intstr", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/json", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/mergepatch", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/net", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/remotecommand", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/runtime", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/sets", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/strategicpatch", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/validation", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/validation/field", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/wait", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/yaml", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/version", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/pkg/watch", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/third_party/forked/golang/json", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/third_party/forked/golang/netutil", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/apimachinery/third_party/forked/golang/reflect", - "Rev": "23d8cc3b3bd5f64c22f31374cb62bdc037c9f6a1" + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, { "ImportPath": "k8s.io/kube-openapi/pkg/common", - "Rev": "89ae48fe8691077463af5b7fb3b6f194632c5946" + "Rev": "868f2f29720b192240e18284659231b440f9cda5" } ] } diff --git a/rest/url_utils.go b/rest/url_utils.go index 14f94650..a56d1838 100644 --- a/rest/url_utils.go +++ b/rest/url_utils.go @@ -56,6 +56,14 @@ func DefaultServerURL(host, apiPath string, groupVersion schema.GroupVersion, de // hostURL.Path should be blank. // // versionedAPIPath, a path relative to baseURL.Path, points to a versioned API base + versionedAPIPath := DefaultVersionedAPIPath(apiPath, groupVersion) + + return hostURL, versionedAPIPath, nil +} + +// DefaultVersionedAPIPathFor constructs the default path for the given group version, assuming the given +// API path, following the standard conventions of the Kubernetes API. +func DefaultVersionedAPIPath(apiPath string, groupVersion schema.GroupVersion) string { versionedAPIPath := path.Join("/", apiPath) // Add the version to the end of the path @@ -64,10 +72,9 @@ func DefaultServerURL(host, apiPath string, groupVersion schema.GroupVersion, de } else { versionedAPIPath = path.Join(versionedAPIPath, groupVersion.Version) - } - return hostURL, versionedAPIPath, nil + return versionedAPIPath } // defaultServerUrlFor is shared between IsConfigTransportTLS and RESTClientFor. It diff --git a/scale/BUILD b/scale/BUILD new file mode 100644 index 00000000..ed2bad0b --- /dev/null +++ b/scale/BUILD @@ -0,0 +1,72 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "client.go", + "doc.go", + "interfaces.go", + "util.go", + ], + importpath = "k8s.io/client-go/scale", + visibility = ["//visibility:public"], + deps = [ + "//vendor/k8s.io/api/autoscaling/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", + "//vendor/k8s.io/client-go/discovery:go_default_library", + "//vendor/k8s.io/client-go/dynamic:go_default_library", + "//vendor/k8s.io/client-go/rest:go_default_library", + "//vendor/k8s.io/client-go/scale/scheme:go_default_library", + "//vendor/k8s.io/client-go/scale/scheme/autoscalingv1:go_default_library", + "//vendor/k8s.io/client-go/scale/scheme/extensionsint:go_default_library", + "//vendor/k8s.io/client-go/scale/scheme/extensionsv1beta1:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = [ + "client_test.go", + "roundtrip_test.go", + ], + importpath = "k8s.io/client-go/scale", + library = ":go_default_library", + deps = [ + "//vendor/github.com/stretchr/testify/assert:go_default_library", + "//vendor/k8s.io/api/apps/v1beta2:go_default_library", + "//vendor/k8s.io/api/autoscaling/v1:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/testing/roundtrip:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", + "//vendor/k8s.io/client-go/discovery:go_default_library", + "//vendor/k8s.io/client-go/discovery/fake:go_default_library", + "//vendor/k8s.io/client-go/dynamic:go_default_library", + "//vendor/k8s.io/client-go/rest/fake:go_default_library", + "//vendor/k8s.io/client-go/testing:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [ + ":package-srcs", + "//staging/src/k8s.io/client-go/scale/scheme:all-srcs", + ], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/scale/client.go b/scale/client.go new file mode 100644 index 00000000..3f85197a --- /dev/null +++ b/scale/client.go @@ -0,0 +1,197 @@ +/* +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 ( + "fmt" + + autoscaling "k8s.io/api/autoscaling/v1" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime/schema" + serializer "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/client-go/dynamic" + restclient "k8s.io/client-go/rest" +) + +var scaleConverter = NewScaleConverter() +var codecs = serializer.NewCodecFactory(scaleConverter.Scheme()) + +// restInterfaceProvider turns a restclient.Config into a restclient.Interface. +// It's overridable for the purposes of testing. +type restInterfaceProvider func(*restclient.Config) (restclient.Interface, error) + +// scaleClient is an implementation of ScalesGetter +// which makes use of a RESTMapper and a generic REST +// client to support an discoverable resource. +// It behaves somewhat similarly to the dynamic ClientPool, +// but is more specifically scoped to Scale. +type scaleClient struct { + mapper meta.RESTMapper + + apiPathResolverFunc dynamic.APIPathResolverFunc + scaleKindResolver ScaleKindResolver + clientBase restclient.Interface +} + +// NewForConfig creates a new ScalesGetter which resolves kinds +// to resources using the given RESTMapper, and API paths using +// the given dynamic.APIPathResolverFunc. +func NewForConfig(cfg *restclient.Config, mapper meta.RESTMapper, resolver dynamic.APIPathResolverFunc, scaleKindResolver ScaleKindResolver) (ScalesGetter, error) { + // so that the RESTClientFor doesn't complain + cfg.GroupVersion = &schema.GroupVersion{} + + cfg.NegotiatedSerializer = serializer.DirectCodecFactory{ + CodecFactory: codecs, + } + if len(cfg.UserAgent) == 0 { + cfg.UserAgent = restclient.DefaultKubernetesUserAgent() + } + + client, err := restclient.RESTClientFor(cfg) + if err != nil { + return nil, err + } + + return New(client, mapper, resolver, scaleKindResolver), nil +} + +// New creates a new ScalesGetter using the given client to make requests. +// The GroupVersion on the client is ignored. +func New(baseClient restclient.Interface, mapper meta.RESTMapper, resolver dynamic.APIPathResolverFunc, scaleKindResolver ScaleKindResolver) ScalesGetter { + return &scaleClient{ + mapper: mapper, + + apiPathResolverFunc: resolver, + scaleKindResolver: scaleKindResolver, + clientBase: baseClient, + } +} + +// pathAndVersionFor returns the appropriate base path and the associated full GroupVersionResource +// for the given GroupResource +func (c *scaleClient) pathAndVersionFor(resource schema.GroupResource) (string, schema.GroupVersionResource, error) { + gvr, err := c.mapper.ResourceFor(resource.WithVersion("")) + if err != nil { + return "", gvr, fmt.Errorf("unable to get full preferred group-version-resource for %s: %v", resource.String(), err) + } + + groupVer := gvr.GroupVersion() + + // we need to set the API path based on GroupVersion (defaulting to the legacy path if none is set) + // TODO: we "cheat" here since the API path really only depends on group ATM, but this should + // *probably* take GroupVersionResource and not GroupVersionKind. + apiPath := c.apiPathResolverFunc(groupVer.WithKind("")) + if apiPath == "" { + apiPath = "/api" + } + + path := restclient.DefaultVersionedAPIPath(apiPath, groupVer) + + return path, gvr, nil +} + +// namespacedScaleClient is an ScaleInterface for fetching +// Scales in a given namespace. +type namespacedScaleClient struct { + client *scaleClient + namespace string +} + +func (c *scaleClient) Scales(namespace string) ScaleInterface { + return &namespacedScaleClient{ + client: c, + namespace: namespace, + } +} + +func (c *namespacedScaleClient) Get(resource schema.GroupResource, name string) (*autoscaling.Scale, error) { + // Currently, a /scale endpoint can return different scale types. + // Until we have support for the alternative API representations proposal, + // we need to deal with accepting different API versions. + // In practice, this is autoscaling/v1.Scale and extensions/v1beta1.Scale + + path, gvr, err := c.client.pathAndVersionFor(resource) + if err != nil { + return nil, fmt.Errorf("unable to get client for %s: %v", resource.String(), err) + } + + rawObj, err := c.client.clientBase.Get(). + AbsPath(path). + Namespace(c.namespace). + Resource(gvr.Resource). + Name(name). + SubResource("scale"). + Do(). + Get() + + if err != nil { + return nil, err + } + + // convert whatever this is to autoscaling/v1.Scale + scaleObj, err := scaleConverter.ConvertToVersion(rawObj, autoscaling.SchemeGroupVersion) + if err != nil { + return nil, fmt.Errorf("received an object from a /scale endpoint which was not convertible to autoscaling Scale: %v", err) + } + + return scaleObj.(*autoscaling.Scale), nil +} + +func (c *namespacedScaleClient) Update(resource schema.GroupResource, scale *autoscaling.Scale) (*autoscaling.Scale, error) { + path, gvr, err := c.client.pathAndVersionFor(resource) + if err != nil { + return nil, fmt.Errorf("unable to get client for %s: %v", resource.String(), err) + } + + // Currently, a /scale endpoint can receive and return different scale types. + // Until we hvae support for the alternative API representations proposal, + // we need to deal with sending and accepting differnet API versions. + + // figure out what scale we actually need here + desiredGVK, err := c.client.scaleKindResolver.ScaleForResource(gvr) + if err != nil { + return nil, fmt.Errorf("could not find proper group-version for scale subresource of %s: %v", gvr.String(), err) + } + + // convert this to whatever this endpoint wants + scaleUpdate, err := scaleConverter.ConvertToVersion(scale, desiredGVK.GroupVersion()) + if err != nil { + return nil, fmt.Errorf("could not convert scale update to internal Scale: %v", err) + } + + rawObj, err := c.client.clientBase.Put(). + AbsPath(path). + Namespace(c.namespace). + Resource(gvr.Resource). + Name(scale.Name). + SubResource("scale"). + Body(scaleUpdate). + Do(). + Get() + + if err != nil { + return nil, fmt.Errorf("could not fetch the scale for %s %s: %v", resource.String(), scale.Name, err) + } + + // convert whatever this is back to autoscaling/v1.Scale + scaleObj, err := scaleConverter.ConvertToVersion(rawObj, autoscaling.SchemeGroupVersion) + if err != nil { + return nil, fmt.Errorf("received an object from a /scale endpoint which was not convertible to autoscaling Scale: %v", err) + } + + return scaleObj.(*autoscaling.Scale), err +} diff --git a/scale/client_test.go b/scale/client_test.go new file mode 100644 index 00000000..a10429c0 --- /dev/null +++ b/scale/client_test.go @@ -0,0 +1,246 @@ +/* +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 ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + serializer "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/client-go/discovery" + fakedisco "k8s.io/client-go/discovery/fake" + "k8s.io/client-go/dynamic" + fakerest "k8s.io/client-go/rest/fake" + + "github.com/stretchr/testify/assert" + appsv1beta2 "k8s.io/api/apps/v1beta2" + autoscalingv1 "k8s.io/api/autoscaling/v1" + corev1 "k8s.io/api/core/v1" + extv1beta1 "k8s.io/api/extensions/v1beta1" + apimeta "k8s.io/apimachinery/pkg/api/meta" + coretesting "k8s.io/client-go/testing" +) + +func bytesBody(bodyBytes []byte) io.ReadCloser { + return ioutil.NopCloser(bytes.NewReader(bodyBytes)) +} + +func defaultHeaders() http.Header { + header := http.Header{} + header.Set("Content-Type", runtime.ContentTypeJSON) + return header +} + +func fakeScaleClient(t *testing.T) (ScalesGetter, []schema.GroupResource) { + fakeDiscoveryClient := &fakedisco.FakeDiscovery{Fake: &coretesting.Fake{}} + fakeDiscoveryClient.Resources = []*metav1.APIResourceList{ + { + GroupVersion: corev1.SchemeGroupVersion.String(), + APIResources: []metav1.APIResource{ + {Name: "pods", Namespaced: true, Kind: "Pod"}, + {Name: "replicationcontrollers", Namespaced: true, Kind: "ReplicationController"}, + {Name: "replicationcontrollers/scale", Namespaced: true, Kind: "Scale", Group: "autoscaling", Version: "v1"}, + }, + }, + { + GroupVersion: extv1beta1.SchemeGroupVersion.String(), + APIResources: []metav1.APIResource{ + {Name: "replicasets", Namespaced: true, Kind: "ReplicaSet"}, + {Name: "replicasets/scale", Namespaced: true, Kind: "Scale"}, + }, + }, + { + GroupVersion: appsv1beta2.SchemeGroupVersion.String(), + APIResources: []metav1.APIResource{ + {Name: "deployments", Namespaced: true, Kind: "Deployment"}, + {Name: "deployments/scale", Namespaced: true, Kind: "Scale", Group: "autoscaling", Version: "v1"}, + }, + }, + // test a resource that doesn't exist anywere to make sure we're not accidentally depending + // on a static RESTMapper anywhere. + { + GroupVersion: "cheese.testing.k8s.io/v27alpha15", + APIResources: []metav1.APIResource{ + {Name: "cheddars", Namespaced: true, Kind: "Cheddar"}, + {Name: "cheddars/scale", Namespaced: true, Kind: "Scale", Group: "extensions", Version: "v1beta1"}, + }, + }, + } + + restMapperRes, err := discovery.GetAPIGroupResources(fakeDiscoveryClient) + if err != nil { + t.Fatalf("unexpected error while constructing resource list from fake discovery client: %v") + } + restMapper := discovery.NewRESTMapper(restMapperRes, apimeta.InterfacesForUnstructured) + + autoscalingScale := &autoscalingv1.Scale{ + TypeMeta: metav1.TypeMeta{ + Kind: "Scale", + APIVersion: autoscalingv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: autoscalingv1.ScaleSpec{Replicas: 10}, + Status: autoscalingv1.ScaleStatus{ + Replicas: 10, + Selector: "foo=bar", + }, + } + extScale := &extv1beta1.Scale{ + TypeMeta: metav1.TypeMeta{ + Kind: "Scale", + APIVersion: extv1beta1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: extv1beta1.ScaleSpec{Replicas: 10}, + Status: extv1beta1.ScaleStatus{ + Replicas: 10, + TargetSelector: "foo=bar", + }, + } + + resourcePaths := map[string]runtime.Object{ + "/api/v1/namespaces/default/replicationcontrollers/foo/scale": autoscalingScale, + "/apis/extensions/v1beta1/namespaces/default/replicasets/foo/scale": extScale, + "/apis/apps/v1beta2/namespaces/default/deployments/foo/scale": autoscalingScale, + "/apis/cheese.testing.k8s.io/v27alpha15/namespaces/default/cheddars/foo/scale": extScale, + } + + fakeReqHandler := func(req *http.Request) (*http.Response, error) { + scale, isScalePath := resourcePaths[req.URL.Path] + if !isScalePath { + return nil, fmt.Errorf("unexpected request for URL %q with method %q", req.URL.String(), req.Method) + } + + switch req.Method { + case "GET": + res, err := json.Marshal(scale) + if err != nil { + return nil, err + } + return &http.Response{StatusCode: 200, Header: defaultHeaders(), Body: bytesBody(res)}, nil + case "PUT": + decoder := codecs.UniversalDeserializer() + body, err := ioutil.ReadAll(req.Body) + if err != nil { + return nil, err + } + newScale, newScaleGVK, err := decoder.Decode(body, nil, nil) + if err != nil { + return nil, fmt.Errorf("unexpected request body: %v", err) + } + if *newScaleGVK != scale.GetObjectKind().GroupVersionKind() { + return nil, fmt.Errorf("unexpected scale API version %s (expected %s)", newScaleGVK.String(), scale.GetObjectKind().GroupVersionKind().String()) + } + res, err := json.Marshal(newScale) + if err != nil { + return nil, err + } + return &http.Response{StatusCode: 200, Header: defaultHeaders(), Body: bytesBody(res)}, nil + default: + return nil, fmt.Errorf("unexpected request for URL %q with method %q", req.URL.String(), req.Method) + } + } + + fakeClient := &fakerest.RESTClient{ + Client: fakerest.CreateHTTPClient(fakeReqHandler), + NegotiatedSerializer: serializer.DirectCodecFactory{ + CodecFactory: codecs, + }, + GroupVersion: schema.GroupVersion{}, + VersionedAPIPath: "/not/a/real/path", + } + + resolver := NewDiscoveryScaleKindResolver(fakeDiscoveryClient) + client := New(fakeClient, restMapper, dynamic.LegacyAPIPathResolverFunc, resolver) + + groupResources := []schema.GroupResource{ + {Group: corev1.GroupName, Resource: "replicationcontroller"}, + {Group: extv1beta1.GroupName, Resource: "replicaset"}, + {Group: appsv1beta2.GroupName, Resource: "deployment"}, + {Group: "cheese.testing.k8s.io", Resource: "cheddar"}, + } + + return client, groupResources +} + +func TestGetScale(t *testing.T) { + scaleClient, groupResources := fakeScaleClient(t) + expectedScale := &autoscalingv1.Scale{ + TypeMeta: metav1.TypeMeta{ + Kind: "Scale", + APIVersion: autoscalingv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: autoscalingv1.ScaleSpec{Replicas: 10}, + Status: autoscalingv1.ScaleStatus{ + Replicas: 10, + Selector: "foo=bar", + }, + } + + for _, groupResource := range groupResources { + scale, err := scaleClient.Scales("default").Get(groupResource, "foo") + if !assert.NoError(t, err, "should have been able to fetch a scale for %s", groupResource.String()) { + continue + } + assert.NotNil(t, scale, "should have returned a non-nil scale for %s", groupResource.String()) + + assert.Equal(t, expectedScale, scale, "should have returned the expected scale for %s", groupResource.String()) + } +} + +func TestUpdateScale(t *testing.T) { + scaleClient, groupResources := fakeScaleClient(t) + expectedScale := &autoscalingv1.Scale{ + TypeMeta: metav1.TypeMeta{ + Kind: "Scale", + APIVersion: autoscalingv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: autoscalingv1.ScaleSpec{Replicas: 10}, + Status: autoscalingv1.ScaleStatus{ + Replicas: 10, + Selector: "foo=bar", + }, + } + + for _, groupResource := range groupResources { + scale, err := scaleClient.Scales("default").Update(groupResource, expectedScale) + if !assert.NoError(t, err, "should have been able to fetch a scale for %s", groupResource.String()) { + continue + } + assert.NotNil(t, scale, "should have returned a non-nil scale for %s", groupResource.String()) + + assert.Equal(t, expectedScale, scale, "should have returned the expected scale for %s", groupResource.String()) + } +} diff --git a/scale/doc.go b/scale/doc.go new file mode 100644 index 00000000..59fd3914 --- /dev/null +++ b/scale/doc.go @@ -0,0 +1,21 @@ +/* +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 provides a polymorphic scale client capable of fetching +// and updating Scale for any resource which implements the `scale` subresource, +// as long as that subresource operates on a version of scale convertable to +// autoscaling.Scale. +package scale diff --git a/scale/interfaces.go b/scale/interfaces.go new file mode 100644 index 00000000..4668c741 --- /dev/null +++ b/scale/interfaces.go @@ -0,0 +1,39 @@ +/* +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 ( + autoscalingapi "k8s.io/api/autoscaling/v1" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// ScalesGetter can produce a ScaleInterface +// for a particular namespace. +type ScalesGetter interface { + Scales(namespace string) ScaleInterface +} + +// ScaleInterface can fetch and update scales for +// resources in a particular namespace which implement +// the scale subresource. +type ScaleInterface interface { + // Get fetches the scale of the given scalable resource. + Get(resource schema.GroupResource, name string) (*autoscalingapi.Scale, error) + + // Update updates the scale of the the given scalable resource. + Update(resource schema.GroupResource, scale *autoscalingapi.Scale) (*autoscalingapi.Scale, error) +} diff --git a/scale/roundtrip_test.go b/scale/roundtrip_test.go new file mode 100644 index 00000000..2ea28875 --- /dev/null +++ b/scale/roundtrip_test.go @@ -0,0 +1,34 @@ +/* +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 ( + "testing" + + "k8s.io/apimachinery/pkg/api/testing/roundtrip" +) + +// NB: this can't be in the scheme package, because importing' +// scheme/autoscalingv1 from scheme causes a depedency loop from +// conversions + +func TestRoundTrip(t *testing.T) { + scheme := NewScaleConverter().Scheme() + // we don't actually need any custom fuzzer funcs ATM -- the defaults + // will do just fine + roundtrip.RoundTripTestForScheme(t, scheme, nil) +} diff --git a/scale/scheme/BUILD b/scale/scheme/BUILD new file mode 100644 index 00000000..effa9141 --- /dev/null +++ b/scale/scheme/BUILD @@ -0,0 +1,39 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "register.go", + "types.go", + "zz_generated.deepcopy.go", + ], + importpath = "k8s.io/client-go/scale/scheme", + visibility = ["//visibility:public"], + deps = [ + "//vendor/k8s.io/api/autoscaling/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [ + ":package-srcs", + "//staging/src/k8s.io/client-go/scale/scheme/autoscalingv1:all-srcs", + "//staging/src/k8s.io/client-go/scale/scheme/extensionsint:all-srcs", + "//staging/src/k8s.io/client-go/scale/scheme/extensionsv1beta1:all-srcs", + ], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/scale/scheme/autoscalingv1/BUILD b/scale/scheme/autoscalingv1/BUILD new file mode 100644 index 00000000..646a6fdf --- /dev/null +++ b/scale/scheme/autoscalingv1/BUILD @@ -0,0 +1,35 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "conversion.go", + "doc.go", + "register.go", + "zz_generated.conversion.go", + ], + importpath = "k8s.io/client-go/scale/scheme/autoscalingv1", + visibility = ["//visibility:public"], + deps = [ + "//vendor/k8s.io/api/autoscaling/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/conversion: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/client-go/scale/scheme:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/scale/scheme/autoscalingv1/conversion.go b/scale/scheme/autoscalingv1/conversion.go new file mode 100644 index 00000000..a775bcc2 --- /dev/null +++ b/scale/scheme/autoscalingv1/conversion.go @@ -0,0 +1,69 @@ +/* +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 autoscalingv1 + +import ( + "fmt" + + v1 "k8s.io/api/autoscaling/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/conversion" + "k8s.io/apimachinery/pkg/runtime" + scheme "k8s.io/client-go/scale/scheme" +) + +// addConversions registers conversions between the internal version +// of Scale and supported external versions of Scale. +func addConversionFuncs(scheme *runtime.Scheme) error { + err := scheme.AddConversionFuncs( + Convert_scheme_ScaleStatus_To_v1_ScaleStatus, + Convert_v1_ScaleStatus_To_scheme_ScaleStatus, + ) + if err != nil { + return err + } + + return nil +} + +func Convert_scheme_ScaleStatus_To_v1_ScaleStatus(in *scheme.ScaleStatus, out *v1.ScaleStatus, s conversion.Scope) error { + out.Replicas = in.Replicas + out.Selector = "" + if in.Selector != nil { + selector, err := metav1.LabelSelectorAsSelector(in.Selector) + if err != nil { + return fmt.Errorf("invalid label selector: %v", err) + } + out.Selector = selector.String() + } + + return nil +} + +func Convert_v1_ScaleStatus_To_scheme_ScaleStatus(in *v1.ScaleStatus, out *scheme.ScaleStatus, s conversion.Scope) error { + out.Replicas = in.Replicas + if in.Selector != "" { + labelSelector, err := metav1.ParseToLabelSelector(in.Selector) + if err != nil { + out.Selector = nil + return fmt.Errorf("failed to parse target selector: %v", err) + } + out.Selector = labelSelector + } + + return nil +} diff --git a/scale/scheme/autoscalingv1/doc.go b/scale/scheme/autoscalingv1/doc.go new file mode 100644 index 00000000..aff53e4c --- /dev/null +++ b/scale/scheme/autoscalingv1/doc.go @@ -0,0 +1,20 @@ +/* +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. +*/ + +// +k8s:conversion-gen=k8s.io/kubernetes/vendor/k8s.io/client-go/scale/scheme +// +k8s:conversion-gen-external-types=../../../../../k8s.io/api/autoscaling/v1 + +package autoscalingv1 // import "k8s.io/client-go/scale/scheme/autoscalingv1" diff --git a/scale/scheme/autoscalingv1/register.go b/scale/scheme/autoscalingv1/register.go new file mode 100644 index 00000000..b15701c4 --- /dev/null +++ b/scale/scheme/autoscalingv1/register.go @@ -0,0 +1,45 @@ +/* +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 autoscalingv1 + +import ( + autoscalingapiv1 "k8s.io/api/autoscaling/v1" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// GroupName is the group name use in this package +const GroupName = autoscalingapiv1.GroupName + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + localSchemeBuilder = &autoscalingapiv1.SchemeBuilder + AddToScheme = localSchemeBuilder.AddToScheme +) + +func init() { + // We only register manually written functions here. The registration of the + // generated functions takes place in the generated files. The separation + // makes the code compile even when the generated files are missing. + localSchemeBuilder.Register(addConversionFuncs) +} diff --git a/scale/scheme/autoscalingv1/zz_generated.conversion.go b/scale/scheme/autoscalingv1/zz_generated.conversion.go new file mode 100644 index 00000000..1eaa0d18 --- /dev/null +++ b/scale/scheme/autoscalingv1/zz_generated.conversion.go @@ -0,0 +1,109 @@ +// +build !ignore_autogenerated + +/* +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. +*/ + +// This file was autogenerated by conversion-gen. Do not edit it manually! + +package autoscalingv1 + +import ( + v1 "k8s.io/api/autoscaling/v1" + conversion "k8s.io/apimachinery/pkg/conversion" + runtime "k8s.io/apimachinery/pkg/runtime" + scheme "k8s.io/client-go/scale/scheme" +) + +func init() { + localSchemeBuilder.Register(RegisterConversions) +} + +// RegisterConversions adds conversion functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterConversions(scheme *runtime.Scheme) error { + return scheme.AddGeneratedConversionFuncs( + Convert_v1_Scale_To_scheme_Scale, + Convert_scheme_Scale_To_v1_Scale, + Convert_v1_ScaleSpec_To_scheme_ScaleSpec, + Convert_scheme_ScaleSpec_To_v1_ScaleSpec, + Convert_v1_ScaleStatus_To_scheme_ScaleStatus, + Convert_scheme_ScaleStatus_To_v1_ScaleStatus, + ) +} + +func autoConvert_v1_Scale_To_scheme_Scale(in *v1.Scale, out *scheme.Scale, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1_ScaleSpec_To_scheme_ScaleSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1_ScaleStatus_To_scheme_ScaleStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1_Scale_To_scheme_Scale is an autogenerated conversion function. +func Convert_v1_Scale_To_scheme_Scale(in *v1.Scale, out *scheme.Scale, s conversion.Scope) error { + return autoConvert_v1_Scale_To_scheme_Scale(in, out, s) +} + +func autoConvert_scheme_Scale_To_v1_Scale(in *scheme.Scale, out *v1.Scale, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_scheme_ScaleSpec_To_v1_ScaleSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_scheme_ScaleStatus_To_v1_ScaleStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_scheme_Scale_To_v1_Scale is an autogenerated conversion function. +func Convert_scheme_Scale_To_v1_Scale(in *scheme.Scale, out *v1.Scale, s conversion.Scope) error { + return autoConvert_scheme_Scale_To_v1_Scale(in, out, s) +} + +func autoConvert_v1_ScaleSpec_To_scheme_ScaleSpec(in *v1.ScaleSpec, out *scheme.ScaleSpec, s conversion.Scope) error { + out.Replicas = in.Replicas + return nil +} + +// Convert_v1_ScaleSpec_To_scheme_ScaleSpec is an autogenerated conversion function. +func Convert_v1_ScaleSpec_To_scheme_ScaleSpec(in *v1.ScaleSpec, out *scheme.ScaleSpec, s conversion.Scope) error { + return autoConvert_v1_ScaleSpec_To_scheme_ScaleSpec(in, out, s) +} + +func autoConvert_scheme_ScaleSpec_To_v1_ScaleSpec(in *scheme.ScaleSpec, out *v1.ScaleSpec, s conversion.Scope) error { + out.Replicas = in.Replicas + return nil +} + +// Convert_scheme_ScaleSpec_To_v1_ScaleSpec is an autogenerated conversion function. +func Convert_scheme_ScaleSpec_To_v1_ScaleSpec(in *scheme.ScaleSpec, out *v1.ScaleSpec, s conversion.Scope) error { + return autoConvert_scheme_ScaleSpec_To_v1_ScaleSpec(in, out, s) +} + +func autoConvert_v1_ScaleStatus_To_scheme_ScaleStatus(in *v1.ScaleStatus, out *scheme.ScaleStatus, s conversion.Scope) error { + out.Replicas = in.Replicas + // WARNING: in.Selector requires manual conversion: inconvertible types (string vs *k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector) + return nil +} + +func autoConvert_scheme_ScaleStatus_To_v1_ScaleStatus(in *scheme.ScaleStatus, out *v1.ScaleStatus, s conversion.Scope) error { + out.Replicas = in.Replicas + // WARNING: in.Selector requires manual conversion: inconvertible types (*k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector vs string) + return nil +} diff --git a/scale/scheme/doc.go b/scale/scheme/doc.go new file mode 100644 index 00000000..8a050ce4 --- /dev/null +++ b/scale/scheme/doc.go @@ -0,0 +1,22 @@ +/* +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. +*/ + +// +k8s:deepcopy-gen=package,register + +// Package scheme contains a runtime.Scheme to be used for serializing +// and deserializing different versions of Scale, and for converting +// in between them. +package scheme diff --git a/scale/scheme/extensionsint/BUILD b/scale/scheme/extensionsint/BUILD new file mode 100644 index 00000000..6174a88b --- /dev/null +++ b/scale/scheme/extensionsint/BUILD @@ -0,0 +1,31 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "register.go", + ], + importpath = "k8s.io/client-go/scale/scheme/extensionsint", + visibility = ["//visibility:public"], + deps = [ + "//vendor/k8s.io/api/extensions/v1beta1: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/client-go/scale/scheme:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/scale/scheme/extensionsint/doc.go b/scale/scheme/extensionsint/doc.go new file mode 100644 index 00000000..cc92bf18 --- /dev/null +++ b/scale/scheme/extensionsint/doc.go @@ -0,0 +1,22 @@ +/* +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 extensionsint contains the necessary scaffolding of the +// internal version of extensions as required by conversion logic. +// It doesn't have any of its own types -- it's just necessary to +// get the expected behavoir out of runtime.Scheme.ConvertToVersion +// and associated methods. +package extensionsint diff --git a/scale/scheme/extensionsint/register.go b/scale/scheme/extensionsint/register.go new file mode 100644 index 00000000..5a96ac56 --- /dev/null +++ b/scale/scheme/extensionsint/register.go @@ -0,0 +1,53 @@ +/* +Copyright 2016 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 extensionsint + +import ( + extensionsv1beta1 "k8s.io/api/extensions/v1beta1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + scalescheme "k8s.io/client-go/scale/scheme" +) + +// GroupName is the group name use in this package +const GroupName = extensionsv1beta1.GroupName + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal} + +// Kind takes an unqualified kind and returns a Group qualified GroupKind +func Kind(kind string) schema.GroupKind { + return SchemeGroupVersion.WithKind(kind).GroupKind() +} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + AddToScheme = SchemeBuilder.AddToScheme +) + +// Adds the list of known types to api.Scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &scalescheme.Scale{}, + ) + return nil +} diff --git a/scale/scheme/extensionsv1beta1/BUILD b/scale/scheme/extensionsv1beta1/BUILD new file mode 100644 index 00000000..4c992c7d --- /dev/null +++ b/scale/scheme/extensionsv1beta1/BUILD @@ -0,0 +1,35 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "conversion.go", + "doc.go", + "register.go", + "zz_generated.conversion.go", + ], + importpath = "k8s.io/client-go/scale/scheme/extensionsv1beta1", + visibility = ["//visibility:public"], + deps = [ + "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/conversion: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/client-go/scale/scheme:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/scale/scheme/extensionsv1beta1/conversion.go b/scale/scheme/extensionsv1beta1/conversion.go new file mode 100644 index 00000000..1b6b9e61 --- /dev/null +++ b/scale/scheme/extensionsv1beta1/conversion.go @@ -0,0 +1,87 @@ +/* +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 extensionsv1beta1 + +import ( + "fmt" + + v1beta1 "k8s.io/api/extensions/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/conversion" + "k8s.io/apimachinery/pkg/runtime" + scheme "k8s.io/client-go/scale/scheme" +) + +// addConversions registers conversions between the internal version +// of Scale and supported external versions of Scale. +func addConversionFuncs(scheme *runtime.Scheme) error { + err := scheme.AddConversionFuncs( + Convert_scheme_ScaleStatus_To_v1beta1_ScaleStatus, + Convert_v1beta1_ScaleStatus_To_scheme_ScaleStatus, + ) + if err != nil { + return err + } + + return nil +} +func Convert_scheme_ScaleStatus_To_v1beta1_ScaleStatus(in *scheme.ScaleStatus, out *v1beta1.ScaleStatus, s conversion.Scope) error { + out.Replicas = in.Replicas + out.Selector = nil + out.TargetSelector = "" + if in.Selector != nil { + if in.Selector.MatchExpressions == nil || len(in.Selector.MatchExpressions) == 0 { + out.Selector = in.Selector.MatchLabels + } + + selector, err := metav1.LabelSelectorAsSelector(in.Selector) + if err != nil { + return fmt.Errorf("invalid label selector: %v", err) + } + out.TargetSelector = selector.String() + } + + return nil +} + +func Convert_v1beta1_ScaleStatus_To_scheme_ScaleStatus(in *v1beta1.ScaleStatus, out *scheme.ScaleStatus, s conversion.Scope) error { + out.Replicas = in.Replicas + + // Normally when 2 fields map to the same internal value we favor the old field, since + // old clients can't be expected to know about new fields but clients that know about the + // new field can be expected to know about the old field (though that's not quite true, due + // to kubectl apply). However, these fields are readonly, so any non-nil value should work. + if in.TargetSelector != "" { + labelSelector, err := metav1.ParseToLabelSelector(in.TargetSelector) + if err != nil { + out.Selector = nil + return fmt.Errorf("failed to parse target selector: %v", err) + } + out.Selector = labelSelector + } else if in.Selector != nil { + out.Selector = new(metav1.LabelSelector) + selector := make(map[string]string) + for key, val := range in.Selector { + selector[key] = val + } + out.Selector.MatchLabels = selector + } else { + out.Selector = nil + } + + return nil +} diff --git a/scale/scheme/extensionsv1beta1/doc.go b/scale/scheme/extensionsv1beta1/doc.go new file mode 100644 index 00000000..40d0fc0e --- /dev/null +++ b/scale/scheme/extensionsv1beta1/doc.go @@ -0,0 +1,20 @@ +/* +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. +*/ + +// +k8s:conversion-gen=k8s.io/kubernetes/vendor/k8s.io/client-go/scale/scheme +// +k8s:conversion-gen-external-types=../../../../../k8s.io/api/extensions/v1beta1 + +package extensionsv1beta1 // import "k8s.io/client-go/scale/scheme/extensionsv1beta1" diff --git a/scale/scheme/extensionsv1beta1/register.go b/scale/scheme/extensionsv1beta1/register.go new file mode 100644 index 00000000..aed1174e --- /dev/null +++ b/scale/scheme/extensionsv1beta1/register.go @@ -0,0 +1,45 @@ +/* +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 extensionsv1beta1 + +import ( + extensionsapiv1beta1 "k8s.io/api/extensions/v1beta1" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// GroupName is the group name use in this package +const GroupName = extensionsapiv1beta1.GroupName + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1beta1"} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + localSchemeBuilder = &extensionsapiv1beta1.SchemeBuilder + AddToScheme = localSchemeBuilder.AddToScheme +) + +func init() { + // We only register manually written functions here. The registration of the + // generated functions takes place in the generated files. The separation + // makes the code compile even when the generated files are missing. + localSchemeBuilder.Register(addConversionFuncs) +} diff --git a/scale/scheme/extensionsv1beta1/zz_generated.conversion.go b/scale/scheme/extensionsv1beta1/zz_generated.conversion.go new file mode 100644 index 00000000..848cb8d2 --- /dev/null +++ b/scale/scheme/extensionsv1beta1/zz_generated.conversion.go @@ -0,0 +1,110 @@ +// +build !ignore_autogenerated + +/* +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. +*/ + +// This file was autogenerated by conversion-gen. Do not edit it manually! + +package extensionsv1beta1 + +import ( + v1beta1 "k8s.io/api/extensions/v1beta1" + conversion "k8s.io/apimachinery/pkg/conversion" + runtime "k8s.io/apimachinery/pkg/runtime" + scheme "k8s.io/client-go/scale/scheme" +) + +func init() { + localSchemeBuilder.Register(RegisterConversions) +} + +// RegisterConversions adds conversion functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterConversions(scheme *runtime.Scheme) error { + return scheme.AddGeneratedConversionFuncs( + Convert_v1beta1_Scale_To_scheme_Scale, + Convert_scheme_Scale_To_v1beta1_Scale, + Convert_v1beta1_ScaleSpec_To_scheme_ScaleSpec, + Convert_scheme_ScaleSpec_To_v1beta1_ScaleSpec, + Convert_v1beta1_ScaleStatus_To_scheme_ScaleStatus, + Convert_scheme_ScaleStatus_To_v1beta1_ScaleStatus, + ) +} + +func autoConvert_v1beta1_Scale_To_scheme_Scale(in *v1beta1.Scale, out *scheme.Scale, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1beta1_ScaleSpec_To_scheme_ScaleSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1beta1_ScaleStatus_To_scheme_ScaleStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1beta1_Scale_To_scheme_Scale is an autogenerated conversion function. +func Convert_v1beta1_Scale_To_scheme_Scale(in *v1beta1.Scale, out *scheme.Scale, s conversion.Scope) error { + return autoConvert_v1beta1_Scale_To_scheme_Scale(in, out, s) +} + +func autoConvert_scheme_Scale_To_v1beta1_Scale(in *scheme.Scale, out *v1beta1.Scale, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_scheme_ScaleSpec_To_v1beta1_ScaleSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_scheme_ScaleStatus_To_v1beta1_ScaleStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_scheme_Scale_To_v1beta1_Scale is an autogenerated conversion function. +func Convert_scheme_Scale_To_v1beta1_Scale(in *scheme.Scale, out *v1beta1.Scale, s conversion.Scope) error { + return autoConvert_scheme_Scale_To_v1beta1_Scale(in, out, s) +} + +func autoConvert_v1beta1_ScaleSpec_To_scheme_ScaleSpec(in *v1beta1.ScaleSpec, out *scheme.ScaleSpec, s conversion.Scope) error { + out.Replicas = in.Replicas + return nil +} + +// Convert_v1beta1_ScaleSpec_To_scheme_ScaleSpec is an autogenerated conversion function. +func Convert_v1beta1_ScaleSpec_To_scheme_ScaleSpec(in *v1beta1.ScaleSpec, out *scheme.ScaleSpec, s conversion.Scope) error { + return autoConvert_v1beta1_ScaleSpec_To_scheme_ScaleSpec(in, out, s) +} + +func autoConvert_scheme_ScaleSpec_To_v1beta1_ScaleSpec(in *scheme.ScaleSpec, out *v1beta1.ScaleSpec, s conversion.Scope) error { + out.Replicas = in.Replicas + return nil +} + +// Convert_scheme_ScaleSpec_To_v1beta1_ScaleSpec is an autogenerated conversion function. +func Convert_scheme_ScaleSpec_To_v1beta1_ScaleSpec(in *scheme.ScaleSpec, out *v1beta1.ScaleSpec, s conversion.Scope) error { + return autoConvert_scheme_ScaleSpec_To_v1beta1_ScaleSpec(in, out, s) +} + +func autoConvert_v1beta1_ScaleStatus_To_scheme_ScaleStatus(in *v1beta1.ScaleStatus, out *scheme.ScaleStatus, s conversion.Scope) error { + out.Replicas = in.Replicas + // WARNING: in.Selector requires manual conversion: inconvertible types (map[string]string vs *k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector) + // WARNING: in.TargetSelector requires manual conversion: does not exist in peer-type + return nil +} + +func autoConvert_scheme_ScaleStatus_To_v1beta1_ScaleStatus(in *scheme.ScaleStatus, out *v1beta1.ScaleStatus, s conversion.Scope) error { + out.Replicas = in.Replicas + // WARNING: in.Selector requires manual conversion: inconvertible types (*k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector vs map[string]string) + return nil +} diff --git a/scale/scheme/register.go b/scale/scheme/register.go new file mode 100644 index 00000000..7e6decff --- /dev/null +++ b/scale/scheme/register.go @@ -0,0 +1,52 @@ +/* +Copyright 2016 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 scheme + +import ( + autoscalingv1 "k8s.io/api/autoscaling/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// GroupName is the group name use in this package +const GroupName = autoscalingv1.GroupName + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal} + +// Kind takes an unqualified kind and returns a Group qualified GroupKind +func Kind(kind string) schema.GroupKind { + return SchemeGroupVersion.WithKind(kind).GroupKind() +} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + AddToScheme = SchemeBuilder.AddToScheme +) + +// Adds the list of known types to api.Scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &Scale{}, + ) + return nil +} diff --git a/scale/scheme/types.go b/scale/scheme/types.go new file mode 100644 index 00000000..13aec2b3 --- /dev/null +++ b/scale/scheme/types.go @@ -0,0 +1,60 @@ +/* +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 scheme + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// This file contains our own "internal" version of scale that we use for conversions, +// since we can't use the main Kubernetes internal versions. + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Scale represents a scaling request for a resource. +type Scale struct { + metav1.TypeMeta + // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata. + // +optional + metav1.ObjectMeta + + // defines the behavior of the scale. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status. + // +optional + Spec ScaleSpec + + // current status of the scale. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status. Read-only. + // +optional + Status ScaleStatus +} + +// ScaleSpec describes the attributes of a scale subresource. +type ScaleSpec struct { + // desired number of instances for the scaled object. + // +optional + Replicas int32 +} + +// ScaleStatus represents the current status of a scale subresource. +type ScaleStatus struct { + // actual number of observed instances of the scaled object. + Replicas int32 + + // label query over pods that should match the replicas count. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors + // +optional + Selector *metav1.LabelSelector +} diff --git a/scale/scheme/zz_generated.deepcopy.go b/scale/scheme/zz_generated.deepcopy.go new file mode 100644 index 00000000..ec9f5f95 --- /dev/null +++ b/scale/scheme/zz_generated.deepcopy.go @@ -0,0 +1,123 @@ +// +build !ignore_autogenerated + +/* +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. +*/ + +// This file was autogenerated by deepcopy-gen. Do not edit it manually! + +package scheme + +import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + conversion "k8s.io/apimachinery/pkg/conversion" + runtime "k8s.io/apimachinery/pkg/runtime" + reflect "reflect" +) + +func init() { + SchemeBuilder.Register(RegisterDeepCopies) +} + +// RegisterDeepCopies adds deep-copy functions to the given scheme. Public +// to allow building arbitrary schemes. +// +// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. +func RegisterDeepCopies(scheme *runtime.Scheme) error { + return scheme.AddGeneratedDeepCopyFuncs( + conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { + in.(*Scale).DeepCopyInto(out.(*Scale)) + return nil + }, InType: reflect.TypeOf(&Scale{})}, + conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { + in.(*ScaleSpec).DeepCopyInto(out.(*ScaleSpec)) + return nil + }, InType: reflect.TypeOf(&ScaleSpec{})}, + conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { + in.(*ScaleStatus).DeepCopyInto(out.(*ScaleStatus)) + return nil + }, InType: reflect.TypeOf(&ScaleStatus{})}, + ) +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Scale) DeepCopyInto(out *Scale) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Scale. +func (in *Scale) DeepCopy() *Scale { + if in == nil { + return nil + } + out := new(Scale) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Scale) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ScaleSpec) DeepCopyInto(out *ScaleSpec) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ScaleSpec. +func (in *ScaleSpec) DeepCopy() *ScaleSpec { + if in == nil { + return nil + } + out := new(ScaleSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ScaleStatus) DeepCopyInto(out *ScaleStatus) { + *out = *in + if in.Selector != nil { + in, out := &in.Selector, &out.Selector + if *in == nil { + *out = nil + } else { + *out = new(v1.LabelSelector) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ScaleStatus. +func (in *ScaleStatus) DeepCopy() *ScaleStatus { + if in == nil { + return nil + } + out := new(ScaleStatus) + in.DeepCopyInto(out) + return out +} diff --git a/scale/util.go b/scale/util.go new file mode 100644 index 00000000..f5de4bc9 --- /dev/null +++ b/scale/util.go @@ -0,0 +1,159 @@ +/* +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 ( + "fmt" + "strings" + "sync" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/discovery" + scalescheme "k8s.io/client-go/scale/scheme" + scaleautoscaling "k8s.io/client-go/scale/scheme/autoscalingv1" + scaleextint "k8s.io/client-go/scale/scheme/extensionsint" + scaleext "k8s.io/client-go/scale/scheme/extensionsv1beta1" +) + +// ScaleKindResolver knows about the relationship between +// resources and the GroupVersionKind of their scale subresources. +type ScaleKindResolver interface { + // ScaleForResource returns the GroupVersionKind of the + // scale subresource for the given GroupVersionResource. + ScaleForResource(resource schema.GroupVersionResource) (scaleVersion schema.GroupVersionKind, err error) +} + +// discoveryScaleResolver is a ScaleKindResolver that uses +// a DiscoveryInterface to associate resources with their +// scale-kinds +type discoveryScaleResolver struct { + discoveryClient discovery.ServerResourcesInterface +} + +func (r *discoveryScaleResolver) ScaleForResource(inputRes schema.GroupVersionResource) (scaleVersion schema.GroupVersionKind, err error) { + groupVerResources, err := r.discoveryClient.ServerResourcesForGroupVersion(inputRes.GroupVersion().String()) + if err != nil { + return schema.GroupVersionKind{}, fmt.Errorf("unable to fetch discovery information for %s: %v", inputRes.String(), err) + } + + for _, resource := range groupVerResources.APIResources { + resourceParts := strings.SplitN(resource.Name, "/", 2) + if len(resourceParts) != 2 || resourceParts[0] != inputRes.Resource || resourceParts[1] != "scale" { + // skip non-scale resources, or scales for resources that we're not looking for + continue + } + + scaleGV := inputRes.GroupVersion() + if resource.Group != "" && resource.Version != "" { + scaleGV = schema.GroupVersion{ + Group: resource.Group, + Version: resource.Version, + } + } + + return scaleGV.WithKind(resource.Kind), nil + } + + return schema.GroupVersionKind{}, fmt.Errorf("could not find scale subresource for %s in discovery information", inputRes.String()) +} + +// cachedScaleKindResolver is a ScaleKindResolver that caches results +// from another ScaleKindResolver, re-fetching on cache misses. +type cachedScaleKindResolver struct { + base ScaleKindResolver + + cache map[schema.GroupVersionResource]schema.GroupVersionKind + mu sync.RWMutex +} + +func (r *cachedScaleKindResolver) ScaleForResource(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) { + r.mu.RLock() + gvk, isCached := r.cache[resource] + r.mu.RUnlock() + if isCached { + return gvk, nil + } + + // we could have multiple fetches of the same resources, but that's probably + // better than limiting to only one reader at once (mu.Mutex), + // or blocking checks for other resources while we fetch + // (mu.Lock before fetch). + gvk, err := r.base.ScaleForResource(resource) + if err != nil { + return schema.GroupVersionKind{}, err + } + + r.mu.Lock() + defer r.mu.Unlock() + r.cache[resource] = gvk + + return gvk, nil +} + +// NewDiscoveryScaleKindResolver creates a new ScaleKindResolver which uses information from the given +// disovery client to resolve the correct Scale GroupVersionKind for different resources. +func NewDiscoveryScaleKindResolver(client discovery.ServerResourcesInterface) ScaleKindResolver { + base := &discoveryScaleResolver{ + discoveryClient: client, + } + + return &cachedScaleKindResolver{ + base: base, + cache: make(map[schema.GroupVersionResource]schema.GroupVersionKind), + } +} + +// ScaleConverter knows how to convert between external scale versions. +type ScaleConverter struct { + scheme *runtime.Scheme + internalVersioner runtime.GroupVersioner +} + +// NewScaleConverter creates a new ScaleConverter for converting between +// Scales in autoscaling/v1 and extensions/v1beta1. +func NewScaleConverter() *ScaleConverter { + scheme := runtime.NewScheme() + scaleautoscaling.AddToScheme(scheme) + scalescheme.AddToScheme(scheme) + scaleext.AddToScheme(scheme) + scaleextint.AddToScheme(scheme) + + return &ScaleConverter{ + scheme: scheme, + internalVersioner: runtime.NewMultiGroupVersioner( + scalescheme.SchemeGroupVersion, + schema.GroupKind{Group: scaleext.GroupName, Kind: "Scale"}, + schema.GroupKind{Group: scaleautoscaling.GroupName, Kind: "Scale"}, + ), + } +} + +// Scheme returns the scheme used by this scale converter. +func (c *ScaleConverter) Scheme() *runtime.Scheme { + return c.scheme +} + +// ConvertToVersion converts the given *external* input object to the given output *external* output group-version. +func (c *ScaleConverter) ConvertToVersion(in runtime.Object, outVersion schema.GroupVersion) (runtime.Object, error) { + scaleInt, err := c.scheme.ConvertToVersion(in, c.internalVersioner) + if err != nil { + return nil, err + } + + return c.scheme.ConvertToVersion(scaleInt, outVersion) +} From 22e2e0ebf18acfe7d5d15d6f0f587cb810e59dfb Mon Sep 17 00:00:00 2001 From: Solly Ross Date: Fri, 13 Oct 2017 17:55:53 -0400 Subject: [PATCH 4/4] [client-go] Add fake scale client This adds a new fake scale client (for use in testing) to match the new polymorphic scale client. Kubernetes-commit: f22bfcd65acafc75235665feac3b147f16e30998 --- scale/BUILD | 1 + scale/fake/BUILD | 28 ++++++++++++++++++ scale/fake/client.go | 67 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+) create mode 100644 scale/fake/BUILD create mode 100644 scale/fake/client.go diff --git a/scale/BUILD b/scale/BUILD index ed2bad0b..e3b3cac1 100644 --- a/scale/BUILD +++ b/scale/BUILD @@ -65,6 +65,7 @@ filegroup( name = "all-srcs", srcs = [ ":package-srcs", + "//staging/src/k8s.io/client-go/scale/fake:all-srcs", "//staging/src/k8s.io/client-go/scale/scheme:all-srcs", ], tags = ["automanaged"], diff --git a/scale/fake/BUILD b/scale/fake/BUILD new file mode 100644 index 00000000..8c937430 --- /dev/null +++ b/scale/fake/BUILD @@ -0,0 +1,28 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["client.go"], + importpath = "k8s.io/client-go/scale/fake", + visibility = ["//visibility:public"], + deps = [ + "//vendor/k8s.io/api/autoscaling/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/client-go/scale:go_default_library", + "//vendor/k8s.io/client-go/testing:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/scale/fake/client.go b/scale/fake/client.go new file mode 100644 index 00000000..1736680f --- /dev/null +++ b/scale/fake/client.go @@ -0,0 +1,67 @@ +/* +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 fake provides a fake client interface to arbitrary Kubernetes +// APIs that exposes common high level operations and exposes common +// metadata. +package fake + +import ( + autoscalingapi "k8s.io/api/autoscaling/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/scale" + "k8s.io/client-go/testing" +) + +// FakeScaleClient provides a fake implementation of scale.ScalesGetter. +type FakeScaleClient struct { + testing.Fake +} + +func (f *FakeScaleClient) Scales(namespace string) scale.ScaleInterface { + return &fakeNamespacedScaleClient{ + namespace: namespace, + fake: &f.Fake, + } +} + +type fakeNamespacedScaleClient struct { + namespace string + fake *testing.Fake +} + +func (f *fakeNamespacedScaleClient) Get(resource schema.GroupResource, name string) (*autoscalingapi.Scale, error) { + obj, err := f.fake. + Invokes(testing.NewGetSubresourceAction(resource.WithVersion(""), f.namespace, "scale", name), &autoscalingapi.Scale{}) + + if err != nil { + return nil, err + } + + return obj.(*autoscalingapi.Scale), err +} + +func (f *fakeNamespacedScaleClient) Update(resource schema.GroupResource, scale *autoscalingapi.Scale) (*autoscalingapi.Scale, error) { + obj, err := f.fake. + Invokes(testing.NewUpdateSubresourceAction(resource.WithVersion(""), f.namespace, "scale", scale), &autoscalingapi.Scale{}) + + if err != nil { + return nil, err + } + + return obj.(*autoscalingapi.Scale), err + +}