feat(scale): add Patch method to ScaleInterface

Signed-off-by: knight42 <anonymousknight96@gmail.com>

Kubernetes-commit: f020c9159869918c63e0aad56abd01e655e61e78
This commit is contained in:
knight42 2019-07-29 12:33:24 +08:00 committed by Kubernetes Publisher
parent 396a06da3b
commit 70681df7b9
4 changed files with 160 additions and 50 deletions

View File

@ -23,6 +23,7 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
serializer "k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/dynamic"
restclient "k8s.io/client-go/rest"
)
@ -75,6 +76,19 @@ func New(baseClient restclient.Interface, mapper PreferredResourceMapper, resolv
}
}
// apiPathFor returns the absolute api path for the given GroupVersion
func (c *scaleClient) apiPathFor(groupVer schema.GroupVersion) string {
// 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"
}
return restclient.DefaultVersionedAPIPath(apiPath, groupVer)
}
// 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) {
@ -85,17 +99,7 @@ func (c *scaleClient) pathAndVersionFor(resource schema.GroupResource) (string,
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
return c.apiPathFor(groupVer), gvr, nil
}
// namespacedScaleClient is an ScaleInterface for fetching
@ -105,6 +109,27 @@ type namespacedScaleClient struct {
namespace string
}
// convertToScale converts the response body to autoscaling/v1.Scale
func convertToScale(result *restclient.Result) (*autoscaling.Scale, error) {
scaleBytes, err := result.Raw()
if err != nil {
return nil, err
}
decoder := scaleConverter.codecs.UniversalDecoder(scaleConverter.ScaleVersions()...)
rawScaleObj, err := runtime.Decode(decoder, scaleBytes)
if err != nil {
return nil, err
}
// convert whatever this is to autoscaling/v1.Scale
scaleObj, err := scaleConverter.ConvertToVersion(rawScaleObj, 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 *scaleClient) Scales(namespace string) ScaleInterface {
return &namespacedScaleClient{
client: c,
@ -134,23 +159,7 @@ func (c *namespacedScaleClient) Get(resource schema.GroupResource, name string)
return nil, err
}
scaleBytes, err := result.Raw()
if err != nil {
return nil, err
}
decoder := scaleConverter.codecs.UniversalDecoder(scaleConverter.ScaleVersions()...)
rawScaleObj, err := runtime.Decode(decoder, scaleBytes)
if err != nil {
return nil, err
}
// convert whatever this is to autoscaling/v1.Scale
scaleObj, err := scaleConverter.ConvertToVersion(rawScaleObj, 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
return convertToScale(&result)
}
func (c *namespacedScaleClient) Update(resource schema.GroupResource, scale *autoscaling.Scale) (*autoscaling.Scale, error) {
@ -195,21 +204,22 @@ func (c *namespacedScaleClient) Update(resource schema.GroupResource, scale *aut
return nil, err
}
scaleBytes, err := result.Raw()
if err != nil {
return nil, err
}
decoder := scaleConverter.codecs.UniversalDecoder(scaleConverter.ScaleVersions()...)
rawScaleObj, err := runtime.Decode(decoder, scaleBytes)
if err != nil {
return nil, err
}
// convert whatever this is back to autoscaling/v1.Scale
scaleObj, err := scaleConverter.ConvertToVersion(rawScaleObj, 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
return convertToScale(&result)
}
func (c *namespacedScaleClient) Patch(gvr schema.GroupVersionResource, name string, pt types.PatchType, data []byte) (*autoscaling.Scale, error) {
groupVersion := gvr.GroupVersion()
result := c.client.clientBase.Patch(pt).
AbsPath(c.client.apiPathFor(groupVersion)).
Namespace(c.namespace).
Resource(gvr.Resource).
Name(name).
SubResource("scale").
Body(data).
Do()
if err := result.Error(); err != nil {
return nil, err
}
return convertToScale(&result)
}

View File

@ -25,9 +25,11 @@ import (
"net/http"
"testing"
jsonpatch "github.com/evanphx/json-patch"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
fakedisco "k8s.io/client-go/discovery/fake"
"k8s.io/client-go/dynamic"
fakerest "k8s.io/client-go/rest/fake"
@ -197,6 +199,37 @@ func fakeScaleClient(t *testing.T) (ScalesGetter, []schema.GroupResource) {
return nil, err
}
return &http.Response{StatusCode: 200, Header: defaultHeaders(), Body: bytesBody(res)}, nil
case "PATCH":
body, err := ioutil.ReadAll(req.Body)
if err != nil {
return nil, err
}
originScale, err := json.Marshal(scale)
if err != nil {
return nil, err
}
var res []byte
contentType := req.Header.Get("Content-Type")
pt := types.PatchType(contentType)
switch pt {
case types.MergePatchType:
res, err = jsonpatch.MergePatch(originScale, body)
if err != nil {
return nil, err
}
case types.JSONPatchType:
patch, err := jsonpatch.DecodePatch(body)
if err != nil {
return nil, err
}
res, err = patch.Apply(originScale)
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("invalid patch type")
}
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)
}
@ -213,10 +246,10 @@ func fakeScaleClient(t *testing.T) (ScalesGetter, []schema.GroupResource) {
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"},
{Group: corev1.GroupName, Resource: "replicationcontrollers"},
{Group: extv1beta1.GroupName, Resource: "replicasets"},
{Group: appsv1beta2.GroupName, Resource: "deployments"},
{Group: "cheese.testing.k8s.io", Resource: "cheddars"},
}
return client, groupResources
@ -277,3 +310,55 @@ func TestUpdateScale(t *testing.T) {
assert.Equal(t, expectedScale, scale, "should have returned the expected scale for %s", groupResource.String())
}
}
func TestPatchScale(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: 5},
Status: autoscalingv1.ScaleStatus{
Replicas: 10,
Selector: "foo=bar",
},
}
gvrs := make([]schema.GroupVersionResource, 0, len(groupResources))
for _, gr := range groupResources {
switch gr.Group {
case corev1.GroupName:
gvrs = append(gvrs, gr.WithVersion(corev1.SchemeGroupVersion.Version))
case extv1beta1.GroupName:
gvrs = append(gvrs, gr.WithVersion(extv1beta1.SchemeGroupVersion.Version))
case appsv1beta2.GroupName:
gvrs = append(gvrs, gr.WithVersion(appsv1beta2.SchemeGroupVersion.Version))
default:
// Group cheese.testing.k8s.io
gvrs = append(gvrs, gr.WithVersion("v27alpha15"))
}
}
patch := []byte(`{"spec":{"replicas":5}}`)
for _, gvr := range gvrs {
scale, err := scaleClient.Scales("default").Patch(gvr, "foo", types.MergePatchType, patch)
if !assert.NoError(t, err, "should have been able to fetch a scale for %s", gvr.String()) {
continue
}
assert.NotNil(t, scale, "should have returned a non-nil scale for %s", gvr.String())
assert.Equal(t, expectedScale, scale, "should have returned the expected scale for %s", gvr.String())
}
patch = []byte(`[{"op":"replace","path":"/spec/replicas","value":5}]`)
for _, gvr := range gvrs {
scale, err := scaleClient.Scales("default").Patch(gvr, "foo", types.JSONPatchType, patch)
if !assert.NoError(t, err, "should have been able to fetch a scale for %s", gvr.String()) {
continue
}
assert.NotNil(t, scale, "should have returned a non-nil scale for %s", gvr.String())
assert.Equal(t, expectedScale, scale, "should have returned the expected scale for %s", gvr.String())
}
}

