k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/registry/core/replicationcontroller/storage/storage_test.go (about) 1 /* 2 Copyright 2014 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 "sync" 23 "testing" 24 25 "github.com/google/go-cmp/cmp" 26 apiequality "k8s.io/apimachinery/pkg/api/equality" 27 "k8s.io/apimachinery/pkg/api/errors" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/fields" 30 "k8s.io/apimachinery/pkg/labels" 31 "k8s.io/apimachinery/pkg/runtime" 32 genericapirequest "k8s.io/apiserver/pkg/endpoints/request" 33 "k8s.io/apiserver/pkg/registry/generic" 34 genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" 35 "k8s.io/apiserver/pkg/registry/rest" 36 etcd3testing "k8s.io/apiserver/pkg/storage/etcd3/testing" 37 "k8s.io/kubernetes/pkg/apis/autoscaling" 38 api "k8s.io/kubernetes/pkg/apis/core" 39 "k8s.io/kubernetes/pkg/registry/registrytest" 40 ) 41 42 const ( 43 namespace = metav1.NamespaceDefault 44 name = "foo" 45 ) 46 47 func newStorage(t *testing.T) (ControllerStorage, *etcd3testing.EtcdTestServer) { 48 etcdStorage, server := registrytest.NewEtcdStorage(t, "") 49 restOptions := generic.RESTOptions{ 50 StorageConfig: etcdStorage, 51 Decorator: generic.UndecoratedStorage, 52 DeleteCollectionWorkers: 1, 53 ResourcePrefix: "replicationcontrollers", 54 } 55 storage, err := NewStorage(restOptions) 56 if err != nil { 57 t.Fatalf("unexpected error from REST storage: %v", err) 58 } 59 return storage, server 60 } 61 62 // createController is a helper function that returns a controller with the updated resource version. 63 func createController(storage *REST, rc api.ReplicationController, t *testing.T) (api.ReplicationController, error) { 64 ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), rc.Namespace) 65 obj, err := storage.Create(ctx, &rc, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}) 66 if err != nil { 67 t.Errorf("Failed to create controller, %v", err) 68 } 69 newRc := obj.(*api.ReplicationController) 70 return *newRc, nil 71 } 72 73 func validNewController() *api.ReplicationController { 74 return &api.ReplicationController{ 75 ObjectMeta: metav1.ObjectMeta{ 76 Name: name, 77 Namespace: namespace, 78 }, 79 Spec: api.ReplicationControllerSpec{ 80 Selector: map[string]string{"a": "b"}, 81 Template: &api.PodTemplateSpec{ 82 ObjectMeta: metav1.ObjectMeta{ 83 Labels: map[string]string{"a": "b"}, 84 }, 85 Spec: api.PodSpec{ 86 Containers: []api.Container{ 87 { 88 Name: "test", 89 Image: "test_image", 90 ImagePullPolicy: api.PullIfNotPresent, 91 TerminationMessagePolicy: api.TerminationMessageReadFile, 92 }, 93 }, 94 RestartPolicy: api.RestartPolicyAlways, 95 DNSPolicy: api.DNSClusterFirst, 96 }, 97 }, 98 }, 99 } 100 } 101 102 var validController = validNewController() 103 104 func TestCreate(t *testing.T) { 105 storage, server := newStorage(t) 106 defer server.Terminate(t) 107 defer storage.Controller.Store.DestroyFunc() 108 test := genericregistrytest.New(t, storage.Controller.Store) 109 controller := validNewController() 110 controller.ObjectMeta = metav1.ObjectMeta{} 111 test.TestCreate( 112 // valid 113 controller, 114 // invalid (invalid selector) 115 &api.ReplicationController{ 116 Spec: api.ReplicationControllerSpec{ 117 Replicas: 2, 118 Selector: map[string]string{}, 119 Template: validController.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.Controller.Store.DestroyFunc() 129 test := genericregistrytest.New(t, storage.Controller.Store) 130 test.TestUpdate( 131 // valid 132 validNewController(), 133 // valid updateFunc 134 func(obj runtime.Object) runtime.Object { 135 object := obj.(*api.ReplicationController) 136 object.Spec.Replicas = object.Spec.Replicas + 1 137 return object 138 }, 139 // invalid updateFunc 140 func(obj runtime.Object) runtime.Object { 141 object := obj.(*api.ReplicationController) 142 object.Name = "" 143 return object 144 }, 145 func(obj runtime.Object) runtime.Object { 146 object := obj.(*api.ReplicationController) 147 object.Spec.Selector = map[string]string{} 148 return object 149 }, 150 ) 151 } 152 153 func TestDelete(t *testing.T) { 154 storage, server := newStorage(t) 155 defer server.Terminate(t) 156 defer storage.Controller.Store.DestroyFunc() 157 test := genericregistrytest.New(t, storage.Controller.Store) 158 test.TestDelete(validNewController()) 159 } 160 161 func TestGenerationNumber(t *testing.T) { 162 storage, server := newStorage(t) 163 defer server.Terminate(t) 164 defer storage.Controller.Store.DestroyFunc() 165 modifiedSno := *validNewController() 166 modifiedSno.Generation = 100 167 modifiedSno.Status.ObservedGeneration = 10 168 ctx := genericapirequest.NewDefaultContext() 169 rc, err := createController(storage.Controller, modifiedSno, t) 170 if err != nil { 171 t.Errorf("unexpected error: %v", err) 172 } 173 ctrl, err := storage.Controller.Get(ctx, rc.Name, &metav1.GetOptions{}) 174 if err != nil { 175 t.Errorf("unexpected error: %v", err) 176 } 177 controller, _ := ctrl.(*api.ReplicationController) 178 179 // Generation initialization 180 if controller.Generation != 1 || controller.Status.ObservedGeneration != 0 { 181 t.Fatalf("Unexpected generation number %v, status generation %v", controller.Generation, controller.Status.ObservedGeneration) 182 } 183 184 // Updates to spec should increment the generation number 185 controller.Spec.Replicas++ 186 if _, _, err := storage.Controller.Update(ctx, controller.Name, rest.DefaultUpdatedObjectInfo(controller), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}); err != nil { 187 t.Errorf("unexpected error: %v", err) 188 } 189 ctrl, err = storage.Controller.Get(ctx, rc.Name, &metav1.GetOptions{}) 190 if err != nil { 191 t.Errorf("unexpected error: %v", err) 192 } 193 controller, _ = ctrl.(*api.ReplicationController) 194 if controller.Generation != 2 || controller.Status.ObservedGeneration != 0 { 195 t.Fatalf("Unexpected generation, spec: %v, status: %v", controller.Generation, controller.Status.ObservedGeneration) 196 } 197 198 // Updates to status should not increment either spec or status generation numbers 199 controller.Status.Replicas++ 200 if _, _, err := storage.Controller.Update(ctx, controller.Name, rest.DefaultUpdatedObjectInfo(controller), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}); err != nil { 201 t.Errorf("unexpected error: %v", err) 202 } 203 ctrl, err = storage.Controller.Get(ctx, rc.Name, &metav1.GetOptions{}) 204 if err != nil { 205 t.Errorf("unexpected error: %v", err) 206 } 207 controller, _ = ctrl.(*api.ReplicationController) 208 if controller.Generation != 2 || controller.Status.ObservedGeneration != 0 { 209 t.Fatalf("Unexpected generation number, spec: %v, status: %v", controller.Generation, controller.Status.ObservedGeneration) 210 } 211 } 212 213 func TestGet(t *testing.T) { 214 storage, server := newStorage(t) 215 defer server.Terminate(t) 216 defer storage.Controller.Store.DestroyFunc() 217 test := genericregistrytest.New(t, storage.Controller.Store) 218 test.TestGet(validNewController()) 219 } 220 221 func TestList(t *testing.T) { 222 storage, server := newStorage(t) 223 defer server.Terminate(t) 224 defer storage.Controller.Store.DestroyFunc() 225 test := genericregistrytest.New(t, storage.Controller.Store) 226 test.TestList(validNewController()) 227 } 228 229 func TestWatch(t *testing.T) { 230 storage, server := newStorage(t) 231 defer server.Terminate(t) 232 defer storage.Controller.Store.DestroyFunc() 233 test := genericregistrytest.New(t, storage.Controller.Store) 234 test.TestWatch( 235 validController, 236 // matching labels 237 []labels.Set{ 238 {"a": "b"}, 239 }, 240 // not matching labels 241 []labels.Set{ 242 {"a": "c"}, 243 {"foo": "bar"}, 244 }, 245 // matching fields 246 []fields.Set{ 247 {"status.replicas": "0"}, 248 {"metadata.name": "foo"}, 249 {"status.replicas": "0", "metadata.name": "foo"}, 250 }, 251 // not matching fields 252 []fields.Set{ 253 {"status.replicas": "10"}, 254 {"metadata.name": "bar"}, 255 {"name": "foo"}, 256 {"status.replicas": "10", "metadata.name": "foo"}, 257 {"status.replicas": "0", "metadata.name": "bar"}, 258 }, 259 ) 260 } 261 262 func TestUpdateStatus(t *testing.T) { 263 storage, server := newStorage(t) 264 defer server.Terminate(t) 265 defer storage.Controller.Store.Destroy() 266 267 ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), namespace) 268 rcStart, err := createController(storage.Controller, *validController, t) 269 if err != nil { 270 t.Fatalf("error setting new replication controller %v: %v", *validController, err) 271 } 272 273 rc := rcStart.DeepCopy() 274 rc.Status.Replicas++ 275 _, _, err = storage.Status.Update(ctx, rc.Name, rest.DefaultUpdatedObjectInfo(rc), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}) 276 if err != nil { 277 t.Fatalf("Unexpected error: %v", err) 278 } 279 obj, err := storage.Status.Get(ctx, rc.Name, &metav1.GetOptions{}) 280 if err != nil { 281 t.Errorf("unexpected error: %v", err) 282 } 283 rcGot := obj.(*api.ReplicationController) 284 // only compare relevant changes b/c of difference in metadata 285 if !apiequality.Semantic.DeepEqual(rc.Status, rcGot.Status) { 286 t.Errorf("unexpected object: %s", cmp.Diff(rc.Status, rcGot.Status)) 287 } 288 } 289 290 func TestScaleGet(t *testing.T) { 291 storage, server := newStorage(t) 292 defer server.Terminate(t) 293 defer storage.Controller.Store.DestroyFunc() 294 295 ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), namespace) 296 rc, err := createController(storage.Controller, *validController, t) 297 if err != nil { 298 t.Fatalf("error setting new replication controller %v: %v", *validController, err) 299 } 300 301 want := &autoscaling.Scale{ 302 ObjectMeta: metav1.ObjectMeta{ 303 Name: name, 304 Namespace: namespace, 305 UID: rc.UID, 306 ResourceVersion: rc.ResourceVersion, 307 CreationTimestamp: rc.CreationTimestamp, 308 }, 309 Spec: autoscaling.ScaleSpec{ 310 Replicas: validController.Spec.Replicas, 311 }, 312 Status: autoscaling.ScaleStatus{ 313 Replicas: validController.Status.Replicas, 314 Selector: labels.SelectorFromSet(validController.Spec.Template.Labels).String(), 315 }, 316 } 317 obj, err := storage.Scale.Get(ctx, name, &metav1.GetOptions{}) 318 if err != nil { 319 t.Fatalf("error fetching scale for %s: %v", name, err) 320 } 321 got := obj.(*autoscaling.Scale) 322 if !apiequality.Semantic.DeepEqual(want, got) { 323 t.Errorf("unexpected scale: %s", cmp.Diff(want, got)) 324 } 325 } 326 327 func TestScaleUpdate(t *testing.T) { 328 storage, server := newStorage(t) 329 defer server.Terminate(t) 330 defer storage.Controller.Store.DestroyFunc() 331 332 ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), namespace) 333 rc, err := createController(storage.Controller, *validController, t) 334 if err != nil { 335 t.Fatalf("error setting new replication controller %v: %v", *validController, err) 336 } 337 replicas := int32(12) 338 update := autoscaling.Scale{ 339 ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace}, 340 Spec: autoscaling.ScaleSpec{ 341 Replicas: replicas, 342 }, 343 } 344 345 if _, _, err := storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}); err != nil { 346 t.Fatalf("error updating scale %v: %v", update, err) 347 } 348 obj, err := storage.Scale.Get(ctx, name, &metav1.GetOptions{}) 349 if err != nil { 350 t.Fatalf("error fetching scale for %s: %v", name, err) 351 } 352 scale := obj.(*autoscaling.Scale) 353 if scale.Spec.Replicas != replicas { 354 t.Errorf("wrong replicas count expected: %d got: %d", replicas, rc.Spec.Replicas) 355 } 356 357 update.ResourceVersion = rc.ResourceVersion 358 update.Spec.Replicas = 15 359 360 if _, _, err = storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}); err != nil && !errors.IsConflict(err) { 361 t.Fatalf("unexpected error, expecting an update conflict but got %v", err) 362 } 363 } 364 365 func TestShortNames(t *testing.T) { 366 storage, server := newStorage(t) 367 defer server.Terminate(t) 368 defer storage.Controller.Store.DestroyFunc() 369 expected := []string{"rc"} 370 registrytest.AssertShortNames(t, storage.Controller, expected) 371 } 372 373 func TestCategories(t *testing.T) { 374 storage, server := newStorage(t) 375 defer server.Terminate(t) 376 defer storage.Controller.Store.DestroyFunc() 377 expected := []string{"all"} 378 registrytest.AssertCategories(t, storage.Controller, expected) 379 } 380 381 func TestScalePatchErrors(t *testing.T) { 382 storage, server := newStorage(t) 383 defer server.Terminate(t) 384 validObj := validController 385 resourceStore := storage.Controller.Store 386 scaleStore := storage.Scale 387 388 defer resourceStore.DestroyFunc() 389 ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), namespace) 390 391 { 392 applyNotFoundPatch := func() rest.TransformFunc { 393 return func(_ context.Context, _, currentObject runtime.Object) (objToUpdate runtime.Object, patchErr error) { 394 t.Errorf("notfound patch called") 395 return currentObject, nil 396 } 397 } 398 _, _, err := scaleStore.Update(ctx, "bad-name", rest.DefaultUpdatedObjectInfo(nil, applyNotFoundPatch()), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}) 399 if !errors.IsNotFound(err) { 400 t.Errorf("expected notfound, got %v", err) 401 } 402 } 403 404 if _, err := resourceStore.Create(ctx, validObj, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}); err != nil { 405 t.Errorf("Unexpected error: %v", err) 406 } 407 408 { 409 applyBadUIDPatch := func() rest.TransformFunc { 410 return func(_ context.Context, _, currentObject runtime.Object) (objToUpdate runtime.Object, patchErr error) { 411 currentObject.(*autoscaling.Scale).UID = "123" 412 return currentObject, nil 413 } 414 } 415 _, _, err := scaleStore.Update(ctx, name, rest.DefaultUpdatedObjectInfo(nil, applyBadUIDPatch()), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}) 416 if !errors.IsConflict(err) { 417 t.Errorf("expected conflict, got %v", err) 418 } 419 } 420 421 { 422 applyBadResourceVersionPatch := func() rest.TransformFunc { 423 return func(_ context.Context, _, currentObject runtime.Object) (objToUpdate runtime.Object, patchErr error) { 424 currentObject.(*autoscaling.Scale).ResourceVersion = "123" 425 return currentObject, nil 426 } 427 } 428 _, _, err := scaleStore.Update(ctx, name, rest.DefaultUpdatedObjectInfo(nil, applyBadResourceVersionPatch()), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}) 429 if !errors.IsConflict(err) { 430 t.Errorf("expected conflict, got %v", err) 431 } 432 } 433 } 434 435 func TestScalePatchConflicts(t *testing.T) { 436 storage, server := newStorage(t) 437 defer server.Terminate(t) 438 validObj := validController 439 resourceStore := storage.Controller.Store 440 scaleStore := storage.Scale 441 442 defer resourceStore.DestroyFunc() 443 ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), namespace) 444 if _, err := resourceStore.Create(ctx, validObj, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}); err != nil { 445 t.Fatalf("Unexpected error: %v", err) 446 } 447 applyLabelPatch := func(labelName, labelValue string) rest.TransformFunc { 448 return func(_ context.Context, _, currentObject runtime.Object) (objToUpdate runtime.Object, patchErr error) { 449 currentObject.(metav1.Object).SetLabels(map[string]string{labelName: labelValue}) 450 return currentObject, nil 451 } 452 } 453 stopCh := make(chan struct{}) 454 wg := &sync.WaitGroup{} 455 wg.Add(1) 456 go func() { 457 defer wg.Done() 458 // continuously submits a patch that updates a label and verifies the label update was effective 459 labelName := "timestamp" 460 for i := 0; ; i++ { 461 select { 462 case <-stopCh: 463 return 464 default: 465 expectedLabelValue := fmt.Sprint(i) 466 updated, _, err := resourceStore.Update(ctx, name, rest.DefaultUpdatedObjectInfo(nil, applyLabelPatch(labelName, fmt.Sprint(i))), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}) 467 if err != nil { 468 t.Errorf("error patching main resource: %v", err) 469 return 470 } 471 gotLabelValue := updated.(metav1.Object).GetLabels()[labelName] 472 if gotLabelValue != expectedLabelValue { 473 t.Errorf("wrong label value: expected: %s, got: %s", expectedLabelValue, gotLabelValue) 474 return 475 } 476 } 477 } 478 }() 479 480 // continuously submits a scale patch of replicas for a monotonically increasing replica value 481 applyReplicaPatch := func(replicas int) rest.TransformFunc { 482 return func(_ context.Context, _, currentObject runtime.Object) (objToUpdate runtime.Object, patchErr error) { 483 currentObject.(*autoscaling.Scale).Spec.Replicas = int32(replicas) 484 return currentObject, nil 485 } 486 } 487 for i := 0; i < 100; i++ { 488 result, _, err := scaleStore.Update(ctx, name, rest.DefaultUpdatedObjectInfo(nil, applyReplicaPatch(i)), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}) 489 if err != nil { 490 t.Fatalf("error patching scale: %v", err) 491 } 492 scale := result.(*autoscaling.Scale) 493 if scale.Spec.Replicas != int32(i) { 494 t.Errorf("wrong replicas count: expected: %d got: %d", i, scale.Spec.Replicas) 495 } 496 } 497 close(stopCh) 498 wg.Wait() 499 }