...

Source file src/k8s.io/client-go/scale/client.go

Documentation: k8s.io/client-go/scale

     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  	"context"
    21  	"fmt"
    22  
    23  	autoscaling "k8s.io/api/autoscaling/v1"
    24  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    25  	"k8s.io/apimachinery/pkg/runtime"
    26  	"k8s.io/apimachinery/pkg/runtime/schema"
    27  	serializer "k8s.io/apimachinery/pkg/runtime/serializer"
    28  	"k8s.io/apimachinery/pkg/types"
    29  	"k8s.io/client-go/dynamic"
    30  	restclient "k8s.io/client-go/rest"
    31  )
    32  
    33  var scaleConverter = NewScaleConverter()
    34  var codecs = serializer.NewCodecFactory(scaleConverter.Scheme())
    35  var parameterScheme = runtime.NewScheme()
    36  var dynamicParameterCodec = runtime.NewParameterCodec(parameterScheme)
    37  
    38  var versionV1 = schema.GroupVersion{Version: "v1"}
    39  
    40  func init() {
    41  	metav1.AddToGroupVersion(parameterScheme, versionV1)
    42  }
    43  
    44  // scaleClient is an implementation of ScalesGetter
    45  // which makes use of a RESTMapper and a generic REST
    46  // client to support an discoverable resource.
    47  // It behaves somewhat similarly to the dynamic ClientPool,
    48  // but is more specifically scoped to Scale.
    49  type scaleClient struct {
    50  	mapper PreferredResourceMapper
    51  
    52  	apiPathResolverFunc dynamic.APIPathResolverFunc
    53  	scaleKindResolver   ScaleKindResolver
    54  	clientBase          restclient.Interface
    55  }
    56  
    57  // NewForConfig creates a new ScalesGetter which resolves kinds
    58  // to resources using the given RESTMapper, and API paths using
    59  // the given dynamic.APIPathResolverFunc.
    60  func NewForConfig(cfg *restclient.Config, mapper PreferredResourceMapper, resolver dynamic.APIPathResolverFunc, scaleKindResolver ScaleKindResolver) (ScalesGetter, error) {
    61  	// so that the RESTClientFor doesn't complain
    62  	cfg.GroupVersion = &schema.GroupVersion{}
    63  
    64  	cfg.NegotiatedSerializer = codecs.WithoutConversion()
    65  	if len(cfg.UserAgent) == 0 {
    66  		cfg.UserAgent = restclient.DefaultKubernetesUserAgent()
    67  	}
    68  
    69  	client, err := restclient.RESTClientFor(cfg)
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  
    74  	return New(client, mapper, resolver, scaleKindResolver), nil
    75  }
    76  
    77  // New creates a new ScalesGetter using the given client to make requests.
    78  // The GroupVersion on the client is ignored.
    79  func New(baseClient restclient.Interface, mapper PreferredResourceMapper, resolver dynamic.APIPathResolverFunc, scaleKindResolver ScaleKindResolver) ScalesGetter {
    80  	return &scaleClient{
    81  		mapper: mapper,
    82  
    83  		apiPathResolverFunc: resolver,
    84  		scaleKindResolver:   scaleKindResolver,
    85  		clientBase:          baseClient,
    86  	}
    87  }
    88  
    89  // apiPathFor returns the absolute api path for the given GroupVersion
    90  func (c *scaleClient) apiPathFor(groupVer schema.GroupVersion) string {
    91  	// we need to set the API path based on GroupVersion (defaulting to the legacy path if none is set)
    92  	// TODO: we "cheat" here since the API path really only depends on group ATM, but this should
    93  	// *probably* take GroupVersionResource and not GroupVersionKind.
    94  	apiPath := c.apiPathResolverFunc(groupVer.WithKind(""))
    95  	if apiPath == "" {
    96  		apiPath = "/api"
    97  	}
    98  
    99  	return restclient.DefaultVersionedAPIPath(apiPath, groupVer)
   100  }
   101  
   102  // pathAndVersionFor returns the appropriate base path and the associated full GroupVersionResource
   103  // for the given GroupResource
   104  func (c *scaleClient) pathAndVersionFor(resource schema.GroupResource) (string, schema.GroupVersionResource, error) {
   105  	gvr, err := c.mapper.ResourceFor(resource.WithVersion(""))
   106  	if err != nil {
   107  		return "", gvr, fmt.Errorf("unable to get full preferred group-version-resource for %s: %v", resource.String(), err)
   108  	}
   109  
   110  	groupVer := gvr.GroupVersion()
   111  
   112  	return c.apiPathFor(groupVer), gvr, nil
   113  }
   114  
   115  // namespacedScaleClient is an ScaleInterface for fetching
   116  // Scales in a given namespace.
   117  type namespacedScaleClient struct {
   118  	client    *scaleClient
   119  	namespace string
   120  }
   121  
   122  // convertToScale converts the response body to autoscaling/v1.Scale
   123  func convertToScale(result *restclient.Result) (*autoscaling.Scale, error) {
   124  	scaleBytes, err := result.Raw()
   125  	if err != nil {
   126  		return nil, err
   127  	}
   128  	decoder := scaleConverter.codecs.UniversalDecoder(scaleConverter.ScaleVersions()...)
   129  	rawScaleObj, err := runtime.Decode(decoder, scaleBytes)
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  
   134  	// convert whatever this is to autoscaling/v1.Scale
   135  	scaleObj, err := scaleConverter.ConvertToVersion(rawScaleObj, autoscaling.SchemeGroupVersion)
   136  	if err != nil {
   137  		return nil, fmt.Errorf("received an object from a /scale endpoint which was not convertible to autoscaling Scale: %v", err)
   138  	}
   139  
   140  	return scaleObj.(*autoscaling.Scale), nil
   141  }
   142  
   143  func (c *scaleClient) Scales(namespace string) ScaleInterface {
   144  	return &namespacedScaleClient{
   145  		client:    c,
   146  		namespace: namespace,
   147  	}
   148  }
   149  
   150  func (c *namespacedScaleClient) Get(ctx context.Context, resource schema.GroupResource, name string, opts metav1.GetOptions) (*autoscaling.Scale, error) {
   151  	// Currently, a /scale endpoint can return different scale types.
   152  	// Until we have support for the alternative API representations proposal,
   153  	// we need to deal with accepting different API versions.
   154  	// In practice, this is autoscaling/v1.Scale and extensions/v1beta1.Scale
   155  
   156  	path, gvr, err := c.client.pathAndVersionFor(resource)
   157  	if err != nil {
   158  		return nil, fmt.Errorf("unable to get client for %s: %v", resource.String(), err)
   159  	}
   160  
   161  	result := c.client.clientBase.Get().
   162  		AbsPath(path).
   163  		NamespaceIfScoped(c.namespace, c.namespace != "").
   164  		Resource(gvr.Resource).
   165  		Name(name).
   166  		SubResource("scale").
   167  		SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
   168  		Do(ctx)
   169  	if err := result.Error(); err != nil {
   170  		return nil, err
   171  	}
   172  
   173  	return convertToScale(&result)
   174  }
   175  
   176  func (c *namespacedScaleClient) Update(ctx context.Context, resource schema.GroupResource, scale *autoscaling.Scale, opts metav1.UpdateOptions) (*autoscaling.Scale, error) {
   177  	path, gvr, err := c.client.pathAndVersionFor(resource)
   178  	if err != nil {
   179  		return nil, fmt.Errorf("unable to get client for %s: %v", resource.String(), err)
   180  	}
   181  
   182  	// Currently, a /scale endpoint can receive and return different scale types.
   183  	// Until we have support for the alternative API representations proposal,
   184  	// we need to deal with sending and accepting different API versions.
   185  
   186  	// figure out what scale we actually need here
   187  	desiredGVK, err := c.client.scaleKindResolver.ScaleForResource(gvr)
   188  	if err != nil {
   189  		return nil, fmt.Errorf("could not find proper group-version for scale subresource of %s: %v", gvr.String(), err)
   190  	}
   191  
   192  	// convert this to whatever this endpoint wants
   193  	scaleUpdate, err := scaleConverter.ConvertToVersion(scale, desiredGVK.GroupVersion())
   194  	if err != nil {
   195  		return nil, fmt.Errorf("could not convert scale update to external Scale: %v", err)
   196  	}
   197  	encoder := scaleConverter.codecs.LegacyCodec(desiredGVK.GroupVersion())
   198  	scaleUpdateBytes, err := runtime.Encode(encoder, scaleUpdate)
   199  	if err != nil {
   200  		return nil, fmt.Errorf("could not encode scale update to external Scale: %v", err)
   201  	}
   202  
   203  	result := c.client.clientBase.Put().
   204  		AbsPath(path).
   205  		NamespaceIfScoped(c.namespace, c.namespace != "").
   206  		Resource(gvr.Resource).
   207  		Name(scale.Name).
   208  		SubResource("scale").
   209  		SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
   210  		Body(scaleUpdateBytes).
   211  		Do(ctx)
   212  	if err := result.Error(); err != nil {
   213  		// propagate "raw" error from the API
   214  		// this allows callers to interpret underlying Reason field
   215  		// for example: errors.IsConflict(err)
   216  		return nil, err
   217  	}
   218  
   219  	return convertToScale(&result)
   220  }
   221  
   222  func (c *namespacedScaleClient) Patch(ctx context.Context, gvr schema.GroupVersionResource, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions) (*autoscaling.Scale, error) {
   223  	groupVersion := gvr.GroupVersion()
   224  	result := c.client.clientBase.Patch(pt).
   225  		AbsPath(c.client.apiPathFor(groupVersion)).
   226  		NamespaceIfScoped(c.namespace, c.namespace != "").
   227  		Resource(gvr.Resource).
   228  		Name(name).
   229  		SubResource("scale").
   230  		SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
   231  		Body(data).
   232  		Do(ctx)
   233  	if err := result.Error(); err != nil {
   234  		return nil, err
   235  	}
   236  
   237  	return convertToScale(&result)
   238  }
   239  

View as plain text