View File

@ -22,6 +22,7 @@ package fake
import (
autoscalingapi "k8s.io/api/autoscaling/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/scale"
"k8s.io/client-go/testing"
)
@ -63,5 +64,15 @@ func (f *fakeNamespacedScaleClient) Update(resource schema.GroupResource, scale
}
return obj.(*autoscalingapi.Scale), err
}
func (f *fakeNamespacedScaleClient) Patch(gvr schema.GroupVersionResource, name string, pt types.PatchType, patch []byte) (*autoscalingapi.Scale, error) {
obj, err := f.fake.
Invokes(testing.NewPatchSubresourceAction(gvr, f.namespace, name, pt, patch, "scale"), &autoscalingapi.Scale{})
if err != nil {
return nil, err
}
return obj.(*autoscalingapi.Scale), err
}

View File

@ -19,6 +19,7 @@ package scale
import (
autoscalingapi "k8s.io/api/autoscaling/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
)
// ScalesGetter can produce a ScaleInterface
@ -36,4 +37,7 @@ type ScaleInterface interface {
// Update updates the scale of the given scalable resource.
Update(resource schema.GroupResource, scale *autoscalingapi.Scale) (*autoscalingapi.Scale, error)
// Patch patches the scale of the given scalable resource.
Patch(gvr schema.GroupVersionResource, name string, pt types.PatchType, data []byte) (*autoscalingapi.Scale, error)
}