k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/integration/replicaset/replicaset_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 replicaset 18 19 import ( 20 "context" 21 "fmt" 22 "reflect" 23 "strings" 24 "testing" 25 "time" 26 27 apps "k8s.io/api/apps/v1" 28 v1 "k8s.io/api/core/v1" 29 apierrors "k8s.io/apimachinery/pkg/api/errors" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/labels" 32 "k8s.io/apimachinery/pkg/util/uuid" 33 "k8s.io/apimachinery/pkg/util/wait" 34 utilfeature "k8s.io/apiserver/pkg/util/feature" 35 "k8s.io/client-go/informers" 36 clientset "k8s.io/client-go/kubernetes" 37 appsclient "k8s.io/client-go/kubernetes/typed/apps/v1" 38 typedv1 "k8s.io/client-go/kubernetes/typed/core/v1" 39 restclient "k8s.io/client-go/rest" 40 "k8s.io/client-go/tools/cache" 41 "k8s.io/client-go/util/retry" 42 featuregatetesting "k8s.io/component-base/featuregate/testing" 43 kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing" 44 podutil "k8s.io/kubernetes/pkg/api/v1/pod" 45 "k8s.io/kubernetes/pkg/apis/core" 46 "k8s.io/kubernetes/pkg/controller/replicaset" 47 "k8s.io/kubernetes/pkg/features" 48 "k8s.io/kubernetes/test/integration/framework" 49 testutil "k8s.io/kubernetes/test/utils" 50 "k8s.io/kubernetes/test/utils/ktesting" 51 "k8s.io/utils/ptr" 52 ) 53 54 const ( 55 interval = 100 * time.Millisecond 56 timeout = 60 * time.Second 57 ) 58 59 func labelMap() map[string]string { 60 return map[string]string{"foo": "bar"} 61 } 62 63 func newRS(name, namespace string, replicas int) *apps.ReplicaSet { 64 replicasCopy := int32(replicas) 65 return &apps.ReplicaSet{ 66 TypeMeta: metav1.TypeMeta{ 67 Kind: "ReplicaSet", 68 APIVersion: "apps/v1", 69 }, 70 ObjectMeta: metav1.ObjectMeta{ 71 Namespace: namespace, 72 Name: name, 73 }, 74 Spec: apps.ReplicaSetSpec{ 75 Selector: &metav1.LabelSelector{ 76 MatchLabels: labelMap(), 77 }, 78 Replicas: &replicasCopy, 79 Template: v1.PodTemplateSpec{ 80 ObjectMeta: metav1.ObjectMeta{ 81 Labels: labelMap(), 82 }, 83 Spec: v1.PodSpec{ 84 Containers: []v1.Container{ 85 { 86 Name: "fake-name", 87 Image: "fakeimage", 88 }, 89 }, 90 }, 91 }, 92 }, 93 } 94 } 95 96 func newMatchingPod(podName, namespace string) *v1.Pod { 97 return &v1.Pod{ 98 TypeMeta: metav1.TypeMeta{ 99 Kind: "Pod", 100 APIVersion: "v1", 101 }, 102 ObjectMeta: metav1.ObjectMeta{ 103 Name: podName, 104 Namespace: namespace, 105 Labels: labelMap(), 106 }, 107 Spec: v1.PodSpec{ 108 Containers: []v1.Container{ 109 { 110 Name: "fake-name", 111 Image: "fakeimage", 112 }, 113 }, 114 }, 115 Status: v1.PodStatus{ 116 Phase: v1.PodRunning, 117 }, 118 } 119 } 120 121 func rmSetup(t *testing.T) (context.Context, kubeapiservertesting.TearDownFunc, *replicaset.ReplicaSetController, informers.SharedInformerFactory, clientset.Interface) { 122 tCtx := ktesting.Init(t) 123 // Disable ServiceAccount admission plugin as we don't have serviceaccount controller running. 124 server := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--disable-admission-plugins=ServiceAccount"}, framework.SharedEtcd()) 125 126 config := restclient.CopyConfig(server.ClientConfig) 127 clientSet, err := clientset.NewForConfig(config) 128 if err != nil { 129 t.Fatalf("Error in create clientset: %v", err) 130 } 131 resyncPeriod := 12 * time.Hour 132 informers := informers.NewSharedInformerFactory(clientset.NewForConfigOrDie(restclient.AddUserAgent(config, "rs-informers")), resyncPeriod) 133 134 rm := replicaset.NewReplicaSetController( 135 tCtx, 136 informers.Apps().V1().ReplicaSets(), 137 informers.Core().V1().Pods(), 138 clientset.NewForConfigOrDie(restclient.AddUserAgent(config, "replicaset-controller")), 139 replicaset.BurstReplicas, 140 ) 141 142 newTeardown := func() { 143 tCtx.Cancel("tearing down controller") 144 server.TearDownFn() 145 } 146 147 return tCtx, newTeardown, rm, informers, clientSet 148 } 149 150 func rmSimpleSetup(t *testing.T) (kubeapiservertesting.TearDownFunc, clientset.Interface) { 151 // Disable ServiceAccount admission plugin as we don't have serviceaccount controller running. 152 server := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--disable-admission-plugins=ServiceAccount"}, framework.SharedEtcd()) 153 154 config := restclient.CopyConfig(server.ClientConfig) 155 clientSet, err := clientset.NewForConfig(config) 156 if err != nil { 157 t.Fatalf("Error in create clientset: %v", err) 158 } 159 return server.TearDownFn, clientSet 160 } 161 162 // Run RS controller and informers 163 func runControllerAndInformers(t *testing.T, rm *replicaset.ReplicaSetController, informers informers.SharedInformerFactory, podNum int) func() { 164 ctx, cancelFn := context.WithCancel(context.Background()) 165 informers.Start(ctx.Done()) 166 waitToObservePods(t, informers.Core().V1().Pods().Informer(), podNum) 167 go rm.Run(ctx, 5) 168 return cancelFn 169 } 170 171 // wait for the podInformer to observe the pods. Call this function before 172 // running the RS controller to prevent the rc manager from creating new pods 173 // rather than adopting the existing ones. 174 func waitToObservePods(t *testing.T, podInformer cache.SharedIndexInformer, podNum int) { 175 if err := wait.PollImmediate(interval, timeout, func() (bool, error) { 176 objects := podInformer.GetIndexer().List() 177 return len(objects) == podNum, nil 178 }); err != nil { 179 t.Fatalf("Error encountered when waiting for podInformer to observe the pods: %v", err) 180 } 181 } 182 183 func createRSsPods(t *testing.T, clientSet clientset.Interface, rss []*apps.ReplicaSet, pods []*v1.Pod) ([]*apps.ReplicaSet, []*v1.Pod) { 184 var createdRSs []*apps.ReplicaSet 185 var createdPods []*v1.Pod 186 for _, rs := range rss { 187 createdRS, err := clientSet.AppsV1().ReplicaSets(rs.Namespace).Create(context.TODO(), rs, metav1.CreateOptions{}) 188 if err != nil { 189 t.Fatalf("Failed to create replica set %s: %v", rs.Name, err) 190 } 191 createdRSs = append(createdRSs, createdRS) 192 } 193 for _, pod := range pods { 194 createdPod, err := clientSet.CoreV1().Pods(pod.Namespace).Create(context.TODO(), pod, metav1.CreateOptions{}) 195 if err != nil { 196 t.Fatalf("Failed to create pod %s: %v", pod.Name, err) 197 } 198 createdPods = append(createdPods, createdPod) 199 } 200 201 return createdRSs, createdPods 202 } 203 204 // Verify .Status.Replicas is equal to .Spec.Replicas 205 func waitRSStable(t *testing.T, clientSet clientset.Interface, rs *apps.ReplicaSet) { 206 if err := testutil.WaitRSStable(t, clientSet, rs, interval, timeout); err != nil { 207 t.Fatal(err) 208 } 209 } 210 211 // Update .Spec.Replicas to replicas and verify .Status.Replicas is changed accordingly 212 func scaleRS(t *testing.T, c clientset.Interface, rs *apps.ReplicaSet, replicas int32) { 213 rsClient := c.AppsV1().ReplicaSets(rs.Namespace) 214 rs = updateRS(t, rsClient, rs.Name, func(rs *apps.ReplicaSet) { 215 *rs.Spec.Replicas = replicas 216 }) 217 waitRSStable(t, c, rs) 218 } 219 220 func updatePod(t *testing.T, podClient typedv1.PodInterface, podName string, updateFunc func(*v1.Pod)) *v1.Pod { 221 var pod *v1.Pod 222 if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error { 223 newPod, err := podClient.Get(context.TODO(), podName, metav1.GetOptions{}) 224 if err != nil { 225 return err 226 } 227 updateFunc(newPod) 228 pod, err = podClient.Update(context.TODO(), newPod, metav1.UpdateOptions{}) 229 return err 230 }); err != nil { 231 t.Fatalf("Failed to update pod %s: %v", podName, err) 232 } 233 return pod 234 } 235 236 func updatePodStatus(t *testing.T, podClient typedv1.PodInterface, podName string, updateStatusFunc func(*v1.Pod)) { 237 if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error { 238 newPod, err := podClient.Get(context.TODO(), podName, metav1.GetOptions{}) 239 if err != nil { 240 return err 241 } 242 updateStatusFunc(newPod) 243 _, err = podClient.UpdateStatus(context.TODO(), newPod, metav1.UpdateOptions{}) 244 return err 245 }); err != nil { 246 t.Fatalf("Failed to update status of pod %s: %v", podName, err) 247 } 248 } 249 250 func getPods(t *testing.T, podClient typedv1.PodInterface, labelMap map[string]string) *v1.PodList { 251 podSelector := labels.Set(labelMap).AsSelector() 252 options := metav1.ListOptions{LabelSelector: podSelector.String()} 253 pods, err := podClient.List(context.TODO(), options) 254 if err != nil { 255 t.Fatalf("Failed obtaining a list of pods that match the pod labels %v: %v", labelMap, err) 256 } 257 return pods 258 } 259 260 func updateRS(t *testing.T, rsClient appsclient.ReplicaSetInterface, rsName string, updateFunc func(*apps.ReplicaSet)) *apps.ReplicaSet { 261 var rs *apps.ReplicaSet 262 if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error { 263 newRS, err := rsClient.Get(context.TODO(), rsName, metav1.GetOptions{}) 264 if err != nil { 265 return err 266 } 267 updateFunc(newRS) 268 rs, err = rsClient.Update(context.TODO(), newRS, metav1.UpdateOptions{}) 269 return err 270 }); err != nil { 271 t.Fatalf("Failed to update rs %s: %v", rsName, err) 272 } 273 return rs 274 } 275 276 // Verify ControllerRef of a RS pod that has incorrect attributes is automatically patched by the RS 277 func testPodControllerRefPatch(t *testing.T, c clientset.Interface, pod *v1.Pod, ownerReference *metav1.OwnerReference, rs *apps.ReplicaSet, expectedOwnerReferenceNum int) { 278 ns := rs.Namespace 279 podClient := c.CoreV1().Pods(ns) 280 updatePod(t, podClient, pod.Name, func(pod *v1.Pod) { 281 pod.OwnerReferences = []metav1.OwnerReference{*ownerReference} 282 }) 283 284 if err := wait.PollImmediate(interval, timeout, func() (bool, error) { 285 newPod, err := podClient.Get(context.TODO(), pod.Name, metav1.GetOptions{}) 286 if err != nil { 287 return false, err 288 } 289 return metav1.GetControllerOf(newPod) != nil, nil 290 }); err != nil { 291 t.Fatalf("Failed to verify ControllerRef for the pod %s is not nil: %v", pod.Name, err) 292 } 293 294 newPod, err := podClient.Get(context.TODO(), pod.Name, metav1.GetOptions{}) 295 if err != nil { 296 t.Fatalf("Failed to obtain pod %s: %v", pod.Name, err) 297 } 298 controllerRef := metav1.GetControllerOf(newPod) 299 if controllerRef.UID != rs.UID { 300 t.Fatalf("RS owner of the pod %s has a different UID: Expected %v, got %v", newPod.Name, rs.UID, controllerRef.UID) 301 } 302 ownerReferenceNum := len(newPod.GetOwnerReferences()) 303 if ownerReferenceNum != expectedOwnerReferenceNum { 304 t.Fatalf("Unexpected number of owner references for pod %s: Expected %d, got %d", newPod.Name, expectedOwnerReferenceNum, ownerReferenceNum) 305 } 306 } 307 308 func setPodsReadyCondition(t *testing.T, clientSet clientset.Interface, pods *v1.PodList, conditionStatus v1.ConditionStatus, lastTransitionTime time.Time) { 309 replicas := int32(len(pods.Items)) 310 var readyPods int32 311 err := wait.PollImmediate(interval, timeout, func() (bool, error) { 312 readyPods = 0 313 for i := range pods.Items { 314 pod := &pods.Items[i] 315 if podutil.IsPodReady(pod) { 316 readyPods++ 317 continue 318 } 319 pod.Status.Phase = v1.PodRunning 320 _, condition := podutil.GetPodCondition(&pod.Status, v1.PodReady) 321 if condition != nil { 322 condition.Status = conditionStatus 323 condition.LastTransitionTime = metav1.Time{Time: lastTransitionTime} 324 } else { 325 condition = &v1.PodCondition{ 326 Type: v1.PodReady, 327 Status: conditionStatus, 328 LastTransitionTime: metav1.Time{Time: lastTransitionTime}, 329 } 330 pod.Status.Conditions = append(pod.Status.Conditions, *condition) 331 } 332 _, err := clientSet.CoreV1().Pods(pod.Namespace).UpdateStatus(context.TODO(), pod, metav1.UpdateOptions{}) 333 if err != nil { 334 // When status fails to be updated, we continue to next pod 335 continue 336 } 337 readyPods++ 338 } 339 return readyPods >= replicas, nil 340 }) 341 if err != nil { 342 t.Fatalf("failed to mark all ReplicaSet pods to ready: %v", err) 343 } 344 } 345 346 func testScalingUsingScaleSubresource(t *testing.T, c clientset.Interface, rs *apps.ReplicaSet, replicas int32) { 347 ns := rs.Namespace 348 rsClient := c.AppsV1().ReplicaSets(ns) 349 newRS, err := rsClient.Get(context.TODO(), rs.Name, metav1.GetOptions{}) 350 if err != nil { 351 t.Fatalf("Failed to obtain rs %s: %v", rs.Name, err) 352 } 353 scale, err := c.AppsV1().ReplicaSets(ns).GetScale(context.TODO(), rs.Name, metav1.GetOptions{}) 354 if err != nil { 355 t.Fatalf("Failed to obtain scale subresource for rs %s: %v", rs.Name, err) 356 } 357 if scale.Spec.Replicas != *newRS.Spec.Replicas { 358 t.Fatalf("Scale subresource for rs %s does not match .Spec.Replicas: expected %d, got %d", rs.Name, *newRS.Spec.Replicas, scale.Spec.Replicas) 359 } 360 361 if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error { 362 scale, err := c.AppsV1().ReplicaSets(ns).GetScale(context.TODO(), rs.Name, metav1.GetOptions{}) 363 if err != nil { 364 return err 365 } 366 scale.Spec.Replicas = replicas 367 _, err = c.AppsV1().ReplicaSets(ns).UpdateScale(context.TODO(), rs.Name, scale, metav1.UpdateOptions{}) 368 return err 369 }); err != nil { 370 t.Fatalf("Failed to set .Spec.Replicas of scale subresource for rs %s: %v", rs.Name, err) 371 } 372 373 newRS, err = rsClient.Get(context.TODO(), rs.Name, metav1.GetOptions{}) 374 if err != nil { 375 t.Fatalf("Failed to obtain rs %s: %v", rs.Name, err) 376 } 377 if *newRS.Spec.Replicas != replicas { 378 t.Fatalf(".Spec.Replicas of rs %s does not match its scale subresource: expected %d, got %d", rs.Name, replicas, *newRS.Spec.Replicas) 379 } 380 } 381 382 func TestAdoption(t *testing.T) { 383 boolPtr := func(b bool) *bool { return &b } 384 testCases := []struct { 385 name string 386 existingOwnerReferences func(rs *apps.ReplicaSet) []metav1.OwnerReference 387 expectedOwnerReferences func(rs *apps.ReplicaSet) []metav1.OwnerReference 388 }{ 389 { 390 "pod refers rs as an owner, not a controller", 391 func(rs *apps.ReplicaSet) []metav1.OwnerReference { 392 return []metav1.OwnerReference{{UID: rs.UID, Name: rs.Name, APIVersion: "apps/v1", Kind: "ReplicaSet"}} 393 }, 394 func(rs *apps.ReplicaSet) []metav1.OwnerReference { 395 return []metav1.OwnerReference{{UID: rs.UID, Name: rs.Name, APIVersion: "apps/v1", Kind: "ReplicaSet", Controller: boolPtr(true), BlockOwnerDeletion: boolPtr(true)}} 396 }, 397 }, 398 { 399 "pod doesn't have owner references", 400 func(rs *apps.ReplicaSet) []metav1.OwnerReference { 401 return []metav1.OwnerReference{} 402 }, 403 func(rs *apps.ReplicaSet) []metav1.OwnerReference { 404 return []metav1.OwnerReference{{UID: rs.UID, Name: rs.Name, APIVersion: "apps/v1", Kind: "ReplicaSet", Controller: boolPtr(true), BlockOwnerDeletion: boolPtr(true)}} 405 }, 406 }, 407 { 408 "pod refers rs as a controller", 409 func(rs *apps.ReplicaSet) []metav1.OwnerReference { 410 return []metav1.OwnerReference{{UID: rs.UID, Name: rs.Name, APIVersion: "apps/v1", Kind: "ReplicaSet", Controller: boolPtr(true)}} 411 }, 412 func(rs *apps.ReplicaSet) []metav1.OwnerReference { 413 return []metav1.OwnerReference{{UID: rs.UID, Name: rs.Name, APIVersion: "apps/v1", Kind: "ReplicaSet", Controller: boolPtr(true)}} 414 }, 415 }, 416 { 417 "pod refers other rs as the controller, refers the rs as an owner", 418 func(rs *apps.ReplicaSet) []metav1.OwnerReference { 419 return []metav1.OwnerReference{ 420 {UID: "1", Name: "anotherRS", APIVersion: "apps/v1", Kind: "ReplicaSet", Controller: boolPtr(true)}, 421 {UID: rs.UID, Name: rs.Name, APIVersion: "apps/v1", Kind: "ReplicaSet"}, 422 } 423 }, 424 func(rs *apps.ReplicaSet) []metav1.OwnerReference { 425 return []metav1.OwnerReference{ 426 {UID: "1", Name: "anotherRS", APIVersion: "apps/v1", Kind: "ReplicaSet", Controller: boolPtr(true)}, 427 {UID: rs.UID, Name: rs.Name, APIVersion: "apps/v1", Kind: "ReplicaSet"}, 428 } 429 }, 430 }, 431 } 432 for i, tc := range testCases { 433 t.Run(tc.name, func(t *testing.T) { 434 tCtx, closeFn, rm, informers, clientSet := rmSetup(t) 435 defer closeFn() 436 437 ns := framework.CreateNamespaceOrDie(clientSet, fmt.Sprintf("rs-adoption-%d", i), t) 438 defer framework.DeleteNamespaceOrDie(clientSet, ns, t) 439 440 rsClient := clientSet.AppsV1().ReplicaSets(ns.Name) 441 podClient := clientSet.CoreV1().Pods(ns.Name) 442 const rsName = "rs" 443 rs, err := rsClient.Create(tCtx, newRS(rsName, ns.Name, 1), metav1.CreateOptions{}) 444 if err != nil { 445 t.Fatalf("Failed to create replica set: %v", err) 446 } 447 podName := fmt.Sprintf("pod%d", i) 448 pod := newMatchingPod(podName, ns.Name) 449 pod.OwnerReferences = tc.existingOwnerReferences(rs) 450 _, err = podClient.Create(tCtx, pod, metav1.CreateOptions{}) 451 if err != nil { 452 t.Fatalf("Failed to create Pod: %v", err) 453 } 454 455 stopControllers := runControllerAndInformers(t, rm, informers, 1) 456 defer stopControllers() 457 if err := wait.PollImmediate(interval, timeout, func() (bool, error) { 458 updatedPod, err := podClient.Get(tCtx, pod.Name, metav1.GetOptions{}) 459 if err != nil { 460 return false, err 461 } 462 463 e, a := tc.expectedOwnerReferences(rs), updatedPod.OwnerReferences 464 if reflect.DeepEqual(e, a) { 465 return true, nil 466 } 467 468 t.Logf("ownerReferences don't match, expect %v, got %v", e, a) 469 return false, nil 470 }); err != nil { 471 t.Fatalf("test %q failed: %v", tc.name, err) 472 } 473 }) 474 } 475 } 476 477 // selectors are IMMUTABLE for all API versions except extensions/v1beta1 478 func TestRSSelectorImmutability(t *testing.T) { 479 closeFn, clientSet := rmSimpleSetup(t) 480 defer closeFn() 481 ns := framework.CreateNamespaceOrDie(clientSet, "rs-selector-immutability", t) 482 defer framework.DeleteNamespaceOrDie(clientSet, ns, t) 483 rs := newRS("rs", ns.Name, 0) 484 createRSsPods(t, clientSet, []*apps.ReplicaSet{rs}, []*v1.Pod{}) 485 486 // test to ensure apps/v1 selector is immutable 487 rsV1, err := clientSet.AppsV1().ReplicaSets(ns.Name).Get(context.TODO(), rs.Name, metav1.GetOptions{}) 488 if err != nil { 489 t.Fatalf("failed to get apps/v1 replicaset %s: %v", rs.Name, err) 490 } 491 newSelectorLabels := map[string]string{"changed_name_apps_v1": "changed_test_apps_v1"} 492 rsV1.Spec.Selector.MatchLabels = newSelectorLabels 493 rsV1.Spec.Template.Labels = newSelectorLabels 494 _, err = clientSet.AppsV1().ReplicaSets(ns.Name).Update(context.TODO(), rsV1, metav1.UpdateOptions{}) 495 if err == nil { 496 t.Fatalf("failed to provide validation error when changing immutable selector when updating apps/v1 replicaset %s", rsV1.Name) 497 } 498 expectedErrType := "Invalid value" 499 expectedErrDetail := "field is immutable" 500 if !strings.Contains(err.Error(), expectedErrType) || !strings.Contains(err.Error(), expectedErrDetail) { 501 t.Errorf("error message does not match, expected type: %s, expected detail: %s, got: %s", expectedErrType, expectedErrDetail, err.Error()) 502 } 503 } 504 505 func TestSpecReplicasChange(t *testing.T) { 506 tCtx, closeFn, rm, informers, c := rmSetup(t) 507 defer closeFn() 508 ns := framework.CreateNamespaceOrDie(c, "test-spec-replicas-change", t) 509 defer framework.DeleteNamespaceOrDie(c, ns, t) 510 stopControllers := runControllerAndInformers(t, rm, informers, 0) 511 defer stopControllers() 512 513 rs := newRS("rs", ns.Name, 2) 514 rss, _ := createRSsPods(t, c, []*apps.ReplicaSet{rs}, []*v1.Pod{}) 515 rs = rss[0] 516 waitRSStable(t, c, rs) 517 518 // Update .Spec.Replicas and verify .Status.Replicas is changed accordingly 519 scaleRS(t, c, rs, 3) 520 scaleRS(t, c, rs, 0) 521 scaleRS(t, c, rs, 2) 522 523 // Add a template annotation change to test RS's status does update 524 // without .Spec.Replicas change 525 rsClient := c.AppsV1().ReplicaSets(ns.Name) 526 var oldGeneration int64 527 newRS := updateRS(t, rsClient, rs.Name, func(rs *apps.ReplicaSet) { 528 oldGeneration = rs.Generation 529 rs.Spec.Template.Annotations = map[string]string{"test": "annotation"} 530 }) 531 savedGeneration := newRS.Generation 532 if savedGeneration == oldGeneration { 533 t.Fatalf("Failed to verify .Generation has incremented for rs %s", rs.Name) 534 } 535 536 if err := wait.PollImmediate(interval, timeout, func() (bool, error) { 537 newRS, err := rsClient.Get(tCtx, rs.Name, metav1.GetOptions{}) 538 if err != nil { 539 return false, err 540 } 541 return newRS.Status.ObservedGeneration >= savedGeneration, nil 542 }); err != nil { 543 t.Fatalf("Failed to verify .Status.ObservedGeneration has incremented for rs %s: %v", rs.Name, err) 544 } 545 } 546 547 func TestDeletingAndFailedPods(t *testing.T) { 548 tCtx, closeFn, rm, informers, c := rmSetup(t) 549 defer closeFn() 550 551 ns := framework.CreateNamespaceOrDie(c, "test-deleting-and-failed-pods", t) 552 defer framework.DeleteNamespaceOrDie(c, ns, t) 553 stopControllers := runControllerAndInformers(t, rm, informers, 0) 554 defer stopControllers() 555 556 rs := newRS("rs", ns.Name, 2) 557 rss, _ := createRSsPods(t, c, []*apps.ReplicaSet{rs}, []*v1.Pod{}) 558 rs = rss[0] 559 waitRSStable(t, c, rs) 560 561 // Verify RS creates 2 pods 562 podClient := c.CoreV1().Pods(ns.Name) 563 pods := getPods(t, podClient, labelMap()) 564 if len(pods.Items) != 2 { 565 t.Fatalf("len(pods) = %d, want 2", len(pods.Items)) 566 } 567 568 // Set first pod as deleting pod 569 // Set finalizers for the pod to simulate pending deletion status 570 deletingPod := &pods.Items[0] 571 updatePod(t, podClient, deletingPod.Name, func(pod *v1.Pod) { 572 pod.Finalizers = []string{"fake.example.com/blockDeletion"} 573 }) 574 if err := c.CoreV1().Pods(ns.Name).Delete(tCtx, deletingPod.Name, metav1.DeleteOptions{}); err != nil { 575 t.Fatalf("Error deleting pod %s: %v", deletingPod.Name, err) 576 } 577 578 // Set second pod as failed pod 579 failedPod := &pods.Items[1] 580 updatePodStatus(t, podClient, failedPod.Name, func(pod *v1.Pod) { 581 pod.Status.Phase = v1.PodFailed 582 }) 583 584 // Pool until 2 new pods have been created to replace deleting and failed pods 585 if err := wait.PollImmediate(interval, timeout, func() (bool, error) { 586 pods = getPods(t, podClient, labelMap()) 587 return len(pods.Items) == 4, nil 588 }); err != nil { 589 t.Fatalf("Failed to verify 2 new pods have been created (expected 4 pods): %v", err) 590 } 591 592 // Verify deleting and failed pods are among the four pods 593 foundDeletingPod := false 594 foundFailedPod := false 595 for _, pod := range pods.Items { 596 if pod.UID == deletingPod.UID { 597 foundDeletingPod = true 598 } 599 if pod.UID == failedPod.UID { 600 foundFailedPod = true 601 } 602 } 603 // Verify deleting pod exists 604 if !foundDeletingPod { 605 t.Fatalf("expected deleting pod %s exists, but it is not found", deletingPod.Name) 606 } 607 // Verify failed pod exists 608 if !foundFailedPod { 609 t.Fatalf("expected failed pod %s exists, but it is not found", failedPod.Name) 610 } 611 } 612 613 func TestPodDeletionCost(t *testing.T) { 614 tests := []struct { 615 name string 616 costs []string 617 restarts []int32 618 enabled bool 619 remainingPodIndex int 620 }{ 621 { 622 name: "enabled-with-different-costs", 623 costs: []string{"1000", "100"}, 624 restarts: []int32{5, 0}, 625 enabled: true, 626 remainingPodIndex: 0, 627 }, 628 { 629 name: "enabled-with-same-costs", 630 costs: []string{"100", "100"}, 631 restarts: []int32{5, 0}, 632 enabled: true, 633 remainingPodIndex: 1, 634 }, 635 { 636 name: "enabled-with-no-costs", 637 restarts: []int32{5, 0}, 638 enabled: true, 639 remainingPodIndex: 1, 640 }, 641 { 642 name: "disabled-with-different-costs", 643 costs: []string{"1000", "100"}, 644 restarts: []int32{5, 0}, 645 enabled: false, 646 remainingPodIndex: 1, 647 }, 648 } 649 for _, tc := range tests { 650 t.Run(tc.name, func(t *testing.T) { 651 featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodDeletionCost, tc.enabled) 652 _, closeFn, rm, informers, c := rmSetup(t) 653 defer closeFn() 654 ns := framework.CreateNamespaceOrDie(c, tc.name, t) 655 defer framework.DeleteNamespaceOrDie(c, ns, t) 656 stopControllers := runControllerAndInformers(t, rm, informers, 0) 657 defer stopControllers() 658 659 rs := newRS("rs", ns.Name, 2) 660 rss, _ := createRSsPods(t, c, []*apps.ReplicaSet{rs}, []*v1.Pod{}) 661 rs = rss[0] 662 waitRSStable(t, c, rs) 663 664 // Verify RS creates 2 pods. 665 podClient := c.CoreV1().Pods(ns.Name) 666 pods := getPods(t, podClient, labelMap()) 667 if err := wait.PollImmediate(interval, timeout, func() (bool, error) { 668 pods = getPods(t, podClient, labelMap()) 669 return len(pods.Items) == 2, nil 670 }); err != nil { 671 t.Fatalf("Failed to verify replicaset has 2 pods: %v", err) 672 } 673 674 // Set a higher deletion cost to the pod that is supposed to remain after scale down. 675 remainingPodUID := pods.Items[tc.remainingPodIndex].UID 676 for i := range pods.Items { 677 podName := pods.Items[i].Name 678 if len(tc.costs) != 0 { 679 updatePod(t, podClient, podName, func(pod *v1.Pod) { 680 pod.Annotations = map[string]string{core.PodDeletionCost: tc.costs[i]} 681 }) 682 } 683 updatePodStatus(t, podClient, podName, func(pod *v1.Pod) { 684 pod.Status.ContainerStatuses = []v1.ContainerStatus{{RestartCount: tc.restarts[i]}} 685 }) 686 } 687 688 // Change RS's number of replics to 1 689 rsClient := c.AppsV1().ReplicaSets(ns.Name) 690 updateRS(t, rsClient, rs.Name, func(rs *apps.ReplicaSet) { 691 rs.Spec.Replicas = ptr.To[int32](1) 692 }) 693 694 // Poll until ReplicaSet is downscaled to 1. 695 if err := wait.PollImmediate(interval, timeout, func() (bool, error) { 696 pods = getPods(t, podClient, labelMap()) 697 return len(pods.Items) == 1, nil 698 }); err != nil { 699 t.Fatalf("Failed to downscale replicaset to 1 pod: %v", err) 700 } 701 702 if pods.Items[0].UID != remainingPodUID { 703 t.Errorf("expected remaining Pod UID %v, got UID %v with container statues %v", 704 remainingPodUID, pods.Items[0].UID, pods.Items[0].Status.ContainerStatuses) 705 } 706 }) 707 } 708 } 709 710 func TestOverlappingRSs(t *testing.T) { 711 tCtx, closeFn, rm, informers, c := rmSetup(t) 712 defer closeFn() 713 ns := framework.CreateNamespaceOrDie(c, "test-overlapping-rss", t) 714 defer framework.DeleteNamespaceOrDie(c, ns, t) 715 stopControllers := runControllerAndInformers(t, rm, informers, 0) 716 defer stopControllers() 717 718 // Create 2 RSs with identical selectors 719 for i := 0; i < 2; i++ { 720 // One RS has 1 replica, and another has 2 replicas 721 rs := newRS(fmt.Sprintf("rs-%d", i+1), ns.Name, i+1) 722 rss, _ := createRSsPods(t, c, []*apps.ReplicaSet{rs}, []*v1.Pod{}) 723 waitRSStable(t, c, rss[0]) 724 } 725 726 // Expect 3 total Pods to be created 727 podClient := c.CoreV1().Pods(ns.Name) 728 pods := getPods(t, podClient, labelMap()) 729 if len(pods.Items) != 3 { 730 t.Errorf("len(pods) = %d, want 3", len(pods.Items)) 731 } 732 733 // Expect both RSs have .status.replicas = .spec.replicas 734 for i := 0; i < 2; i++ { 735 newRS, err := c.AppsV1().ReplicaSets(ns.Name).Get(tCtx, fmt.Sprintf("rs-%d", i+1), metav1.GetOptions{}) 736 if err != nil { 737 t.Fatalf("failed to obtain rs rs-%d: %v", i+1, err) 738 } 739 if newRS.Status.Replicas != *newRS.Spec.Replicas { 740 t.Fatalf(".Status.Replicas %d is not equal to .Spec.Replicas %d", newRS.Status.Replicas, *newRS.Spec.Replicas) 741 } 742 } 743 } 744 745 func TestPodOrphaningAndAdoptionWhenLabelsChange(t *testing.T) { 746 tCtx, closeFn, rm, informers, c := rmSetup(t) 747 defer closeFn() 748 ns := framework.CreateNamespaceOrDie(c, "test-pod-orphaning-and-adoption-when-labels-change", t) 749 defer framework.DeleteNamespaceOrDie(c, ns, t) 750 stopControllers := runControllerAndInformers(t, rm, informers, 0) 751 defer stopControllers() 752 753 rs := newRS("rs", ns.Name, 1) 754 rss, _ := createRSsPods(t, c, []*apps.ReplicaSet{rs}, []*v1.Pod{}) 755 rs = rss[0] 756 waitRSStable(t, c, rs) 757 758 // Orphaning: RS should remove OwnerReference from a pod when the pod's labels change to not match its labels 759 podClient := c.CoreV1().Pods(ns.Name) 760 pods := getPods(t, podClient, labelMap()) 761 if len(pods.Items) != 1 { 762 t.Fatalf("len(pods) = %d, want 1", len(pods.Items)) 763 } 764 pod := &pods.Items[0] 765 766 // Start by verifying ControllerRef for the pod is not nil 767 if metav1.GetControllerOf(pod) == nil { 768 t.Fatalf("ControllerRef of pod %s is nil", pod.Name) 769 } 770 newLabelMap := map[string]string{"new-foo": "new-bar"} 771 updatePod(t, podClient, pod.Name, func(pod *v1.Pod) { 772 pod.Labels = newLabelMap 773 }) 774 if err := wait.PollImmediate(interval, timeout, func() (bool, error) { 775 newPod, err := podClient.Get(tCtx, pod.Name, metav1.GetOptions{}) 776 if err != nil { 777 return false, err 778 } 779 pod = newPod 780 return metav1.GetControllerOf(newPod) == nil, nil 781 }); err != nil { 782 t.Fatalf("Failed to verify ControllerRef for the pod %s is nil: %v", pod.Name, err) 783 } 784 785 // Adoption: RS should add ControllerRef to a pod when the pod's labels change to match its labels 786 updatePod(t, podClient, pod.Name, func(pod *v1.Pod) { 787 pod.Labels = labelMap() 788 }) 789 if err := wait.PollImmediate(interval, timeout, func() (bool, error) { 790 newPod, err := podClient.Get(tCtx, pod.Name, metav1.GetOptions{}) 791 if err != nil { 792 // If the pod is not found, it means the RS picks the pod for deletion (it is extra) 793 // Verify there is only one pod in namespace and it has ControllerRef to the RS 794 if !apierrors.IsNotFound(err) { 795 return false, err 796 } 797 798 pods := getPods(t, podClient, labelMap()) 799 if len(pods.Items) != 1 { 800 return false, fmt.Errorf("Expected 1 pod in current namespace, got %d", len(pods.Items)) 801 } 802 // Set the pod accordingly 803 pod = &pods.Items[0] 804 return true, nil 805 } 806 // Always update the pod so that we can save a GET call to API server later 807 pod = newPod 808 // If the pod is found, verify the pod has a ControllerRef 809 return metav1.GetControllerOf(newPod) != nil, nil 810 }); err != nil { 811 t.Fatalf("Failed to verify ControllerRef for pod %s is not nil: %v", pod.Name, err) 812 } 813 // Verify the pod has a ControllerRef to the RS 814 // Do nothing if the pod is nil (i.e., has been picked for deletion) 815 if pod != nil { 816 controllerRef := metav1.GetControllerOf(pod) 817 if controllerRef.UID != rs.UID { 818 t.Fatalf("RS owner of the pod %s has a different UID: Expected %v, got %v", pod.Name, rs.UID, controllerRef.UID) 819 } 820 } 821 } 822 823 func TestGeneralPodAdoption(t *testing.T) { 824 _, closeFn, rm, informers, c := rmSetup(t) 825 defer closeFn() 826 ns := framework.CreateNamespaceOrDie(c, "test-general-pod-adoption", t) 827 defer framework.DeleteNamespaceOrDie(c, ns, t) 828 stopControllers := runControllerAndInformers(t, rm, informers, 0) 829 defer stopControllers() 830 831 rs := newRS("rs", ns.Name, 1) 832 rss, _ := createRSsPods(t, c, []*apps.ReplicaSet{rs}, []*v1.Pod{}) 833 rs = rss[0] 834 waitRSStable(t, c, rs) 835 836 podClient := c.CoreV1().Pods(ns.Name) 837 pods := getPods(t, podClient, labelMap()) 838 if len(pods.Items) != 1 { 839 t.Fatalf("len(pods) = %d, want 1", len(pods.Items)) 840 } 841 pod := &pods.Items[0] 842 var falseVar = false 843 844 // When the only OwnerReference of the pod points to another type of API object such as statefulset 845 // with Controller=false, the RS should add a second OwnerReference (ControllerRef) pointing to itself 846 // with Controller=true 847 ownerReference := metav1.OwnerReference{UID: uuid.NewUUID(), APIVersion: "apps/v1", Kind: "StatefulSet", Name: rs.Name, Controller: &falseVar} 848 testPodControllerRefPatch(t, c, pod, &ownerReference, rs, 2) 849 850 // When the only OwnerReference of the pod points to the RS, but Controller=false 851 ownerReference = metav1.OwnerReference{UID: rs.UID, APIVersion: "apps/v1", Kind: "ReplicaSet", Name: rs.Name, Controller: &falseVar} 852 testPodControllerRefPatch(t, c, pod, &ownerReference, rs, 1) 853 } 854 855 func TestReadyAndAvailableReplicas(t *testing.T) { 856 tCtx, closeFn, rm, informers, c := rmSetup(t) 857 defer closeFn() 858 ns := framework.CreateNamespaceOrDie(c, "test-ready-and-available-replicas", t) 859 defer framework.DeleteNamespaceOrDie(c, ns, t) 860 stopControllers := runControllerAndInformers(t, rm, informers, 0) 861 defer stopControllers() 862 863 rs := newRS("rs", ns.Name, 3) 864 rs.Spec.MinReadySeconds = 3600 865 rss, _ := createRSsPods(t, c, []*apps.ReplicaSet{rs}, []*v1.Pod{}) 866 rs = rss[0] 867 waitRSStable(t, c, rs) 868 869 // First verify no pod is available 870 if rs.Status.AvailableReplicas != 0 { 871 t.Fatalf("Unexpected .Status.AvailableReplicas: Expected 0, saw %d", rs.Status.AvailableReplicas) 872 } 873 874 podClient := c.CoreV1().Pods(ns.Name) 875 pods := getPods(t, podClient, labelMap()) 876 if len(pods.Items) != 3 { 877 t.Fatalf("len(pods) = %d, want 3", len(pods.Items)) 878 } 879 880 // Separate 3 pods into their own list 881 firstPodList := &v1.PodList{Items: pods.Items[:1]} 882 secondPodList := &v1.PodList{Items: pods.Items[1:2]} 883 thirdPodList := &v1.PodList{Items: pods.Items[2:]} 884 // First pod: Running, but not Ready 885 // by setting the Ready condition to false with LastTransitionTime to be now 886 setPodsReadyCondition(t, c, firstPodList, v1.ConditionFalse, time.Now()) 887 // Second pod: Running and Ready, but not Available 888 // by setting LastTransitionTime to now 889 setPodsReadyCondition(t, c, secondPodList, v1.ConditionTrue, time.Now()) 890 // Third pod: Running, Ready, and Available 891 // by setting LastTransitionTime to more than 3600 seconds ago 892 setPodsReadyCondition(t, c, thirdPodList, v1.ConditionTrue, time.Now().Add(-120*time.Minute)) 893 894 rsClient := c.AppsV1().ReplicaSets(ns.Name) 895 if err := wait.PollImmediate(interval, timeout, func() (bool, error) { 896 newRS, err := rsClient.Get(tCtx, rs.Name, metav1.GetOptions{}) 897 if err != nil { 898 return false, err 899 } 900 // Verify 3 pods exist, 2 pods are Ready, and 1 pod is Available 901 return newRS.Status.Replicas == 3 && newRS.Status.ReadyReplicas == 2 && newRS.Status.AvailableReplicas == 1, nil 902 }); err != nil { 903 t.Fatalf("Failed to verify number of Replicas, ReadyReplicas and AvailableReplicas of rs %s to be as expected: %v", rs.Name, err) 904 } 905 } 906 907 func TestRSScaleSubresource(t *testing.T) { 908 _, closeFn, rm, informers, c := rmSetup(t) 909 defer closeFn() 910 ns := framework.CreateNamespaceOrDie(c, "test-rs-scale-subresource", t) 911 defer framework.DeleteNamespaceOrDie(c, ns, t) 912 stopControllers := runControllerAndInformers(t, rm, informers, 0) 913 defer stopControllers() 914 915 rs := newRS("rs", ns.Name, 1) 916 rss, _ := createRSsPods(t, c, []*apps.ReplicaSet{rs}, []*v1.Pod{}) 917 rs = rss[0] 918 waitRSStable(t, c, rs) 919 920 // Use scale subresource to scale up .Spec.Replicas to 3 921 testScalingUsingScaleSubresource(t, c, rs, 3) 922 // Use the scale subresource to scale down .Spec.Replicas to 0 923 testScalingUsingScaleSubresource(t, c, rs, 0) 924 } 925 926 func TestExtraPodsAdoptionAndDeletion(t *testing.T) { 927 _, closeFn, rm, informers, c := rmSetup(t) 928 defer closeFn() 929 ns := framework.CreateNamespaceOrDie(c, "test-extra-pods-adoption-and-deletion", t) 930 defer framework.DeleteNamespaceOrDie(c, ns, t) 931 932 rs := newRS("rs", ns.Name, 2) 933 // Create 3 pods, RS should adopt only 2 of them 934 podList := []*v1.Pod{} 935 for i := 0; i < 3; i++ { 936 pod := newMatchingPod(fmt.Sprintf("pod-%d", i+1), ns.Name) 937 pod.Labels = labelMap() 938 podList = append(podList, pod) 939 } 940 rss, _ := createRSsPods(t, c, []*apps.ReplicaSet{rs}, podList) 941 rs = rss[0] 942 stopControllers := runControllerAndInformers(t, rm, informers, 3) 943 defer stopControllers() 944 waitRSStable(t, c, rs) 945 946 // Verify the extra pod is deleted eventually by determining whether number of 947 // all pods within namespace matches .spec.replicas of the RS (2 in this case) 948 podClient := c.CoreV1().Pods(ns.Name) 949 if err := wait.PollImmediate(interval, timeout, func() (bool, error) { 950 // All pods have labelMap as their labels 951 pods := getPods(t, podClient, labelMap()) 952 return int32(len(pods.Items)) == *rs.Spec.Replicas, nil 953 }); err != nil { 954 t.Fatalf("Failed to verify number of all pods within current namespace matches .spec.replicas of rs %s: %v", rs.Name, err) 955 } 956 } 957 958 func TestFullyLabeledReplicas(t *testing.T) { 959 tCtx, closeFn, rm, informers, c := rmSetup(t) 960 defer closeFn() 961 ns := framework.CreateNamespaceOrDie(c, "test-fully-labeled-replicas", t) 962 defer framework.DeleteNamespaceOrDie(c, ns, t) 963 stopControllers := runControllerAndInformers(t, rm, informers, 0) 964 defer stopControllers() 965 966 extraLabelMap := map[string]string{"foo": "bar", "extraKey": "extraValue"} 967 rs := newRS("rs", ns.Name, 2) 968 rss, _ := createRSsPods(t, c, []*apps.ReplicaSet{rs}, []*v1.Pod{}) 969 rs = rss[0] 970 waitRSStable(t, c, rs) 971 972 // Change RS's template labels to have extra labels, but not its selector 973 rsClient := c.AppsV1().ReplicaSets(ns.Name) 974 updateRS(t, rsClient, rs.Name, func(rs *apps.ReplicaSet) { 975 rs.Spec.Template.Labels = extraLabelMap 976 }) 977 978 // Set one of the pods to have extra labels 979 podClient := c.CoreV1().Pods(ns.Name) 980 pods := getPods(t, podClient, labelMap()) 981 if len(pods.Items) != 2 { 982 t.Fatalf("len(pods) = %d, want 2", len(pods.Items)) 983 } 984 fullyLabeledPod := &pods.Items[0] 985 updatePod(t, podClient, fullyLabeledPod.Name, func(pod *v1.Pod) { 986 pod.Labels = extraLabelMap 987 }) 988 989 // Verify only one pod is fully labeled 990 if err := wait.PollImmediate(interval, timeout, func() (bool, error) { 991 newRS, err := rsClient.Get(tCtx, rs.Name, metav1.GetOptions{}) 992 if err != nil { 993 return false, err 994 } 995 return (newRS.Status.Replicas == 2 && newRS.Status.FullyLabeledReplicas == 1), nil 996 }); err != nil { 997 t.Fatalf("Failed to verify only one pod is fully labeled: %v", err) 998 } 999 } 1000 1001 func TestReplicaSetsAppsV1DefaultGCPolicy(t *testing.T) { 1002 tCtx, closeFn, rm, informers, c := rmSetup(t) 1003 defer closeFn() 1004 ns := framework.CreateNamespaceOrDie(c, "test-default-gc-v1", t) 1005 defer framework.DeleteNamespaceOrDie(c, ns, t) 1006 stopControllers := runControllerAndInformers(t, rm, informers, 0) 1007 defer stopControllers() 1008 1009 rs := newRS("rs", ns.Name, 2) 1010 fakeFinalizer := "kube.io/dummy-finalizer" 1011 rs.Finalizers = []string{fakeFinalizer} 1012 rss, _ := createRSsPods(t, c, []*apps.ReplicaSet{rs}, []*v1.Pod{}) 1013 rs = rss[0] 1014 waitRSStable(t, c, rs) 1015 1016 // Verify RS creates 2 pods 1017 podClient := c.CoreV1().Pods(ns.Name) 1018 pods := getPods(t, podClient, labelMap()) 1019 if len(pods.Items) != 2 { 1020 t.Fatalf("len(pods) = %d, want 2", len(pods.Items)) 1021 } 1022 1023 rsClient := c.AppsV1().ReplicaSets(ns.Name) 1024 err := rsClient.Delete(tCtx, rs.Name, metav1.DeleteOptions{}) 1025 if err != nil { 1026 t.Fatalf("Failed to delete rs: %v", err) 1027 } 1028 1029 // Verify no new finalizer has been added 1030 if err := wait.PollImmediate(interval, timeout, func() (bool, error) { 1031 newRS, err := rsClient.Get(tCtx, rs.Name, metav1.GetOptions{}) 1032 if err != nil { 1033 return false, err 1034 } 1035 if newRS.DeletionTimestamp == nil { 1036 return false, nil 1037 } 1038 if got, want := newRS.Finalizers, []string{fakeFinalizer}; !reflect.DeepEqual(got, want) { 1039 return false, fmt.Errorf("got finalizers: %+v; want: %+v", got, want) 1040 } 1041 return true, nil 1042 }); err != nil { 1043 t.Fatalf("Failed to verify the finalizer: %v", err) 1044 } 1045 1046 updateRS(t, c.AppsV1().ReplicaSets(ns.Name), rs.Name, func(rs *apps.ReplicaSet) { 1047 var finalizers []string 1048 // remove fakeFinalizer 1049 for _, finalizer := range rs.Finalizers { 1050 if finalizer != fakeFinalizer { 1051 finalizers = append(finalizers, finalizer) 1052 } 1053 } 1054 rs.Finalizers = finalizers 1055 }) 1056 1057 _ = rsClient.Delete(tCtx, rs.Name, metav1.DeleteOptions{}) 1058 }