k8s.io/kubernetes@v1.29.3/pkg/registry/apps/deployment/storage/storage_test.go (about) 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 "reflect" 24 "strings" 25 "sync" 26 "testing" 27 28 "github.com/google/go-cmp/cmp" 29 apiequality "k8s.io/apimachinery/pkg/api/equality" 30 apierrors "k8s.io/apimachinery/pkg/api/errors" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 "k8s.io/apimachinery/pkg/fields" 33 "k8s.io/apimachinery/pkg/labels" 34 "k8s.io/apimachinery/pkg/runtime" 35 "k8s.io/apimachinery/pkg/util/intstr" 36 genericapirequest "k8s.io/apiserver/pkg/endpoints/request" 37 "k8s.io/apiserver/pkg/registry/generic" 38 genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" 39 "k8s.io/apiserver/pkg/registry/rest" 40 storeerr "k8s.io/apiserver/pkg/storage/errors" 41 etcd3testing "k8s.io/apiserver/pkg/storage/etcd3/testing" 42 "k8s.io/kubernetes/pkg/apis/apps" 43 "k8s.io/kubernetes/pkg/apis/autoscaling" 44 api "k8s.io/kubernetes/pkg/apis/core" 45 "k8s.io/kubernetes/pkg/registry/registrytest" 46 ) 47 48 const defaultReplicas = 100 49 50 func newStorage(t *testing.T) (*DeploymentStorage, *etcd3testing.EtcdTestServer) { 51 etcdStorage, server := registrytest.NewEtcdStorage(t, apps.GroupName) 52 restOptions := generic.RESTOptions{StorageConfig: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1, ResourcePrefix: "deployments"} 53 deploymentStorage, err := NewStorage(restOptions) 54 if err != nil { 55 t.Fatalf("unexpected error from REST storage: %v", err) 56 } 57 return &deploymentStorage, server 58 } 59 60 var namespace = "foo-namespace" 61 var name = "foo-deployment" 62 63 func validNewDeployment() *apps.Deployment { 64 return &apps.Deployment{ 65 ObjectMeta: metav1.ObjectMeta{ 66 Name: name, 67 Namespace: namespace, 68 }, 69 Spec: apps.DeploymentSpec{ 70 Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}}, 71 Strategy: apps.DeploymentStrategy{ 72 Type: apps.RollingUpdateDeploymentStrategyType, 73 RollingUpdate: &apps.RollingUpdateDeployment{ 74 MaxSurge: intstr.FromInt32(1), 75 MaxUnavailable: intstr.FromInt32(1), 76 }, 77 }, 78 Template: api.PodTemplateSpec{ 79 ObjectMeta: metav1.ObjectMeta{ 80 Labels: map[string]string{"a": "b"}, 81 }, 82 Spec: api.PodSpec{ 83 Containers: []api.Container{ 84 { 85 Name: "test", 86 Image: "test_image", 87 ImagePullPolicy: api.PullIfNotPresent, 88 TerminationMessagePolicy: api.TerminationMessageReadFile, 89 }, 90 }, 91 RestartPolicy: api.RestartPolicyAlways, 92 DNSPolicy: api.DNSClusterFirst, 93 }, 94 }, 95 Replicas: 7, 96 }, 97 Status: apps.DeploymentStatus{ 98 Replicas: 5, 99 }, 100 } 101 } 102 103 var validDeployment = *validNewDeployment() 104 105 func TestCreate(t *testing.T) { 106 storage, server := newStorage(t) 107 defer server.Terminate(t) 108 defer storage.Deployment.Store.DestroyFunc() 109 test := genericregistrytest.New(t, storage.Deployment.Store) 110 deployment := validNewDeployment() 111 deployment.ObjectMeta = metav1.ObjectMeta{} 112 test.TestCreate( 113 // valid 114 deployment, 115 // invalid (invalid selector) 116 &apps.Deployment{ 117 Spec: apps.DeploymentSpec{ 118 Selector: &metav1.LabelSelector{MatchLabels: map[string]string{}}, 119 Template: validDeployment.Spec.Template, 120 }, 121 }, 122 ) 123 } 124 125 func TestUpdate(t *testing.T) { 126 storage, server := newStorage(t) 127 defer server.Terminate(t) 128 defer storage.Deployment.Store.DestroyFunc() 129 test := genericregistrytest.New(t, storage.Deployment.Store) 130 test.TestUpdate( 131 // valid 132 validNewDeployment(), 133 // updateFunc 134 func(obj runtime.Object) runtime.Object { 135 object := obj.(*apps.Deployment) 136 object.Spec.Template.Spec.NodeSelector = map[string]string{"c": "d"} 137 return object 138 }, 139 // invalid updateFunc 140 func(obj runtime.Object) runtime.Object { 141 object := obj.(*apps.Deployment) 142 object.Name = "" 143 return object 144 }, 145 func(obj runtime.Object) runtime.Object { 146 object := obj.(*apps.Deployment) 147 object.Spec.Template.Spec.RestartPolicy = api.RestartPolicyOnFailure 148 return object 149 }, 150 func(obj runtime.Object) runtime.Object { 151 object := obj.(*apps.Deployment) 152 object.Spec.Selector = &metav1.LabelSelector{MatchLabels: map[string]string{}} 153 return object 154 }, 155 ) 156 } 157 158 func TestDelete(t *testing.T) { 159 storage, server := newStorage(t) 160 defer server.Terminate(t) 161 defer storage.Deployment.Store.DestroyFunc() 162 test := genericregistrytest.New(t, storage.Deployment.Store) 163 test.TestDelete(validNewDeployment()) 164 } 165 166 func TestGet(t *testing.T) { 167 storage, server := newStorage(t) 168 defer server.Terminate(t) 169 defer storage.Deployment.Store.DestroyFunc() 170 test := genericregistrytest.New(t, storage.Deployment.Store) 171 test.TestGet(validNewDeployment()) 172 } 173 174 func TestList(t *testing.T) { 175 storage, server := newStorage(t) 176 defer server.Terminate(t) 177 defer storage.Deployment.Store.DestroyFunc() 178 test := genericregistrytest.New(t, storage.Deployment.Store) 179 test.TestList(validNewDeployment()) 180 } 181 182 func TestWatch(t *testing.T) { 183 storage, server := newStorage(t) 184 defer server.Terminate(t) 185 defer storage.Deployment.Store.DestroyFunc() 186 test := genericregistrytest.New(t, storage.Deployment.Store) 187 test.TestWatch( 188 validNewDeployment(), 189 // matching labels 190 []labels.Set{}, 191 // not matching labels 192 []labels.Set{ 193 {"a": "c"}, 194 {"foo": "bar"}, 195 }, 196 // matching fields 197 []fields.Set{ 198 {"metadata.name": name}, 199 }, 200 // not matching fields 201 []fields.Set{ 202 {"metadata.name": "bar"}, 203 {"name": name}, 204 }, 205 ) 206 } 207 208 func TestScaleGet(t *testing.T) { 209 storage, server := newStorage(t) 210 defer server.Terminate(t) 211 defer storage.Deployment.Store.DestroyFunc() 212 var deployment apps.Deployment 213 ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), namespace) 214 key := "/deployments/" + namespace + "/" + name 215 if err := storage.Deployment.Storage.Create(ctx, key, &validDeployment, &deployment, 0, false); err != nil { 216 t.Fatalf("error setting new deployment (key: %s) %v: %v", key, validDeployment, err) 217 } 218 219 selector, err := metav1.LabelSelectorAsSelector(validDeployment.Spec.Selector) 220 if err != nil { 221 t.Fatal(err) 222 } 223 want := &autoscaling.Scale{ 224 ObjectMeta: metav1.ObjectMeta{ 225 Name: name, 226 Namespace: namespace, 227 UID: deployment.UID, 228 ResourceVersion: deployment.ResourceVersion, 229 CreationTimestamp: deployment.CreationTimestamp, 230 }, 231 Spec: autoscaling.ScaleSpec{ 232 Replicas: validDeployment.Spec.Replicas, 233 }, 234 Status: autoscaling.ScaleStatus{ 235 Replicas: validDeployment.Status.Replicas, 236 Selector: selector.String(), 237 }, 238 } 239 obj, err := storage.Scale.Get(ctx, name, &metav1.GetOptions{}) 240 if err != nil { 241 t.Fatalf("error fetching scale for %s: %v", name, err) 242 } 243 got := obj.(*autoscaling.Scale) 244 if !apiequality.Semantic.DeepEqual(want, got) { 245 t.Errorf("unexpected scale: %s", cmp.Diff(want, got)) 246 } 247 } 248 249 func TestScaleUpdate(t *testing.T) { 250 storage, server := newStorage(t) 251 defer server.Terminate(t) 252 defer storage.Deployment.Store.DestroyFunc() 253 var deployment apps.Deployment 254 ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), namespace) 255 key := "/deployments/" + namespace + "/" + name 256 if err := storage.Deployment.Storage.Create(ctx, key, &validDeployment, &deployment, 0, false); err != nil { 257 t.Fatalf("error setting new deployment (key: %s) %v: %v", key, validDeployment, err) 258 } 259 replicas := int32(12) 260 update := autoscaling.Scale{ 261 ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace}, 262 Spec: autoscaling.ScaleSpec{ 263 Replicas: replicas, 264 }, 265 } 266 267 if _, _, err := storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}); err != nil { 268 t.Fatalf("error updating scale %v: %v", update, err) 269 } 270 obj, err := storage.Scale.Get(ctx, name, &metav1.GetOptions{}) 271 if err != nil { 272 t.Fatalf("error fetching scale for %s: %v", name, err) 273 } 274 scale := obj.(*autoscaling.Scale) 275 if scale.Spec.Replicas != replicas { 276 t.Errorf("wrong replicas count expected: %d got: %d", replicas, deployment.Spec.Replicas) 277 } 278 279 update.ResourceVersion = deployment.ResourceVersion 280 update.Spec.Replicas = 15 281 282 if _, _, err = storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}); err != nil && !apierrors.IsConflict(err) { 283 t.Fatalf("unexpected error, expecting an update conflict but got %v", err) 284 } 285 } 286 287 func TestStatusUpdate(t *testing.T) { 288 storage, server := newStorage(t) 289 defer server.Terminate(t) 290 defer storage.Deployment.Store.DestroyFunc() 291 ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), namespace) 292 key := "/deployments/" + namespace + "/" + name 293 if err := storage.Deployment.Storage.Create(ctx, key, &validDeployment, nil, 0, false); err != nil { 294 t.Fatalf("unexpected error: %v", err) 295 } 296 update := apps.Deployment{ 297 ObjectMeta: validDeployment.ObjectMeta, 298 Spec: apps.DeploymentSpec{ 299 Replicas: defaultReplicas, 300 }, 301 Status: apps.DeploymentStatus{ 302 Replicas: defaultReplicas, 303 }, 304 } 305 306 if _, _, err := storage.Status.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}); err != nil { 307 t.Fatalf("unexpected error: %v", err) 308 } 309 obj, err := storage.Deployment.Get(ctx, name, &metav1.GetOptions{}) 310 if err != nil { 311 t.Fatalf("unexpected error: %v", err) 312 } 313 314 deployment := obj.(*apps.Deployment) 315 if deployment.Spec.Replicas != 7 { 316 t.Errorf("we expected .spec.replicas to not be updated but it was updated to %v", deployment.Spec.Replicas) 317 } 318 if deployment.Status.Replicas != defaultReplicas { 319 t.Errorf("we expected .status.replicas to be updated to %d but it was %v", defaultReplicas, deployment.Status.Replicas) 320 } 321 } 322 323 func TestEtcdCreateDeploymentRollback(t *testing.T) { 324 testCases := map[string]struct { 325 rollback apps.DeploymentRollback 326 errOK func(error) bool 327 }{ 328 "normal": { 329 rollback: apps.DeploymentRollback{ 330 Name: name, 331 UpdatedAnnotations: map[string]string{}, 332 RollbackTo: apps.RollbackConfig{Revision: 1}, 333 }, 334 errOK: func(err error) bool { return err == nil }, 335 }, 336 "noAnnotation": { 337 rollback: apps.DeploymentRollback{ 338 Name: name, 339 RollbackTo: apps.RollbackConfig{Revision: 1}, 340 }, 341 errOK: func(err error) bool { return err == nil }, 342 }, 343 "noName": { 344 rollback: apps.DeploymentRollback{ 345 UpdatedAnnotations: map[string]string{}, 346 RollbackTo: apps.RollbackConfig{Revision: 1}, 347 }, 348 errOK: func(err error) bool { return err != nil }, 349 }, 350 } 351 storage, server := newStorage(t) 352 defer server.Terminate(t) 353 defer storage.Deployment.Store.DestroyFunc() 354 for k, test := range testCases { 355 rollbackStorage := storage.Rollback 356 deployment := validNewDeployment() 357 deployment.Namespace = fmt.Sprintf("namespace-%s", strings.ToLower(k)) 358 ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), deployment.Namespace) 359 360 if _, err := storage.Deployment.Create(ctx, deployment, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}); err != nil { 361 t.Fatalf("%s: unexpected error: %v", k, err) 362 } 363 rollbackRespStatus, err := rollbackStorage.Create(ctx, test.rollback.Name, &test.rollback, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}) 364 if !test.errOK(err) { 365 t.Errorf("%s: unexpected error: %v", k, err) 366 } else if err == nil { 367 // If rollback succeeded, verify Rollback response and Rollback field of deployment 368 status, ok := rollbackRespStatus.(*metav1.Status) 369 if !ok { 370 t.Errorf("%s: unexpected response format", k) 371 } 372 if status.Code != http.StatusOK || status.Status != metav1.StatusSuccess { 373 t.Errorf("%s: unexpected response, code: %d, status: %s", k, status.Code, status.Status) 374 } 375 d, err := storage.Deployment.Get(ctx, deployment.ObjectMeta.Name, &metav1.GetOptions{}) 376 if err != nil { 377 t.Errorf("%s: unexpected error: %v", k, err) 378 } else if !reflect.DeepEqual(*d.(*apps.Deployment).Spec.RollbackTo, test.rollback.RollbackTo) { 379 t.Errorf("%s: expected: %v, got: %v", k, *d.(*apps.Deployment).Spec.RollbackTo, test.rollback.RollbackTo) 380 } 381 } 382 } 383 } 384 385 func TestCreateDeploymentRollbackValidation(t *testing.T) { 386 storage, server := newStorage(t) 387 rollbackStorage := storage.Rollback 388 rollback := apps.DeploymentRollback{ 389 Name: name, 390 UpdatedAnnotations: map[string]string{}, 391 RollbackTo: apps.RollbackConfig{Revision: 1}, 392 } 393 394 ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), namespace) 395 396 if _, err := storage.Deployment.Create(ctx, validNewDeployment(), rest.ValidateAllObjectFunc, &metav1.CreateOptions{}); err != nil { 397 t.Fatalf("Unexpected error: %v", err) 398 } 399 400 validationError := fmt.Errorf("admission deny") 401 alwaysDenyValidationFunc := func(ctx context.Context, obj runtime.Object) error { return validationError } 402 _, err := rollbackStorage.Create(ctx, rollback.Name, &rollback, alwaysDenyValidationFunc, &metav1.CreateOptions{}) 403 404 if err == nil || validationError != err { 405 t.Errorf("expected: %v, got: %v", validationError, err) 406 } 407 408 storage.Deployment.Store.DestroyFunc() 409 server.Terminate(t) 410 } 411 412 // Ensure that when a deploymentRollback is created for a deployment that has already been deleted 413 // by the API server, API server returns not-found error. 414 func TestEtcdCreateDeploymentRollbackNoDeployment(t *testing.T) { 415 storage, server := newStorage(t) 416 defer server.Terminate(t) 417 defer storage.Deployment.Store.DestroyFunc() 418 rollbackStorage := storage.Rollback 419 ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), namespace) 420 421 _, err := rollbackStorage.Create(ctx, name, &apps.DeploymentRollback{ 422 Name: name, 423 UpdatedAnnotations: map[string]string{}, 424 RollbackTo: apps.RollbackConfig{Revision: 1}, 425 }, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}) 426 if err == nil { 427 t.Fatalf("Expected not-found-error but got nothing") 428 } 429 if !apierrors.IsNotFound(storeerr.InterpretGetError(err, apps.Resource("deployments"), name)) { 430 t.Fatalf("Unexpected error returned: %#v", err) 431 } 432 433 _, err = storage.Deployment.Get(ctx, name, &metav1.GetOptions{}) 434 if err == nil { 435 t.Fatalf("Expected not-found-error but got nothing") 436 } 437 if !apierrors.IsNotFound(storeerr.InterpretGetError(err, apps.Resource("deployments"), name)) { 438 t.Fatalf("Unexpected error: %v", err) 439 } 440 } 441 442 func TestShortNames(t *testing.T) { 443 storage, server := newStorage(t) 444 defer server.Terminate(t) 445 defer storage.Deployment.Store.DestroyFunc() 446 expected := []string{"deploy"} 447 registrytest.AssertShortNames(t, storage.Deployment, expected) 448 } 449 450 func TestCategories(t *testing.T) { 451 storage, server := newStorage(t) 452 defer server.Terminate(t) 453 defer storage.Deployment.Store.DestroyFunc() 454 expected := []string{"all"} 455 registrytest.AssertCategories(t, storage.Deployment, expected) 456 } 457 458 func TestScalePatchErrors(t *testing.T) { 459 storage, server := newStorage(t) 460 defer server.Terminate(t) 461 validObj := validNewDeployment() 462 resourceStore := storage.Deployment.Store 463 scaleStore := storage.Scale 464 465 defer resourceStore.DestroyFunc() 466 ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), namespace) 467 468 { 469 applyNotFoundPatch := func() rest.TransformFunc { 470 return func(_ context.Context, _, currentObject runtime.Object) (objToUpdate runtime.Object, patchErr error) { 471 t.Errorf("notfound patch called") 472 return currentObject, nil 473 } 474 } 475 _, _, err := scaleStore.Update(ctx, "bad-name", rest.DefaultUpdatedObjectInfo(nil, applyNotFoundPatch()), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}) 476 if !apierrors.IsNotFound(err) { 477 t.Errorf("expected notfound, got %v", err) 478 } 479 } 480 481 if _, err := resourceStore.Create(ctx, validObj, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}); err != nil { 482 t.Errorf("Unexpected error: %v", err) 483 } 484 485 { 486 applyBadUIDPatch := func() rest.TransformFunc { 487 return func(_ context.Context, _, currentObject runtime.Object) (objToUpdate runtime.Object, patchErr error) { 488 currentObject.(*autoscaling.Scale).UID = "123" 489 return currentObject, nil 490 } 491 } 492 _, _, err := scaleStore.Update(ctx, name, rest.DefaultUpdatedObjectInfo(nil, applyBadUIDPatch()), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}) 493 if !apierrors.IsConflict(err) { 494 t.Errorf("expected conflict, got %v", err) 495 } 496 } 497 498 { 499 applyBadResourceVersionPatch := func() rest.TransformFunc { 500 return func(_ context.Context, _, currentObject runtime.Object) (objToUpdate runtime.Object, patchErr error) { 501 currentObject.(*autoscaling.Scale).ResourceVersion = "123" 502 return currentObject, nil 503 } 504 } 505 _, _, err := scaleStore.Update(ctx, name, rest.DefaultUpdatedObjectInfo(nil, applyBadResourceVersionPatch()), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}) 506 if !apierrors.IsConflict(err) { 507 t.Errorf("expected conflict, got %v", err) 508 } 509 } 510 } 511 512 func TestScalePatchConflicts(t *testing.T) { 513 storage, server := newStorage(t) 514 defer server.Terminate(t) 515 validObj := validNewDeployment() 516 resourceStore := storage.Deployment.Store 517 scaleStore := storage.Scale 518 519 defer resourceStore.DestroyFunc() 520 ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), namespace) 521 if _, err := resourceStore.Create(ctx, validObj, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}); err != nil { 522 t.Fatalf("Unexpected error: %v", err) 523 } 524 applyLabelPatch := func(labelName, labelValue string) rest.TransformFunc { 525 return func(_ context.Context, _, currentObject runtime.Object) (objToUpdate runtime.Object, patchErr error) { 526 currentObject.(metav1.Object).SetLabels(map[string]string{labelName: labelValue}) 527 return currentObject, nil 528 } 529 } 530 stopCh := make(chan struct{}) 531 wg := &sync.WaitGroup{} 532 wg.Add(1) 533 go func() { 534 defer wg.Done() 535 // continuously submits a patch that updates a label and verifies the label update was effective 536 labelName := "timestamp" 537 for i := 0; ; i++ { 538 select { 539 case <-stopCh: 540 return 541 default: 542 expectedLabelValue := fmt.Sprint(i) 543 updated, _, err := resourceStore.Update(ctx, name, rest.DefaultUpdatedObjectInfo(nil, applyLabelPatch(labelName, fmt.Sprint(i))), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}) 544 if err != nil { 545 t.Errorf("error patching main resource: %v", err) 546 return 547 } 548 gotLabelValue := updated.(metav1.Object).GetLabels()[labelName] 549 if gotLabelValue != expectedLabelValue { 550 t.Errorf("wrong label value: expected: %s, got: %s", expectedLabelValue, gotLabelValue) 551 return 552 } 553 } 554 } 555 }() 556 557 // continuously submits a scale patch of replicas for a monotonically increasing replica value 558 applyReplicaPatch := func(replicas int) rest.TransformFunc { 559 return func(_ context.Context, _, currentObject runtime.Object) (objToUpdate runtime.Object, patchErr error) { 560 currentObject.(*autoscaling.Scale).Spec.Replicas = int32(replicas) 561 return currentObject, nil 562 } 563 } 564 for i := 0; i < 100; i++ { 565 result, _, err := scaleStore.Update(ctx, name, rest.DefaultUpdatedObjectInfo(nil, applyReplicaPatch(i)), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}) 566 if err != nil { 567 t.Fatalf("error patching scale: %v", err) 568 } 569 scale := result.(*autoscaling.Scale) 570 if scale.Spec.Replicas != int32(i) { 571 t.Errorf("wrong replicas count: expected: %d got: %d", i, scale.Spec.Replicas) 572 } 573 } 574 close(stopCh) 575 wg.Wait() 576 }