...

Source file src/k8s.io/kubernetes/pkg/registry/apps/deployment/storage/storage.go

Documentation: k8s.io/kubernetes/pkg/registry/apps/deployment/storage

     1  /*
     2  Copyright 2015 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 storage
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"net/http"
    23  
    24  	"k8s.io/apimachinery/pkg/api/errors"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/runtime"
    27  	"k8s.io/apimachinery/pkg/runtime/schema"
    28  	"k8s.io/apimachinery/pkg/util/managedfields"
    29  	genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
    30  	"k8s.io/apiserver/pkg/registry/generic"
    31  	genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
    32  	"k8s.io/apiserver/pkg/registry/rest"
    33  	"k8s.io/apiserver/pkg/storage"
    34  	storeerr "k8s.io/apiserver/pkg/storage/errors"
    35  	"k8s.io/apiserver/pkg/util/dryrun"
    36  	"k8s.io/klog/v2"
    37  	"k8s.io/kubernetes/pkg/apis/apps"
    38  	appsv1beta1 "k8s.io/kubernetes/pkg/apis/apps/v1beta1"
    39  	appsv1beta2 "k8s.io/kubernetes/pkg/apis/apps/v1beta2"
    40  	appsvalidation "k8s.io/kubernetes/pkg/apis/apps/validation"
    41  	"k8s.io/kubernetes/pkg/apis/autoscaling"
    42  	autoscalingv1 "k8s.io/kubernetes/pkg/apis/autoscaling/v1"
    43  	autoscalingvalidation "k8s.io/kubernetes/pkg/apis/autoscaling/validation"
    44  	extensionsv1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
    45  	"k8s.io/kubernetes/pkg/printers"
    46  	printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
    47  	printerstorage "k8s.io/kubernetes/pkg/printers/storage"
    48  	"k8s.io/kubernetes/pkg/registry/apps/deployment"
    49  	"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
    50  )
    51  
    52  // DeploymentStorage includes dummy storage for Deployments and for Scale subresource.
    53  type DeploymentStorage struct {
    54  	Deployment *REST
    55  	Status     *StatusREST
    56  	Scale      *ScaleREST
    57  	Rollback   *RollbackREST
    58  }
    59  
    60  // ReplicasPathMappings returns the mappings between each group version and a replicas path
    61  func ReplicasPathMappings() managedfields.ResourcePathMappings {
    62  	return replicasPathInDeployment
    63  }
    64  
    65  // maps a group version to the replicas path in a deployment object
    66  var replicasPathInDeployment = managedfields.ResourcePathMappings{
    67  	schema.GroupVersion{Group: "apps", Version: "v1beta1"}.String(): fieldpath.MakePathOrDie("spec", "replicas"),
    68  	schema.GroupVersion{Group: "apps", Version: "v1beta2"}.String(): fieldpath.MakePathOrDie("spec", "replicas"),
    69  	schema.GroupVersion{Group: "apps", Version: "v1"}.String():      fieldpath.MakePathOrDie("spec", "replicas"),
    70  }
    71  
    72  // NewStorage returns new instance of DeploymentStorage.
    73  func NewStorage(optsGetter generic.RESTOptionsGetter) (DeploymentStorage, error) {
    74  	deploymentRest, deploymentStatusRest, deploymentRollbackRest, err := NewREST(optsGetter)
    75  	if err != nil {
    76  		return DeploymentStorage{}, err
    77  	}
    78  
    79  	return DeploymentStorage{
    80  		Deployment: deploymentRest,
    81  		Status:     deploymentStatusRest,
    82  		Scale:      &ScaleREST{store: deploymentRest.Store},
    83  		Rollback:   deploymentRollbackRest,
    84  	}, nil
    85  }
    86  
    87  // REST implements a RESTStorage for Deployments.
    88  type REST struct {
    89  	*genericregistry.Store
    90  }
    91  
    92  // NewREST returns a RESTStorage object that will work against deployments.
    93  func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, *RollbackREST, error) {
    94  	store := &genericregistry.Store{
    95  		NewFunc:                   func() runtime.Object { return &apps.Deployment{} },
    96  		NewListFunc:               func() runtime.Object { return &apps.DeploymentList{} },
    97  		DefaultQualifiedResource:  apps.Resource("deployments"),
    98  		SingularQualifiedResource: apps.Resource("deployment"),
    99  
   100  		CreateStrategy:      deployment.Strategy,
   101  		UpdateStrategy:      deployment.Strategy,
   102  		DeleteStrategy:      deployment.Strategy,
   103  		ResetFieldsStrategy: deployment.Strategy,
   104  
   105  		TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
   106  	}
   107  	options := &generic.StoreOptions{RESTOptions: optsGetter}
   108  	if err := store.CompleteWithOptions(options); err != nil {
   109  		return nil, nil, nil, err
   110  	}
   111  
   112  	statusStore := *store
   113  	statusStore.UpdateStrategy = deployment.StatusStrategy
   114  	statusStore.ResetFieldsStrategy = deployment.StatusStrategy
   115  	return &REST{store}, &StatusREST{store: &statusStore}, &RollbackREST{store: store}, nil
   116  }
   117  
   118  // Implement ShortNamesProvider
   119  var _ rest.ShortNamesProvider = &REST{}
   120  
   121  // ShortNames implements the ShortNamesProvider interface. Returns a list of short names for a resource.
   122  func (r *REST) ShortNames() []string {
   123  	return []string{"deploy"}
   124  }
   125  
   126  // Implement CategoriesProvider
   127  var _ rest.CategoriesProvider = &REST{}
   128  
   129  // Categories implements the CategoriesProvider interface. Returns a list of categories a resource is part of.
   130  func (r *REST) Categories() []string {
   131  	return []string{"all"}
   132  }
   133  
   134  // StatusREST implements the REST endpoint for changing the status of a deployment
   135  type StatusREST struct {
   136  	store *genericregistry.Store
   137  }
   138  
   139  // New returns empty Deployment object.
   140  func (r *StatusREST) New() runtime.Object {
   141  	return &apps.Deployment{}
   142  }
   143  
   144  // Destroy cleans up resources on shutdown.
   145  func (r *StatusREST) Destroy() {
   146  	// Given that underlying store is shared with REST,
   147  	// we don't destroy it here explicitly.
   148  }
   149  
   150  // Get retrieves the object from the storage. It is required to support Patch.
   151  func (r *StatusREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
   152  	return r.store.Get(ctx, name, options)
   153  }
   154  
   155  // Update alters the status subset of an object.
   156  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) {
   157  	// We are explicitly setting forceAllowCreate to false in the call to the underlying storage because
   158  	// subresources should never allow create on update.
   159  	return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
   160  }
   161  
   162  // GetResetFields implements rest.ResetFieldsStrategy
   163  func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
   164  	return r.store.GetResetFields()
   165  }
   166  
   167  func (r *StatusREST) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
   168  	return r.store.ConvertToTable(ctx, object, tableOptions)
   169  }
   170  
   171  // RollbackREST implements the REST endpoint for initiating the rollback of a deployment
   172  type RollbackREST struct {
   173  	store *genericregistry.Store
   174  }
   175  
   176  // ProducesMIMETypes returns a list of the MIME types the specified HTTP verb (GET, POST, DELETE,
   177  // PATCH) can respond with.
   178  func (r *RollbackREST) ProducesMIMETypes(verb string) []string {
   179  	return nil
   180  }
   181  
   182  // ProducesObject returns an object the specified HTTP verb respond with. It will overwrite storage object if
   183  // it is not nil. Only the type of the return object matters, the value will be ignored.
   184  func (r *RollbackREST) ProducesObject(verb string) interface{} {
   185  	return metav1.Status{}
   186  }
   187  
   188  var _ = rest.StorageMetadata(&RollbackREST{})
   189  
   190  // New creates a rollback
   191  func (r *RollbackREST) New() runtime.Object {
   192  	return &apps.DeploymentRollback{}
   193  }
   194  
   195  // Destroy cleans up resources on shutdown.
   196  func (r *RollbackREST) Destroy() {
   197  	// Given that underlying store is shared with REST,
   198  	// we don't destroy it here explicitly.
   199  }
   200  
   201  var _ = rest.NamedCreater(&RollbackREST{})
   202  
   203  // Create runs rollback for deployment
   204  func (r *RollbackREST) Create(ctx context.Context, name string, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
   205  	rollback, ok := obj.(*apps.DeploymentRollback)
   206  	if !ok {
   207  		return nil, errors.NewBadRequest(fmt.Sprintf("not a DeploymentRollback: %#v", obj))
   208  	}
   209  
   210  	if errs := appsvalidation.ValidateDeploymentRollback(rollback); len(errs) != 0 {
   211  		return nil, errors.NewInvalid(apps.Kind("DeploymentRollback"), rollback.Name, errs)
   212  	}
   213  	if name != rollback.Name {
   214  		return nil, errors.NewBadRequest("name in URL does not match name in DeploymentRollback object")
   215  	}
   216  
   217  	if createValidation != nil {
   218  		if err := createValidation(ctx, obj.DeepCopyObject()); err != nil {
   219  			return nil, err
   220  		}
   221  	}
   222  
   223  	// Update the Deployment with information in DeploymentRollback to trigger rollback
   224  	err := r.rollbackDeployment(ctx, rollback.Name, &rollback.RollbackTo, rollback.UpdatedAnnotations, dryrun.IsDryRun(options.DryRun))
   225  	if err != nil {
   226  		return nil, err
   227  	}
   228  	return &metav1.Status{
   229  		Status:  metav1.StatusSuccess,
   230  		Message: fmt.Sprintf("rollback request for deployment %q succeeded", rollback.Name),
   231  		Code:    http.StatusOK,
   232  	}, nil
   233  }
   234  
   235  func (r *RollbackREST) rollbackDeployment(ctx context.Context, deploymentID string, config *apps.RollbackConfig, annotations map[string]string, dryRun bool) error {
   236  	if _, err := r.setDeploymentRollback(ctx, deploymentID, config, annotations, dryRun); err != nil {
   237  		err = storeerr.InterpretGetError(err, apps.Resource("deployments"), deploymentID)
   238  		err = storeerr.InterpretUpdateError(err, apps.Resource("deployments"), deploymentID)
   239  		if _, ok := err.(*errors.StatusError); !ok {
   240  			err = errors.NewInternalError(err)
   241  		}
   242  		return err
   243  	}
   244  	return nil
   245  }
   246  
   247  func (r *RollbackREST) setDeploymentRollback(ctx context.Context, deploymentID string, config *apps.RollbackConfig, annotations map[string]string, dryRun bool) (*apps.Deployment, error) {
   248  	dKey, err := r.store.KeyFunc(ctx, deploymentID)
   249  	if err != nil {
   250  		return nil, err
   251  	}
   252  	var finalDeployment *apps.Deployment
   253  	err = r.store.Storage.GuaranteedUpdate(ctx, dKey, &apps.Deployment{}, false, nil, storage.SimpleUpdate(func(obj runtime.Object) (runtime.Object, error) {
   254  		d, ok := obj.(*apps.Deployment)
   255  		if !ok {
   256  			return nil, fmt.Errorf("unexpected object: %#v", obj)
   257  		}
   258  		if d.Annotations == nil {
   259  			d.Annotations = make(map[string]string)
   260  		}
   261  		for k, v := range annotations {
   262  			d.Annotations[k] = v
   263  		}
   264  		d.Spec.RollbackTo = config
   265  		finalDeployment = d
   266  		return d, nil
   267  	}), dryRun, nil)
   268  	return finalDeployment, err
   269  }
   270  
   271  // ScaleREST implements a Scale for Deployment.
   272  type ScaleREST struct {
   273  	store *genericregistry.Store
   274  }
   275  
   276  // ScaleREST implements Patcher
   277  var _ = rest.Patcher(&ScaleREST{})
   278  var _ = rest.GroupVersionKindProvider(&ScaleREST{})
   279  
   280  // GroupVersionKind returns GroupVersionKind for Deployment Scale object
   281  func (r *ScaleREST) GroupVersionKind(containingGV schema.GroupVersion) schema.GroupVersionKind {
   282  	switch containingGV {
   283  	case extensionsv1beta1.SchemeGroupVersion:
   284  		return extensionsv1beta1.SchemeGroupVersion.WithKind("Scale")
   285  	case appsv1beta1.SchemeGroupVersion:
   286  		return appsv1beta1.SchemeGroupVersion.WithKind("Scale")
   287  	case appsv1beta2.SchemeGroupVersion:
   288  		return appsv1beta2.SchemeGroupVersion.WithKind("Scale")
   289  	default:
   290  		return autoscalingv1.SchemeGroupVersion.WithKind("Scale")
   291  	}
   292  }
   293  
   294  // New creates a new Scale object
   295  func (r *ScaleREST) New() runtime.Object {
   296  	return &autoscaling.Scale{}
   297  }
   298  
   299  // Destroy cleans up resources on shutdown.
   300  func (r *ScaleREST) Destroy() {
   301  	// Given that underlying store is shared with REST,
   302  	// we don't destroy it here explicitly.
   303  }
   304  
   305  // Get retrieves object from Scale storage.
   306  func (r *ScaleREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
   307  	obj, err := r.store.Get(ctx, name, options)
   308  	if err != nil {
   309  		return nil, errors.NewNotFound(apps.Resource("deployments/scale"), name)
   310  	}
   311  	deployment := obj.(*apps.Deployment)
   312  	scale, err := scaleFromDeployment(deployment)
   313  	if err != nil {
   314  		return nil, errors.NewBadRequest(fmt.Sprintf("%v", err))
   315  	}
   316  	return scale, nil
   317  }
   318  
   319  // Update alters scale subset of Deployment object.
   320  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) {
   321  	obj, _, err := r.store.Update(
   322  		ctx,
   323  		name,
   324  		&scaleUpdatedObjectInfo{name, objInfo},
   325  		toScaleCreateValidation(createValidation),
   326  		toScaleUpdateValidation(updateValidation),
   327  		false,
   328  		options,
   329  	)
   330  	if err != nil {
   331  		return nil, false, err
   332  	}
   333  	deployment := obj.(*apps.Deployment)
   334  	newScale, err := scaleFromDeployment(deployment)
   335  	if err != nil {
   336  		return nil, false, errors.NewBadRequest(fmt.Sprintf("%v", err))
   337  	}
   338  	return newScale, false, nil
   339  }
   340  
   341  func (r *ScaleREST) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
   342  	return r.store.ConvertToTable(ctx, object, tableOptions)
   343  }
   344  
   345  func toScaleCreateValidation(f rest.ValidateObjectFunc) rest.ValidateObjectFunc {
   346  	return func(ctx context.Context, obj runtime.Object) error {
   347  		scale, err := scaleFromDeployment(obj.(*apps.Deployment))
   348  		if err != nil {
   349  			return err
   350  		}
   351  		return f(ctx, scale)
   352  	}
   353  }
   354  
   355  func toScaleUpdateValidation(f rest.ValidateObjectUpdateFunc) rest.ValidateObjectUpdateFunc {
   356  	return func(ctx context.Context, obj, old runtime.Object) error {
   357  		newScale, err := scaleFromDeployment(obj.(*apps.Deployment))
   358  		if err != nil {
   359  			return err
   360  		}
   361  		oldScale, err := scaleFromDeployment(old.(*apps.Deployment))
   362  		if err != nil {
   363  			return err
   364  		}
   365  		return f(ctx, newScale, oldScale)
   366  	}
   367  }
   368  
   369  // scaleFromDeployment returns a scale subresource for a deployment.
   370  func scaleFromDeployment(deployment *apps.Deployment) (*autoscaling.Scale, error) {
   371  	selector, err := metav1.LabelSelectorAsSelector(deployment.Spec.Selector)
   372  	if err != nil {
   373  		return nil, err
   374  	}
   375  
   376  	return &autoscaling.Scale{
   377  		// TODO: Create a variant of ObjectMeta type that only contains the fields below.
   378  		ObjectMeta: metav1.ObjectMeta{
   379  			Name:              deployment.Name,
   380  			Namespace:         deployment.Namespace,
   381  			UID:               deployment.UID,
   382  			ResourceVersion:   deployment.ResourceVersion,
   383  			CreationTimestamp: deployment.CreationTimestamp,
   384  		},
   385  		Spec: autoscaling.ScaleSpec{
   386  			Replicas: deployment.Spec.Replicas,
   387  		},
   388  		Status: autoscaling.ScaleStatus{
   389  			Replicas: deployment.Status.Replicas,
   390  			Selector: selector.String(),
   391  		},
   392  	}, nil
   393  }
   394  
   395  // scaleUpdatedObjectInfo transforms existing deployment -> existing scale -> new scale -> new deployment
   396  type scaleUpdatedObjectInfo struct {
   397  	name       string
   398  	reqObjInfo rest.UpdatedObjectInfo
   399  }
   400  
   401  func (i *scaleUpdatedObjectInfo) Preconditions() *metav1.Preconditions {
   402  	return i.reqObjInfo.Preconditions()
   403  }
   404  
   405  func (i *scaleUpdatedObjectInfo) UpdatedObject(ctx context.Context, oldObj runtime.Object) (runtime.Object, error) {
   406  	deployment, ok := oldObj.DeepCopyObject().(*apps.Deployment)
   407  	if !ok {
   408  		return nil, errors.NewBadRequest(fmt.Sprintf("expected existing object type to be Deployment, got %T", deployment))
   409  	}
   410  	// if zero-value, the existing object does not exist
   411  	if len(deployment.ResourceVersion) == 0 {
   412  		return nil, errors.NewNotFound(apps.Resource("deployments/scale"), i.name)
   413  	}
   414  
   415  	groupVersion := schema.GroupVersion{Group: "apps", Version: "v1"}
   416  	if requestInfo, found := genericapirequest.RequestInfoFrom(ctx); found {
   417  		requestGroupVersion := schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion}
   418  		if _, ok := replicasPathInDeployment[requestGroupVersion.String()]; ok {
   419  			groupVersion = requestGroupVersion
   420  		} else {
   421  			klog.Fatalf("Unrecognized group/version in request info %q", requestGroupVersion.String())
   422  		}
   423  	}
   424  
   425  	managedFieldsHandler := managedfields.NewScaleHandler(
   426  		deployment.ManagedFields,
   427  		groupVersion,
   428  		replicasPathInDeployment,
   429  	)
   430  
   431  	// deployment -> old scale
   432  	oldScale, err := scaleFromDeployment(deployment)
   433  	if err != nil {
   434  		return nil, err
   435  	}
   436  	scaleManagedFields, err := managedFieldsHandler.ToSubresource()
   437  	if err != nil {
   438  		return nil, err
   439  	}
   440  	oldScale.ManagedFields = scaleManagedFields
   441  
   442  	// old scale -> new scale
   443  	newScaleObj, err := i.reqObjInfo.UpdatedObject(ctx, oldScale)
   444  	if err != nil {
   445  		return nil, err
   446  	}
   447  	if newScaleObj == nil {
   448  		return nil, errors.NewBadRequest("nil update passed to Scale")
   449  	}
   450  	scale, ok := newScaleObj.(*autoscaling.Scale)
   451  	if !ok {
   452  		return nil, errors.NewBadRequest(fmt.Sprintf("expected input object type to be Scale, but %T", newScaleObj))
   453  	}
   454  
   455  	// validate
   456  	if errs := autoscalingvalidation.ValidateScale(scale); len(errs) > 0 {
   457  		return nil, errors.NewInvalid(autoscaling.Kind("Scale"), deployment.Name, errs)
   458  	}
   459  
   460  	// validate precondition if specified (resourceVersion matching is handled by storage)
   461  	if len(scale.UID) > 0 && scale.UID != deployment.UID {
   462  		return nil, errors.NewConflict(
   463  			apps.Resource("deployments/scale"),
   464  			deployment.Name,
   465  			fmt.Errorf("Precondition failed: UID in precondition: %v, UID in object meta: %v", scale.UID, deployment.UID),
   466  		)
   467  	}
   468  
   469  	// move replicas/resourceVersion fields to object and return
   470  	deployment.Spec.Replicas = scale.Spec.Replicas
   471  	deployment.ResourceVersion = scale.ResourceVersion
   472  
   473  	updatedEntries, err := managedFieldsHandler.ToParent(scale.ManagedFields)
   474  	if err != nil {
   475  		return nil, err
   476  	}
   477  	deployment.ManagedFields = updatedEntries
   478  
   479  	return deployment, nil
   480  }
   481  

View as plain text