k8s.io/kubernetes@v1.29.3/pkg/registry/apps/replicaset/storage/storage.go (about) 1 /* 2 Copyright 2016 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 // If you make changes to this file, you should also make the corresponding change in ReplicationController. 18 19 package storage 20 21 import ( 22 "context" 23 "fmt" 24 25 "k8s.io/apimachinery/pkg/api/errors" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/runtime" 28 "k8s.io/apimachinery/pkg/runtime/schema" 29 "k8s.io/apimachinery/pkg/util/managedfields" 30 genericapirequest "k8s.io/apiserver/pkg/endpoints/request" 31 "k8s.io/apiserver/pkg/registry/generic" 32 genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" 33 "k8s.io/apiserver/pkg/registry/rest" 34 "k8s.io/klog/v2" 35 "k8s.io/kubernetes/pkg/apis/apps" 36 appsv1beta1 "k8s.io/kubernetes/pkg/apis/apps/v1beta1" 37 appsv1beta2 "k8s.io/kubernetes/pkg/apis/apps/v1beta2" 38 "k8s.io/kubernetes/pkg/apis/autoscaling" 39 autoscalingv1 "k8s.io/kubernetes/pkg/apis/autoscaling/v1" 40 autoscalingvalidation "k8s.io/kubernetes/pkg/apis/autoscaling/validation" 41 extensionsv1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" 42 "k8s.io/kubernetes/pkg/printers" 43 printersinternal "k8s.io/kubernetes/pkg/printers/internalversion" 44 printerstorage "k8s.io/kubernetes/pkg/printers/storage" 45 "k8s.io/kubernetes/pkg/registry/apps/replicaset" 46 "sigs.k8s.io/structured-merge-diff/v4/fieldpath" 47 ) 48 49 // ReplicaSetStorage includes dummy storage for ReplicaSets and for Scale subresource. 50 type ReplicaSetStorage struct { 51 ReplicaSet *REST 52 Status *StatusREST 53 Scale *ScaleREST 54 } 55 56 // ReplicasPathMappings returns the mappings between each group version and a replicas path 57 func ReplicasPathMappings() managedfields.ResourcePathMappings { 58 return replicasPathInReplicaSet 59 } 60 61 // maps a group version to the replicas path in a replicaset object 62 var replicasPathInReplicaSet = managedfields.ResourcePathMappings{ 63 schema.GroupVersion{Group: "apps", Version: "v1beta2"}.String(): fieldpath.MakePathOrDie("spec", "replicas"), 64 schema.GroupVersion{Group: "apps", Version: "v1"}.String(): fieldpath.MakePathOrDie("spec", "replicas"), 65 } 66 67 // NewStorage returns new instance of ReplicaSetStorage. 68 func NewStorage(optsGetter generic.RESTOptionsGetter) (ReplicaSetStorage, error) { 69 replicaSetRest, replicaSetStatusRest, err := NewREST(optsGetter) 70 if err != nil { 71 return ReplicaSetStorage{}, err 72 } 73 74 return ReplicaSetStorage{ 75 ReplicaSet: replicaSetRest, 76 Status: replicaSetStatusRest, 77 Scale: &ScaleREST{store: replicaSetRest.Store}, 78 }, nil 79 } 80 81 // REST implements a RESTStorage for ReplicaSet. 82 type REST struct { 83 *genericregistry.Store 84 } 85 86 // NewREST returns a RESTStorage object that will work against ReplicaSet. 87 func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) { 88 store := &genericregistry.Store{ 89 NewFunc: func() runtime.Object { return &apps.ReplicaSet{} }, 90 NewListFunc: func() runtime.Object { return &apps.ReplicaSetList{} }, 91 PredicateFunc: replicaset.MatchReplicaSet, 92 DefaultQualifiedResource: apps.Resource("replicasets"), 93 SingularQualifiedResource: apps.Resource("replicaset"), 94 95 CreateStrategy: replicaset.Strategy, 96 UpdateStrategy: replicaset.Strategy, 97 DeleteStrategy: replicaset.Strategy, 98 ResetFieldsStrategy: replicaset.Strategy, 99 100 TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)}, 101 } 102 options := &generic.StoreOptions{RESTOptions: optsGetter, AttrFunc: replicaset.GetAttrs} 103 if err := store.CompleteWithOptions(options); err != nil { 104 return nil, nil, err 105 } 106 107 statusStore := *store 108 statusStore.UpdateStrategy = replicaset.StatusStrategy 109 statusStore.ResetFieldsStrategy = replicaset.StatusStrategy 110 111 return &REST{store}, &StatusREST{store: &statusStore}, nil 112 } 113 114 // Implement ShortNamesProvider 115 var _ rest.ShortNamesProvider = &REST{} 116 117 // ShortNames implements the ShortNamesProvider interface. Returns a list of short names for a resource. 118 func (r *REST) ShortNames() []string { 119 return []string{"rs"} 120 } 121 122 // Implement CategoriesProvider 123 var _ rest.CategoriesProvider = &REST{} 124 125 // Categories implements the CategoriesProvider interface. Returns a list of categories a resource is part of. 126 func (r *REST) Categories() []string { 127 return []string{"all"} 128 } 129 130 // StatusREST implements the REST endpoint for changing the status of a ReplicaSet 131 type StatusREST struct { 132 store *genericregistry.Store 133 } 134 135 // New returns empty ReplicaSet object. 136 func (r *StatusREST) New() runtime.Object { 137 return &apps.ReplicaSet{} 138 } 139 140 // Destroy cleans up resources on shutdown. 141 func (r *StatusREST) Destroy() { 142 // Given that underlying store is shared with REST, 143 // we don't destroy it here explicitly. 144 } 145 146 // Get retrieves the object from the storage. It is required to support Patch. 147 func (r *StatusREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { 148 return r.store.Get(ctx, name, options) 149 } 150 151 // Update alters the status subset of an object. 152 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) { 153 // We are explicitly setting forceAllowCreate to false in the call to the underlying storage because 154 // subresources should never allow create on update. 155 return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options) 156 } 157 158 // GetResetFields implements rest.ResetFieldsStrategy 159 func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set { 160 return r.store.GetResetFields() 161 } 162 163 func (r *StatusREST) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { 164 return r.store.ConvertToTable(ctx, object, tableOptions) 165 } 166 167 // ScaleREST implements a Scale for ReplicaSet. 168 type ScaleREST struct { 169 store *genericregistry.Store 170 } 171 172 // ScaleREST implements Patcher 173 var _ = rest.Patcher(&ScaleREST{}) 174 var _ = rest.GroupVersionKindProvider(&ScaleREST{}) 175 176 // GroupVersionKind returns GroupVersionKind for ReplicaSet Scale object 177 func (r *ScaleREST) GroupVersionKind(containingGV schema.GroupVersion) schema.GroupVersionKind { 178 switch containingGV { 179 case extensionsv1beta1.SchemeGroupVersion: 180 return extensionsv1beta1.SchemeGroupVersion.WithKind("Scale") 181 case appsv1beta1.SchemeGroupVersion: 182 return appsv1beta1.SchemeGroupVersion.WithKind("Scale") 183 case appsv1beta2.SchemeGroupVersion: 184 return appsv1beta2.SchemeGroupVersion.WithKind("Scale") 185 default: 186 return autoscalingv1.SchemeGroupVersion.WithKind("Scale") 187 } 188 } 189 190 // New creates a new Scale object 191 func (r *ScaleREST) New() runtime.Object { 192 return &autoscaling.Scale{} 193 } 194 195 // Destroy cleans up resources on shutdown. 196 func (r *ScaleREST) Destroy() { 197 // Given that underlying store is shared with REST, 198 // we don't destroy it here explicitly. 199 } 200 201 // Get retrieves object from Scale storage. 202 func (r *ScaleREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { 203 obj, err := r.store.Get(ctx, name, options) 204 if err != nil { 205 return nil, errors.NewNotFound(apps.Resource("replicasets/scale"), name) 206 } 207 rs := obj.(*apps.ReplicaSet) 208 scale, err := scaleFromReplicaSet(rs) 209 if err != nil { 210 return nil, errors.NewBadRequest(fmt.Sprintf("%v", err)) 211 } 212 return scale, err 213 } 214 215 // Update alters scale subset of ReplicaSet object. 216 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) { 217 obj, _, err := r.store.Update( 218 ctx, 219 name, 220 &scaleUpdatedObjectInfo{name, objInfo}, 221 toScaleCreateValidation(createValidation), 222 toScaleUpdateValidation(updateValidation), 223 false, 224 options, 225 ) 226 if err != nil { 227 return nil, false, err 228 } 229 rs := obj.(*apps.ReplicaSet) 230 newScale, err := scaleFromReplicaSet(rs) 231 if err != nil { 232 return nil, false, errors.NewBadRequest(fmt.Sprintf("%v", err)) 233 } 234 return newScale, false, err 235 } 236 237 func (r *ScaleREST) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { 238 return r.store.ConvertToTable(ctx, object, tableOptions) 239 } 240 241 func toScaleCreateValidation(f rest.ValidateObjectFunc) rest.ValidateObjectFunc { 242 return func(ctx context.Context, obj runtime.Object) error { 243 scale, err := scaleFromReplicaSet(obj.(*apps.ReplicaSet)) 244 if err != nil { 245 return err 246 } 247 return f(ctx, scale) 248 } 249 } 250 251 func toScaleUpdateValidation(f rest.ValidateObjectUpdateFunc) rest.ValidateObjectUpdateFunc { 252 return func(ctx context.Context, obj, old runtime.Object) error { 253 newScale, err := scaleFromReplicaSet(obj.(*apps.ReplicaSet)) 254 if err != nil { 255 return err 256 } 257 oldScale, err := scaleFromReplicaSet(old.(*apps.ReplicaSet)) 258 if err != nil { 259 return err 260 } 261 return f(ctx, newScale, oldScale) 262 } 263 } 264 265 // scaleFromReplicaSet returns a scale subresource for a replica set. 266 func scaleFromReplicaSet(rs *apps.ReplicaSet) (*autoscaling.Scale, error) { 267 selector, err := metav1.LabelSelectorAsSelector(rs.Spec.Selector) 268 if err != nil { 269 return nil, err 270 } 271 return &autoscaling.Scale{ 272 // TODO: Create a variant of ObjectMeta type that only contains the fields below. 273 ObjectMeta: metav1.ObjectMeta{ 274 Name: rs.Name, 275 Namespace: rs.Namespace, 276 UID: rs.UID, 277 ResourceVersion: rs.ResourceVersion, 278 CreationTimestamp: rs.CreationTimestamp, 279 }, 280 Spec: autoscaling.ScaleSpec{ 281 Replicas: rs.Spec.Replicas, 282 }, 283 Status: autoscaling.ScaleStatus{ 284 Replicas: rs.Status.Replicas, 285 Selector: selector.String(), 286 }, 287 }, nil 288 } 289 290 // scaleUpdatedObjectInfo transforms existing replicaset -> existing scale -> new scale -> new replicaset 291 type scaleUpdatedObjectInfo struct { 292 name string 293 reqObjInfo rest.UpdatedObjectInfo 294 } 295 296 func (i *scaleUpdatedObjectInfo) Preconditions() *metav1.Preconditions { 297 return i.reqObjInfo.Preconditions() 298 } 299 300 func (i *scaleUpdatedObjectInfo) UpdatedObject(ctx context.Context, oldObj runtime.Object) (runtime.Object, error) { 301 replicaset, ok := oldObj.DeepCopyObject().(*apps.ReplicaSet) 302 if !ok { 303 return nil, errors.NewBadRequest(fmt.Sprintf("expected existing object type to be ReplicaSet, got %T", replicaset)) 304 } 305 // if zero-value, the existing object does not exist 306 if len(replicaset.ResourceVersion) == 0 { 307 return nil, errors.NewNotFound(apps.Resource("replicasets/scale"), i.name) 308 } 309 310 groupVersion := schema.GroupVersion{Group: "apps", Version: "v1"} 311 if requestInfo, found := genericapirequest.RequestInfoFrom(ctx); found { 312 requestGroupVersion := schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion} 313 if _, ok := replicasPathInReplicaSet[requestGroupVersion.String()]; ok { 314 groupVersion = requestGroupVersion 315 } else { 316 klog.Fatalf("Unrecognized group/version in request info %q", requestGroupVersion.String()) 317 } 318 } 319 320 managedFieldsHandler := managedfields.NewScaleHandler( 321 replicaset.ManagedFields, 322 groupVersion, 323 replicasPathInReplicaSet, 324 ) 325 326 // replicaset -> old scale 327 oldScale, err := scaleFromReplicaSet(replicaset) 328 if err != nil { 329 return nil, err 330 } 331 332 scaleManagedFields, err := managedFieldsHandler.ToSubresource() 333 if err != nil { 334 return nil, err 335 } 336 oldScale.ManagedFields = scaleManagedFields 337 338 // old scale -> new scale 339 newScaleObj, err := i.reqObjInfo.UpdatedObject(ctx, oldScale) 340 if err != nil { 341 return nil, err 342 } 343 if newScaleObj == nil { 344 return nil, errors.NewBadRequest("nil update passed to Scale") 345 } 346 scale, ok := newScaleObj.(*autoscaling.Scale) 347 if !ok { 348 return nil, errors.NewBadRequest(fmt.Sprintf("expected input object type to be Scale, but %T", newScaleObj)) 349 } 350 351 // validate 352 if errs := autoscalingvalidation.ValidateScale(scale); len(errs) > 0 { 353 return nil, errors.NewInvalid(autoscaling.Kind("Scale"), replicaset.Name, errs) 354 } 355 356 // validate precondition if specified (resourceVersion matching is handled by storage) 357 if len(scale.UID) > 0 && scale.UID != replicaset.UID { 358 return nil, errors.NewConflict( 359 apps.Resource("replicasets/scale"), 360 replicaset.Name, 361 fmt.Errorf("Precondition failed: UID in precondition: %v, UID in object meta: %v", scale.UID, replicaset.UID), 362 ) 363 } 364 365 // move replicas/resourceVersion fields to object and return 366 replicaset.Spec.Replicas = scale.Spec.Replicas 367 replicaset.ResourceVersion = scale.ResourceVersion 368 369 updatedEntries, err := managedFieldsHandler.ToParent(scale.ManagedFields) 370 if err != nil { 371 return nil, err 372 } 373 replicaset.ManagedFields = updatedEntries 374 375 return replicaset, nil 376 }