...

Source file src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd.go

Documentation: k8s.io/apiextensions-apiserver/pkg/registry/customresource

     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 customresource
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"strings"
    23  
    24  	autoscalingv1 "k8s.io/api/autoscaling/v1"
    25  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    28  	"k8s.io/apimachinery/pkg/runtime"
    29  	"k8s.io/apimachinery/pkg/runtime/schema"
    30  	"k8s.io/apimachinery/pkg/util/managedfields"
    31  	"k8s.io/apiserver/pkg/registry/generic"
    32  	genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
    33  	"k8s.io/apiserver/pkg/registry/rest"
    34  	"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
    35  )
    36  
    37  // CustomResourceStorage includes dummy storage for CustomResources, and their Status and Scale subresources.
    38  type CustomResourceStorage struct {
    39  	CustomResource *REST
    40  	Status         *StatusREST
    41  	Scale          *ScaleREST
    42  }
    43  
    44  func NewStorage(resource schema.GroupResource, singularResource schema.GroupResource, kind, listKind schema.GroupVersionKind, strategy customResourceStrategy, optsGetter generic.RESTOptionsGetter, categories []string, tableConvertor rest.TableConvertor, replicasPathMapping managedfields.ResourcePathMappings) CustomResourceStorage {
    45  	var storage CustomResourceStorage
    46  	store := &genericregistry.Store{
    47  		NewFunc: func() runtime.Object {
    48  			// set the expected group/version/kind in the new object as a signal to the versioning decoder
    49  			ret := &unstructured.Unstructured{}
    50  			ret.SetGroupVersionKind(kind)
    51  			return ret
    52  		},
    53  		NewListFunc: func() runtime.Object {
    54  			// lists are never stored, only manufactured, so stomp in the right kind
    55  			ret := &unstructured.UnstructuredList{}
    56  			ret.SetGroupVersionKind(listKind)
    57  			return ret
    58  		},
    59  		PredicateFunc:             strategy.MatchCustomResourceDefinitionStorage,
    60  		DefaultQualifiedResource:  resource,
    61  		SingularQualifiedResource: singularResource,
    62  
    63  		CreateStrategy:      strategy,
    64  		UpdateStrategy:      strategy,
    65  		DeleteStrategy:      strategy,
    66  		ResetFieldsStrategy: strategy,
    67  
    68  		TableConvertor: tableConvertor,
    69  	}
    70  	options := &generic.StoreOptions{RESTOptions: optsGetter, AttrFunc: strategy.GetAttrs}
    71  	if err := store.CompleteWithOptions(options); err != nil {
    72  		panic(err) // TODO: Propagate error up
    73  	}
    74  	storage.CustomResource = &REST{store, categories}
    75  
    76  	if strategy.status != nil {
    77  		statusStore := *store
    78  		statusStrategy := NewStatusStrategy(strategy)
    79  		statusStore.UpdateStrategy = statusStrategy
    80  		statusStore.ResetFieldsStrategy = statusStrategy
    81  		storage.Status = &StatusREST{store: &statusStore}
    82  	}
    83  
    84  	if scale := strategy.scale; scale != nil {
    85  		var labelSelectorPath string
    86  		if scale.LabelSelectorPath != nil {
    87  			labelSelectorPath = *scale.LabelSelectorPath
    88  		}
    89  
    90  		storage.Scale = &ScaleREST{
    91  			store:               store,
    92  			specReplicasPath:    scale.SpecReplicasPath,
    93  			statusReplicasPath:  scale.StatusReplicasPath,
    94  			labelSelectorPath:   labelSelectorPath,
    95  			parentGV:            kind.GroupVersion(),
    96  			replicasPathMapping: replicasPathMapping,
    97  		}
    98  	}
    99  
   100  	return storage
   101  }
   102  
   103  // REST implements a RESTStorage for API services against etcd
   104  type REST struct {
   105  	*genericregistry.Store
   106  	categories []string
   107  }
   108  
   109  // Implement CategoriesProvider
   110  var _ rest.CategoriesProvider = &REST{}
   111  
   112  // Categories implements the CategoriesProvider interface. Returns a list of categories a resource is part of.
   113  func (r *REST) Categories() []string {
   114  	return r.categories
   115  }
   116  
   117  // StatusREST implements the REST endpoint for changing the status of a CustomResource
   118  type StatusREST struct {
   119  	store *genericregistry.Store
   120  }
   121  
   122  var _ = rest.Patcher(&StatusREST{})
   123  
   124  func (r *StatusREST) New() runtime.Object {
   125  	return r.store.New()
   126  }
   127  
   128  // Get retrieves the object from the storage. It is required to support Patch.
   129  func (r *StatusREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
   130  	o, err := r.store.Get(ctx, name, options)
   131  	if err != nil {
   132  		return nil, err
   133  	}
   134  	return o, nil
   135  }
   136  
   137  // Update alters the status subset of an object.
   138  func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
   139  	// We are explicitly setting forceAllowCreate to false in the call to the underlying storage because
   140  	// subresources should never allow create on update.
   141  	return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
   142  }
   143  
   144  // GetResetFields implements rest.ResetFieldsStrategy
   145  func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
   146  	return r.store.GetResetFields()
   147  }
   148  
   149  type ScaleREST struct {
   150  	store               *genericregistry.Store
   151  	specReplicasPath    string
   152  	statusReplicasPath  string
   153  	labelSelectorPath   string
   154  	parentGV            schema.GroupVersion
   155  	replicasPathMapping managedfields.ResourcePathMappings
   156  }
   157  
   158  // ScaleREST implements Patcher
   159  var _ = rest.Patcher(&ScaleREST{})
   160  var _ = rest.GroupVersionKindProvider(&ScaleREST{})
   161  
   162  func (r *ScaleREST) GroupVersionKind(containingGV schema.GroupVersion) schema.GroupVersionKind {
   163  	return autoscalingv1.SchemeGroupVersion.WithKind("Scale")
   164  }
   165  
   166  // New creates a new Scale object
   167  func (r *ScaleREST) New() runtime.Object {
   168  	return &autoscalingv1.Scale{}
   169  }
   170  
   171  func (r *ScaleREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
   172  	obj, err := r.store.Get(ctx, name, options)
   173  	if err != nil {
   174  		return nil, err
   175  	}
   176  	cr := obj.(*unstructured.Unstructured)
   177  
   178  	scaleObject, replicasFound, err := scaleFromCustomResource(cr, r.specReplicasPath, r.statusReplicasPath, r.labelSelectorPath)
   179  	if err != nil {
   180  		return nil, err
   181  	}
   182  	if !replicasFound {
   183  		return nil, apierrors.NewInternalError(fmt.Errorf("the spec replicas field %q does not exist", r.specReplicasPath))
   184  	}
   185  	return scaleObject, err
   186  }
   187  
   188  func (r *ScaleREST) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
   189  	scaleObjInfo := &scaleUpdatedObjectInfo{
   190  		reqObjInfo:          objInfo,
   191  		specReplicasPath:    r.specReplicasPath,
   192  		labelSelectorPath:   r.labelSelectorPath,
   193  		statusReplicasPath:  r.statusReplicasPath,
   194  		parentGV:            r.parentGV,
   195  		replicasPathMapping: r.replicasPathMapping,
   196  	}
   197  
   198  	obj, _, err := r.store.Update(
   199  		ctx,
   200  		name,
   201  		scaleObjInfo,
   202  		toScaleCreateValidation(createValidation, r.specReplicasPath, r.statusReplicasPath, r.labelSelectorPath),
   203  		toScaleUpdateValidation(updateValidation, r.specReplicasPath, r.statusReplicasPath, r.labelSelectorPath),
   204  		false,
   205  		options,
   206  	)
   207  	if err != nil {
   208  		return nil, false, err
   209  	}
   210  	cr := obj.(*unstructured.Unstructured)
   211  
   212  	newScale, _, err := scaleFromCustomResource(cr, r.specReplicasPath, r.statusReplicasPath, r.labelSelectorPath)
   213  	if err != nil {
   214  		return nil, false, apierrors.NewBadRequest(err.Error())
   215  	}
   216  
   217  	return newScale, false, err
   218  }
   219  
   220  func toScaleCreateValidation(f rest.ValidateObjectFunc, specReplicasPath, statusReplicasPath, labelSelectorPath string) rest.ValidateObjectFunc {
   221  	return func(ctx context.Context, obj runtime.Object) error {
   222  		scale, _, err := scaleFromCustomResource(obj.(*unstructured.Unstructured), specReplicasPath, statusReplicasPath, labelSelectorPath)
   223  		if err != nil {
   224  			return err
   225  		}
   226  		return f(ctx, scale)
   227  	}
   228  }
   229  
   230  func toScaleUpdateValidation(f rest.ValidateObjectUpdateFunc, specReplicasPath, statusReplicasPath, labelSelectorPath string) rest.ValidateObjectUpdateFunc {
   231  	return func(ctx context.Context, obj, old runtime.Object) error {
   232  		newScale, _, err := scaleFromCustomResource(obj.(*unstructured.Unstructured), specReplicasPath, statusReplicasPath, labelSelectorPath)
   233  		if err != nil {
   234  			return err
   235  		}
   236  		oldScale, _, err := scaleFromCustomResource(old.(*unstructured.Unstructured), specReplicasPath, statusReplicasPath, labelSelectorPath)
   237  		if err != nil {
   238  			return err
   239  		}
   240  		return f(ctx, newScale, oldScale)
   241  	}
   242  }
   243  
   244  // Split the path per period, ignoring the leading period.
   245  func splitReplicasPath(replicasPath string) []string {
   246  	return strings.Split(strings.TrimPrefix(replicasPath, "."), ".")
   247  }
   248  
   249  // scaleFromCustomResource returns a scale subresource for a customresource and a bool signalling wether
   250  // the specReplicas value was found.
   251  func scaleFromCustomResource(cr *unstructured.Unstructured, specReplicasPath, statusReplicasPath, labelSelectorPath string) (*autoscalingv1.Scale, bool, error) {
   252  	specReplicas, foundSpecReplicas, err := unstructured.NestedInt64(cr.UnstructuredContent(), splitReplicasPath(specReplicasPath)...)
   253  	if err != nil {
   254  		return nil, false, err
   255  	} else if !foundSpecReplicas {
   256  		specReplicas = 0
   257  	}
   258  
   259  	statusReplicas, found, err := unstructured.NestedInt64(cr.UnstructuredContent(), splitReplicasPath(statusReplicasPath)...)
   260  	if err != nil {
   261  		return nil, false, err
   262  	} else if !found {
   263  		statusReplicas = 0
   264  	}
   265  
   266  	var labelSelector string
   267  	if len(labelSelectorPath) > 0 {
   268  		labelSelector, _, err = unstructured.NestedString(cr.UnstructuredContent(), splitReplicasPath(labelSelectorPath)...)
   269  		if err != nil {
   270  			return nil, false, err
   271  		}
   272  	}
   273  
   274  	scale := &autoscalingv1.Scale{
   275  		// Populate apiVersion and kind so conversion recognizes we are already in the desired GVK and doesn't try to convert
   276  		TypeMeta: metav1.TypeMeta{
   277  			APIVersion: "autoscaling/v1",
   278  			Kind:       "Scale",
   279  		},
   280  		ObjectMeta: metav1.ObjectMeta{
   281  			Name:              cr.GetName(),
   282  			Namespace:         cr.GetNamespace(),
   283  			UID:               cr.GetUID(),
   284  			ResourceVersion:   cr.GetResourceVersion(),
   285  			CreationTimestamp: cr.GetCreationTimestamp(),
   286  		},
   287  		Spec: autoscalingv1.ScaleSpec{
   288  			Replicas: int32(specReplicas),
   289  		},
   290  		Status: autoscalingv1.ScaleStatus{
   291  			Replicas: int32(statusReplicas),
   292  			Selector: labelSelector,
   293  		},
   294  	}
   295  
   296  	return scale, foundSpecReplicas, nil
   297  }
   298  
   299  type scaleUpdatedObjectInfo struct {
   300  	reqObjInfo          rest.UpdatedObjectInfo
   301  	specReplicasPath    string
   302  	statusReplicasPath  string
   303  	labelSelectorPath   string
   304  	parentGV            schema.GroupVersion
   305  	replicasPathMapping managedfields.ResourcePathMappings
   306  }
   307  
   308  func (i *scaleUpdatedObjectInfo) Preconditions() *metav1.Preconditions {
   309  	return i.reqObjInfo.Preconditions()
   310  }
   311  
   312  func (i *scaleUpdatedObjectInfo) UpdatedObject(ctx context.Context, oldObj runtime.Object) (runtime.Object, error) {
   313  	cr := oldObj.DeepCopyObject().(*unstructured.Unstructured)
   314  	const invalidSpecReplicas = -2147483648 // smallest int32
   315  
   316  	managedFieldsHandler := managedfields.NewScaleHandler(
   317  		cr.GetManagedFields(),
   318  		i.parentGV,
   319  		i.replicasPathMapping,
   320  	)
   321  
   322  	oldScale, replicasFound, err := scaleFromCustomResource(cr, i.specReplicasPath, i.statusReplicasPath, i.labelSelectorPath)
   323  	if err != nil {
   324  		return nil, err
   325  	}
   326  	if !replicasFound {
   327  		oldScale.Spec.Replicas = invalidSpecReplicas // signal that this was not set before
   328  	}
   329  
   330  	scaleManagedFields, err := managedFieldsHandler.ToSubresource()
   331  	if err != nil {
   332  		return nil, err
   333  	}
   334  	oldScale.ManagedFields = scaleManagedFields
   335  
   336  	obj, err := i.reqObjInfo.UpdatedObject(ctx, oldScale)
   337  	if err != nil {
   338  		return nil, err
   339  	}
   340  	if obj == nil {
   341  		return nil, apierrors.NewBadRequest(fmt.Sprintf("nil update passed to Scale"))
   342  	}
   343  
   344  	scale, ok := obj.(*autoscalingv1.Scale)
   345  	if !ok {
   346  		return nil, apierrors.NewBadRequest(fmt.Sprintf("wrong object passed to Scale update: %v", obj))
   347  	}
   348  
   349  	if scale.Spec.Replicas == invalidSpecReplicas {
   350  		return nil, apierrors.NewBadRequest(fmt.Sprintf("the spec replicas field %q cannot be empty", i.specReplicasPath))
   351  	}
   352  
   353  	if err := unstructured.SetNestedField(cr.Object, int64(scale.Spec.Replicas), splitReplicasPath(i.specReplicasPath)...); err != nil {
   354  		return nil, err
   355  	}
   356  	if len(scale.ResourceVersion) != 0 {
   357  		// The client provided a resourceVersion precondition.
   358  		// Set that precondition and return any conflict errors to the client.
   359  		cr.SetResourceVersion(scale.ResourceVersion)
   360  	}
   361  
   362  	updatedEntries, err := managedFieldsHandler.ToParent(scale.ManagedFields)
   363  	if err != nil {
   364  		return nil, err
   365  	}
   366  	cr.SetManagedFields(updatedEntries)
   367  
   368  	return cr, nil
   369  }
   370  

View as plain text