Skip to content

Commit d61a2d9

Browse files
committed
[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
1 parent f83a196 commit d61a2d9

29 files changed

+1780
-2
lines changed

hack/.golint_failures

+5
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,11 @@ staging/src/k8s.io/client-go/plugin/pkg/auth/authenticator/token/oidc/testing
715715
staging/src/k8s.io/client-go/plugin/pkg/client/auth/oidc
716716
staging/src/k8s.io/client-go/rest
717717
staging/src/k8s.io/client-go/rest/fake
718+
staging/src/k8s.io/client-go/scale
719+
staging/src/k8s.io/client-go/scale/scheme
720+
staging/src/k8s.io/client-go/scale/scheme/autoscalingv1
721+
staging/src/k8s.io/client-go/scale/scheme/extensionsint
722+
staging/src/k8s.io/client-go/scale/scheme/extensionsv1beta1
718723
staging/src/k8s.io/client-go/testing
719724
staging/src/k8s.io/client-go/tools/cache
720725
staging/src/k8s.io/client-go/tools/cache/testing

staging/BUILD

+1
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ filegroup(
167167
"//staging/src/k8s.io/client-go/plugin/pkg/auth/authenticator/token/oidc/testing:all-srcs",
168168
"//staging/src/k8s.io/client-go/plugin/pkg/client/auth:all-srcs",
169169
"//staging/src/k8s.io/client-go/rest:all-srcs",
170+
"//staging/src/k8s.io/client-go/scale:all-srcs",
170171
"//staging/src/k8s.io/client-go/testing:all-srcs",
171172
"//staging/src/k8s.io/client-go/third_party/forked/golang/template:all-srcs",
172173
"//staging/src/k8s.io/client-go/tools/auth:all-srcs",

staging/src/k8s.io/client-go/Godeps/Godeps.json

+20
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

staging/src/k8s.io/client-go/rest/url_utils.go

+9-2
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,14 @@ func DefaultServerURL(host, apiPath string, groupVersion schema.GroupVersion, de
5656
// hostURL.Path should be blank.
5757
//
5858
// versionedAPIPath, a path relative to baseURL.Path, points to a versioned API base
59+
versionedAPIPath := DefaultVersionedAPIPath(apiPath, groupVersion)
60+
61+
return hostURL, versionedAPIPath, nil
62+
}
63+
64+
// DefaultVersionedAPIPathFor constructs the default path for the given group version, assuming the given
65+
// API path, following the standard conventions of the Kubernetes API.
66+
func DefaultVersionedAPIPath(apiPath string, groupVersion schema.GroupVersion) string {
5967
versionedAPIPath := path.Join("/", apiPath)
6068

6169
// Add the version to the end of the path
@@ -64,10 +72,9 @@ func DefaultServerURL(host, apiPath string, groupVersion schema.GroupVersion, de
6472

6573
} else {
6674
versionedAPIPath = path.Join(versionedAPIPath, groupVersion.Version)
67-
6875
}
6976

70-
return hostURL, versionedAPIPath, nil
77+
return versionedAPIPath
7178
}
7279

7380
// defaultServerUrlFor is shared between IsConfigTransportTLS and RESTClientFor. It
+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
2+
3+
go_library(
4+
name = "go_default_library",
5+
srcs = [
6+
"client.go",
7+
"doc.go",
8+
"interfaces.go",
9+
"util.go",
10+
],
11+
importpath = "k8s.io/client-go/scale",
12+
visibility = ["//visibility:public"],
13+
deps = [
14+
"//vendor/k8s.io/api/autoscaling/v1:go_default_library",
15+
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
16+
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
17+
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
18+
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
19+
"//vendor/k8s.io/client-go/discovery:go_default_library",
20+
"//vendor/k8s.io/client-go/dynamic:go_default_library",
21+
"//vendor/k8s.io/client-go/rest:go_default_library",
22+
"//vendor/k8s.io/client-go/scale/scheme:go_default_library",
23+
"//vendor/k8s.io/client-go/scale/scheme/autoscalingv1:go_default_library",
24+
"//vendor/k8s.io/client-go/scale/scheme/extensionsint:go_default_library",
25+
"//vendor/k8s.io/client-go/scale/scheme/extensionsv1beta1:go_default_library",
26+
],
27+
)
28+
29+
go_test(
30+
name = "go_default_test",
31+
srcs = [
32+
"client_test.go",
33+
"roundtrip_test.go",
34+
],
35+
importpath = "k8s.io/client-go/scale",
36+
library = ":go_default_library",
37+
deps = [
38+
"//vendor/github.com/stretchr/testify/assert:go_default_library",
39+
"//vendor/k8s.io/api/apps/v1beta2:go_default_library",
40+
"//vendor/k8s.io/api/autoscaling/v1:go_default_library",
41+
"//vendor/k8s.io/api/core/v1:go_default_library",
42+
"//vendor/k8s.io/api/extensions/v1beta1:go_default_library",
43+
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
44+
"//vendor/k8s.io/apimachinery/pkg/api/testing/roundtrip:go_default_library",
45+
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
46+
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
47+
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
48+
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
49+
"//vendor/k8s.io/client-go/discovery:go_default_library",
50+
"//vendor/k8s.io/client-go/discovery/fake:go_default_library",
51+
"//vendor/k8s.io/client-go/dynamic:go_default_library",
52+
"//vendor/k8s.io/client-go/rest/fake:go_default_library",
53+
"//vendor/k8s.io/client-go/testing:go_default_library",
54+
],
55+
)
56+
57+
filegroup(
58+
name = "package-srcs",
59+
srcs = glob(["**"]),
60+
tags = ["automanaged"],
61+
visibility = ["//visibility:private"],
62+
)
63+
64+
filegroup(
65+
name = "all-srcs",
66+
srcs = [
67+
":package-srcs",
68+
"//staging/src/k8s.io/client-go/scale/scheme:all-srcs",
69+
],
70+
tags = ["automanaged"],
71+
visibility = ["//visibility:public"],
72+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
/*
2+
Copyright 2017 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package scale
18+
19+
import (
20+
"fmt"
21+
22+
autoscaling "k8s.io/api/autoscaling/v1"
23+
"k8s.io/apimachinery/pkg/api/meta"
24+
"k8s.io/apimachinery/pkg/runtime/schema"
25+
serializer "k8s.io/apimachinery/pkg/runtime/serializer"
26+
"k8s.io/client-go/dynamic"
27+
restclient "k8s.io/client-go/rest"
28+
)
29+
30+
var scaleConverter = NewScaleConverter()
31+
var codecs = serializer.NewCodecFactory(scaleConverter.Scheme())
32+
33+
// restInterfaceProvider turns a restclient.Config into a restclient.Interface.
34+
// It's overridable for the purposes of testing.
35+
type restInterfaceProvider func(*restclient.Config) (restclient.Interface, error)
36+
37+
// scaleClient is an implementation of ScalesGetter
38+
// which makes use of a RESTMapper and a generic REST
39+
// client to support an discoverable resource.
40+
// It behaves somewhat similarly to the dynamic ClientPool,
41+
// but is more specifically scoped to Scale.
42+
type scaleClient struct {
43+
mapper meta.RESTMapper
44+
45+
apiPathResolverFunc dynamic.APIPathResolverFunc
46+
scaleKindResolver ScaleKindResolver
47+
clientBase restclient.Interface
48+
}
49+
50+
// NewForConfig creates a new ScalesGetter which resolves kinds
51+
// to resources using the given RESTMapper, and API paths using
52+
// the given dynamic.APIPathResolverFunc.
53+
func NewForConfig(cfg *restclient.Config, mapper meta.RESTMapper, resolver dynamic.APIPathResolverFunc, scaleKindResolver ScaleKindResolver) (ScalesGetter, error) {
54+
// so that the RESTClientFor doesn't complain
55+
cfg.GroupVersion = &schema.GroupVersion{}
56+
57+
cfg.NegotiatedSerializer = serializer.DirectCodecFactory{
58+
CodecFactory: codecs,
59+
}
60+
if len(cfg.UserAgent) == 0 {
61+
cfg.UserAgent = restclient.DefaultKubernetesUserAgent()
62+
}
63+
64+
client, err := restclient.RESTClientFor(cfg)
65+
if err != nil {
66+
return nil, err
67+
}
68+
69+
return New(client, mapper, resolver, scaleKindResolver), nil
70+
}
71+
72+
// New creates a new ScalesGetter using the given client to make requests.
73+
// The GroupVersion on the client is ignored.
74+
func New(baseClient restclient.Interface, mapper meta.RESTMapper, resolver dynamic.APIPathResolverFunc, scaleKindResolver ScaleKindResolver) ScalesGetter {
75+
return &scaleClient{
76+
mapper: mapper,
77+
78+
apiPathResolverFunc: resolver,
79+
scaleKindResolver: scaleKindResolver,
80+
clientBase: baseClient,
81+
}
82+
}
83+
84+
// pathAndVersionFor returns the appropriate base path and the associated full GroupVersionResource
85+
// for the given GroupResource
86+
func (c *scaleClient) pathAndVersionFor(resource schema.GroupResource) (string, schema.GroupVersionResource, error) {
87+
gvr, err := c.mapper.ResourceFor(resource.WithVersion(""))
88+
if err != nil {
89+
return "", gvr, fmt.Errorf("unable to get full preferred group-version-resource for %s: %v", resource.String(), err)
90+
}
91+
92+
groupVer := gvr.GroupVersion()
93+
94+
// we need to set the API path based on GroupVersion (defaulting to the legacy path if none is set)
95+
// TODO: we "cheat" here since the API path really only depends on group ATM, but this should
96+
// *probably* take GroupVersionResource and not GroupVersionKind.
97+
apiPath := c.apiPathResolverFunc(groupVer.WithKind(""))
98+
if apiPath == "" {
99+
apiPath = "/api"
100+
}
101+
102+
path := restclient.DefaultVersionedAPIPath(apiPath, groupVer)
103+
104+
return path, gvr, nil
105+
}
106+
107+
// namespacedScaleClient is an ScaleInterface for fetching
108+
// Scales in a given namespace.
109+
type namespacedScaleClient struct {
110+
client *scaleClient
111+
namespace string
112+
}
113+
114+
func (c *scaleClient) Scales(namespace string) ScaleInterface {
115+
return &namespacedScaleClient{
116+
client: c,
117+
namespace: namespace,
118+
}
119+
}
120+
121+
func (c *namespacedScaleClient) Get(resource schema.GroupResource, name string) (*autoscaling.Scale, error) {
122+
// Currently, a /scale endpoint can return different scale types.
123+
// Until we have support for the alternative API representations proposal,
124+
// we need to deal with accepting different API versions.
125+
// In practice, this is autoscaling/v1.Scale and extensions/v1beta1.Scale
126+
127+
path, gvr, err := c.client.pathAndVersionFor(resource)
128+
if err != nil {
129+
return nil, fmt.Errorf("unable to get client for %s: %v", resource.String(), err)
130+
}
131+
132+
rawObj, err := c.client.clientBase.Get().
133+
AbsPath(path).
134+
Namespace(c.namespace).
135+
Resource(gvr.Resource).
136+
Name(name).
137+
SubResource("scale").
138+
Do().
139+
Get()
140+
141+
if err != nil {
142+
return nil, err
143+
}
144+
145+
// convert whatever this is to autoscaling/v1.Scale
146+
scaleObj, err := scaleConverter.ConvertToVersion(rawObj, autoscaling.SchemeGroupVersion)
147+
if err != nil {
148+
return nil, fmt.Errorf("received an object from a /scale endpoint which was not convertible to autoscaling Scale: %v", err)
149+
}
150+
151+
return scaleObj.(*autoscaling.Scale), nil
152+
}
153+
154+
func (c *namespacedScaleClient) Update(resource schema.GroupResource, scale *autoscaling.Scale) (*autoscaling.Scale, error) {
155+
path, gvr, err := c.client.pathAndVersionFor(resource)
156+
if err != nil {
157+
return nil, fmt.Errorf("unable to get client for %s: %v", resource.String(), err)
158+
}
159+
160+
// Currently, a /scale endpoint can receive and return different scale types.
161+
// Until we hvae support for the alternative API representations proposal,
162+
// we need to deal with sending and accepting differnet API versions.
163+
164+
// figure out what scale we actually need here
165+
desiredGVK, err := c.client.scaleKindResolver.ScaleForResource(gvr)
166+
if err != nil {
167+
return nil, fmt.Errorf("could not find proper group-version for scale subresource of %s: %v", gvr.String(), err)
168+
}
169+
170+
// convert this to whatever this endpoint wants
171+
scaleUpdate, err := scaleConverter.ConvertToVersion(scale, desiredGVK.GroupVersion())
172+
if err != nil {
173+
return nil, fmt.Errorf("could not convert scale update to internal Scale: %v", err)
174+
}
175+
176+
rawObj, err := c.client.clientBase.Put().
177+
AbsPath(path).
178+
Namespace(c.namespace).
179+
Resource(gvr.Resource).
180+
Name(scale.Name).
181+
SubResource("scale").
182+
Body(scaleUpdate).
183+
Do().
184+
Get()
185+
186+
if err != nil {
187+
return nil, fmt.Errorf("could not fetch the scale for %s %s: %v", resource.String(), scale.Name, err)
188+
}
189+
190+
// convert whatever this is back to autoscaling/v1.Scale
191+
scaleObj, err := scaleConverter.ConvertToVersion(rawObj, autoscaling.SchemeGroupVersion)
192+
if err != nil {
193+
return nil, fmt.Errorf("received an object from a /scale endpoint which was not convertible to autoscaling Scale: %v", err)
194+
}
195+
196+
return scaleObj.(*autoscaling.Scale), err
197+
}

0 commit comments

Comments
 (0)