mirror of
https://github.com/ceph/ceph-csi.git
synced 2024-12-25 14:30:22 +00:00
222 lines
7.5 KiB
Go
222 lines
7.5 KiB
Go
|
/*
|
||
|
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/runtime"
|
||
|
"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 PreferredResourceMapper
|
||
|
|
||
|
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 PreferredResourceMapper, 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 PreferredResourceMapper, 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)
|
||
|
}
|
||
|
|
||
|
result := c.client.clientBase.Get().
|
||
|
AbsPath(path).
|
||
|
Namespace(c.namespace).
|
||
|
Resource(gvr.Resource).
|
||
|
Name(name).
|
||
|
SubResource("scale").
|
||
|
Do()
|
||
|
if err := result.Error(); err != nil {
|
||
|
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
|
||
|
}
|
||
|
|
||
|
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 have support for the alternative API representations proposal,
|
||
|
// we need to deal with sending and accepting different 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 external Scale: %v", err)
|
||
|
}
|
||
|
encoder := scaleConverter.codecs.LegacyCodec(desiredGVK.GroupVersion())
|
||
|
scaleUpdateBytes, err := runtime.Encode(encoder, scaleUpdate)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("could not encode scale update to external Scale: %v", err)
|
||
|
}
|
||
|
|
||
|
result := c.client.clientBase.Put().
|
||
|
AbsPath(path).
|
||
|
Namespace(c.namespace).
|
||
|
Resource(gvr.Resource).
|
||
|
Name(scale.Name).
|
||
|
SubResource("scale").
|
||
|
Body(scaleUpdateBytes).
|
||
|
Do()
|
||
|
if err := result.Error(); err != nil {
|
||
|
// propagate "raw" error from the API
|
||
|
// this allows callers to interpret underlying Reason field
|
||
|
// for example: errors.IsConflict(err)
|
||
|
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
|
||
|
}
|