k8s.io/kubernetes@v1.29.3/test/integration/statefulset/statefulset_test.go (about) 1 /* 2 Copyright 2018 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 statefulset 18 19 import ( 20 "context" 21 "fmt" 22 "testing" 23 "time" 24 25 "github.com/google/go-cmp/cmp" 26 "github.com/google/go-cmp/cmp/cmpopts" 27 appsv1 "k8s.io/api/apps/v1" 28 v1 "k8s.io/api/core/v1" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 31 "k8s.io/apimachinery/pkg/types" 32 "k8s.io/apimachinery/pkg/util/json" 33 "k8s.io/apimachinery/pkg/util/wait" 34 utilfeature "k8s.io/apiserver/pkg/util/feature" 35 "k8s.io/client-go/dynamic" 36 "k8s.io/client-go/informers" 37 clientset "k8s.io/client-go/kubernetes" 38 restclient "k8s.io/client-go/rest" 39 featuregatetesting "k8s.io/component-base/featuregate/testing" 40 "k8s.io/klog/v2/ktesting" 41 apiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing" 42 podutil "k8s.io/kubernetes/pkg/api/v1/pod" 43 "k8s.io/kubernetes/pkg/controller/statefulset" 44 "k8s.io/kubernetes/pkg/controlplane" 45 "k8s.io/kubernetes/pkg/features" 46 "k8s.io/kubernetes/test/integration/framework" 47 "k8s.io/utils/ptr" 48 ) 49 50 const ( 51 interval = 100 * time.Millisecond 52 timeout = 60 * time.Second 53 ) 54 55 // TestVolumeTemplateNoopUpdate ensures embedded StatefulSet objects with embedded PersistentVolumes can be updated 56 func TestVolumeTemplateNoopUpdate(t *testing.T) { 57 // Start the server with default storage setup 58 server := apiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd()) 59 defer server.TearDownFn() 60 61 c, err := dynamic.NewForConfig(server.ClientConfig) 62 if err != nil { 63 t.Fatal(err) 64 } 65 66 // Use an unstructured client to ensure we send exactly the bytes we expect for the embedded PVC template 67 sts := &unstructured.Unstructured{} 68 err = json.Unmarshal([]byte(`{ 69 "apiVersion": "apps/v1", 70 "kind": "StatefulSet", 71 "metadata": {"name": "web"}, 72 "spec": { 73 "selector": {"matchLabels": {"app": "nginx"}}, 74 "serviceName": "nginx", 75 "replicas": 3, 76 "template": { 77 "metadata": {"labels": {"app": "nginx"}}, 78 "spec": { 79 "terminationGracePeriodSeconds": 10, 80 "containers": [{ 81 "name": "nginx", 82 "image": "registry.k8s.io/nginx-slim:0.8", 83 "ports": [{"containerPort": 80,"name": "web"}], 84 "volumeMounts": [{"name": "www","mountPath": "/usr/share/nginx/html"}] 85 }] 86 } 87 }, 88 "volumeClaimTemplates": [{ 89 "apiVersion": "v1", 90 "kind": "PersistentVolumeClaim", 91 "metadata": {"name": "www"}, 92 "spec": { 93 "accessModes": ["ReadWriteOnce"], 94 "storageClassName": "my-storage-class", 95 "resources": {"requests": {"storage": "1Gi"}} 96 } 97 } 98 ] 99 } 100 }`), &sts.Object) 101 if err != nil { 102 t.Fatal(err) 103 } 104 105 stsClient := c.Resource(appsv1.SchemeGroupVersion.WithResource("statefulsets")).Namespace("default") 106 107 // Create the statefulset 108 persistedSTS, err := stsClient.Create(context.TODO(), sts, metav1.CreateOptions{}) 109 if err != nil { 110 t.Fatal(err) 111 } 112 113 // Update with the original spec (all the same defaulting should apply, should be a no-op and pass validation 114 originalSpec, ok, err := unstructured.NestedFieldCopy(sts.Object, "spec") 115 if err != nil || !ok { 116 t.Fatal(err, ok) 117 } 118 err = unstructured.SetNestedField(persistedSTS.Object, originalSpec, "spec") 119 if err != nil { 120 t.Fatal(err) 121 } 122 _, err = stsClient.Update(context.TODO(), persistedSTS, metav1.UpdateOptions{}) 123 if err != nil { 124 t.Fatal(err) 125 } 126 } 127 128 func TestSpecReplicasChange(t *testing.T) { 129 _, ctx := ktesting.NewTestContext(t) 130 closeFn, rm, informers, c := scSetup(ctx, t) 131 defer closeFn() 132 ns := framework.CreateNamespaceOrDie(c, "test-spec-replicas-change", t) 133 defer framework.DeleteNamespaceOrDie(c, ns, t) 134 cancel := runControllerAndInformers(rm, informers) 135 defer cancel() 136 137 createHeadlessService(t, c, newHeadlessService(ns.Name)) 138 sts := newSTS("sts", ns.Name, 2) 139 stss, _ := createSTSsPods(t, c, []*appsv1.StatefulSet{sts}, []*v1.Pod{}) 140 sts = stss[0] 141 waitSTSStable(t, c, sts) 142 143 // Update .Spec.Replicas and verify .Status.Replicas is changed accordingly 144 scaleSTS(t, c, sts, 3) 145 scaleSTS(t, c, sts, 0) 146 scaleSTS(t, c, sts, 2) 147 148 // Add a template annotation change to test STS's status does update 149 // without .Spec.Replicas change 150 stsClient := c.AppsV1().StatefulSets(ns.Name) 151 var oldGeneration int64 152 newSTS := updateSTS(t, stsClient, sts.Name, func(sts *appsv1.StatefulSet) { 153 oldGeneration = sts.Generation 154 sts.Spec.Template.Annotations = map[string]string{"test": "annotation"} 155 }) 156 savedGeneration := newSTS.Generation 157 if savedGeneration == oldGeneration { 158 t.Fatalf("failed to verify .Generation has incremented for sts %s", sts.Name) 159 } 160 161 if err := wait.PollImmediate(pollInterval, pollTimeout, func() (bool, error) { 162 newSTS, err := stsClient.Get(context.TODO(), sts.Name, metav1.GetOptions{}) 163 if err != nil { 164 return false, err 165 } 166 return newSTS.Status.ObservedGeneration >= savedGeneration, nil 167 }); err != nil { 168 t.Fatalf("failed to verify .Status.ObservedGeneration has incremented for sts %s: %v", sts.Name, err) 169 } 170 } 171 172 func TestDeletingAndTerminatingPods(t *testing.T) { 173 _, ctx := ktesting.NewTestContext(t) 174 closeFn, rm, informers, c := scSetup(ctx, t) 175 defer closeFn() 176 ns := framework.CreateNamespaceOrDie(c, "test-deleting-and-failed-pods", t) 177 defer framework.DeleteNamespaceOrDie(c, ns, t) 178 cancel := runControllerAndInformers(rm, informers) 179 defer cancel() 180 181 podCount := 3 182 183 labelMap := labelMap() 184 sts := newSTS("sts", ns.Name, podCount) 185 stss, _ := createSTSsPods(t, c, []*appsv1.StatefulSet{sts}, []*v1.Pod{}) 186 sts = stss[0] 187 waitSTSStable(t, c, sts) 188 189 // Verify STS creates 3 pods 190 podClient := c.CoreV1().Pods(ns.Name) 191 pods := getPods(t, podClient, labelMap) 192 if len(pods.Items) != podCount { 193 t.Fatalf("len(pods) = %d, want %d", len(pods.Items), podCount) 194 } 195 196 // Set first pod as deleting pod 197 // Set finalizers for the pod to simulate pending deletion status 198 deletingPod := &pods.Items[0] 199 updatePod(t, podClient, deletingPod.Name, func(pod *v1.Pod) { 200 pod.Finalizers = []string{"fake.example.com/blockDeletion"} 201 }) 202 if err := c.CoreV1().Pods(ns.Name).Delete(context.TODO(), deletingPod.Name, metav1.DeleteOptions{}); err != nil { 203 t.Fatalf("error deleting pod %s: %v", deletingPod.Name, err) 204 } 205 206 // Set second pod as failed pod 207 failedPod := &pods.Items[1] 208 updatePodStatus(t, podClient, failedPod.Name, func(pod *v1.Pod) { 209 pod.Status.Phase = v1.PodFailed 210 }) 211 212 // Set third pod as succeeded pod 213 succeededPod := &pods.Items[2] 214 updatePodStatus(t, podClient, succeededPod.Name, func(pod *v1.Pod) { 215 pod.Status.Phase = v1.PodSucceeded 216 }) 217 218 exists := func(pods []v1.Pod, uid types.UID) bool { 219 for _, pod := range pods { 220 if pod.UID == uid { 221 return true 222 } 223 } 224 return false 225 } 226 227 if err := wait.PollImmediate(pollInterval, pollTimeout, func() (bool, error) { 228 // Verify only 3 pods exist: deleting pod and new pod replacing failed pod 229 pods = getPods(t, podClient, labelMap) 230 if len(pods.Items) != podCount { 231 return false, nil 232 } 233 234 // Verify deleting pod still exists 235 // Immediately return false with an error if it does not exist 236 if !exists(pods.Items, deletingPod.UID) { 237 return false, fmt.Errorf("expected deleting pod %s still exists, but it is not found", deletingPod.Name) 238 } 239 // Verify failed pod does not exist anymore 240 if exists(pods.Items, failedPod.UID) { 241 return false, nil 242 } 243 // Verify succeeded pod does not exist anymore 244 if exists(pods.Items, succeededPod.UID) { 245 return false, nil 246 } 247 // Verify all pods have non-terminated status 248 for _, pod := range pods.Items { 249 if pod.Status.Phase == v1.PodFailed || pod.Status.Phase == v1.PodSucceeded { 250 return false, nil 251 } 252 } 253 return true, nil 254 }); err != nil { 255 t.Fatalf("failed to verify failed pod %s has been replaced with a new non-failed pod, and deleting pod %s survives: %v", failedPod.Name, deletingPod.Name, err) 256 } 257 258 // Remove finalizers of deleting pod to simulate successful deletion 259 updatePod(t, podClient, deletingPod.Name, func(pod *v1.Pod) { 260 pod.Finalizers = []string{} 261 }) 262 263 if err := wait.PollImmediate(pollInterval, pollTimeout, func() (bool, error) { 264 // Verify only 2 pods exist: new non-deleting pod replacing deleting pod and the non-failed pod 265 pods = getPods(t, podClient, labelMap) 266 if len(pods.Items) != podCount { 267 return false, nil 268 } 269 // Verify deleting pod does not exist anymore 270 return !exists(pods.Items, deletingPod.UID), nil 271 }); err != nil { 272 t.Fatalf("failed to verify deleting pod %s has been replaced with a new non-deleting pod: %v", deletingPod.Name, err) 273 } 274 } 275 276 func TestStatefulSetAvailable(t *testing.T) { 277 tests := []struct { 278 name string 279 totalReplicas int32 280 readyReplicas int32 281 activeReplicas int32 282 }{ 283 { 284 name: "only certain replicas would become active", 285 totalReplicas: 4, 286 readyReplicas: 3, 287 activeReplicas: 2, 288 }, 289 } 290 for _, test := range tests { 291 t.Run(test.name, func(t *testing.T) { 292 _, ctx := ktesting.NewTestContext(t) 293 closeFn, rm, informers, c := scSetup(ctx, t) 294 defer closeFn() 295 ns := framework.CreateNamespaceOrDie(c, "test-available-pods", t) 296 defer framework.DeleteNamespaceOrDie(c, ns, t) 297 cancel := runControllerAndInformers(rm, informers) 298 defer cancel() 299 300 labelMap := labelMap() 301 sts := newSTS("sts", ns.Name, 4) 302 sts.Spec.MinReadySeconds = int32(3600) 303 stss, _ := createSTSsPods(t, c, []*appsv1.StatefulSet{sts}, []*v1.Pod{}) 304 sts = stss[0] 305 waitSTSStable(t, c, sts) 306 307 // Verify STS creates 4 pods 308 podClient := c.CoreV1().Pods(ns.Name) 309 pods := getPods(t, podClient, labelMap) 310 if len(pods.Items) != 4 { 311 t.Fatalf("len(pods) = %d, want 4", len(pods.Items)) 312 } 313 314 // Separate 3 pods into their own list 315 firstPodList := &v1.PodList{Items: pods.Items[:1]} 316 secondPodList := &v1.PodList{Items: pods.Items[1:2]} 317 thirdPodList := &v1.PodList{Items: pods.Items[2:]} 318 // First pod: Running, but not Ready 319 // by setting the Ready condition to false with LastTransitionTime to be now 320 setPodsReadyCondition(t, c, firstPodList, v1.ConditionFalse, time.Now()) 321 // Second pod: Running and Ready, but not Available 322 // by setting LastTransitionTime to now 323 setPodsReadyCondition(t, c, secondPodList, v1.ConditionTrue, time.Now()) 324 // Third pod: Running, Ready, and Available 325 // by setting LastTransitionTime to more than 3600 seconds ago 326 setPodsReadyCondition(t, c, thirdPodList, v1.ConditionTrue, time.Now().Add(-120*time.Minute)) 327 328 stsClient := c.AppsV1().StatefulSets(ns.Name) 329 if err := wait.PollImmediate(interval, timeout, func() (bool, error) { 330 newSts, err := stsClient.Get(context.TODO(), sts.Name, metav1.GetOptions{}) 331 if err != nil { 332 return false, err 333 } 334 // Verify 4 pods exist, 3 pods are Ready, and 2 pods are Available 335 return newSts.Status.Replicas == test.totalReplicas && newSts.Status.ReadyReplicas == test.readyReplicas && newSts.Status.AvailableReplicas == test.activeReplicas, nil 336 }); err != nil { 337 t.Fatalf("Failed to verify number of Replicas, ReadyReplicas and AvailableReplicas of rs %s to be as expected: %v", sts.Name, err) 338 } 339 }) 340 } 341 } 342 343 func setPodsReadyCondition(t *testing.T, clientSet clientset.Interface, pods *v1.PodList, conditionStatus v1.ConditionStatus, lastTransitionTime time.Time) { 344 replicas := int32(len(pods.Items)) 345 var readyPods int32 346 err := wait.PollImmediate(interval, timeout, func() (bool, error) { 347 readyPods = 0 348 for i := range pods.Items { 349 pod := &pods.Items[i] 350 if podutil.IsPodReady(pod) { 351 readyPods++ 352 continue 353 } 354 pod.Status.Phase = v1.PodRunning 355 _, condition := podutil.GetPodCondition(&pod.Status, v1.PodReady) 356 if condition != nil { 357 condition.Status = conditionStatus 358 condition.LastTransitionTime = metav1.Time{Time: lastTransitionTime} 359 } else { 360 condition = &v1.PodCondition{ 361 Type: v1.PodReady, 362 Status: conditionStatus, 363 LastTransitionTime: metav1.Time{Time: lastTransitionTime}, 364 } 365 pod.Status.Conditions = append(pod.Status.Conditions, *condition) 366 } 367 _, err := clientSet.CoreV1().Pods(pod.Namespace).UpdateStatus(context.TODO(), pod, metav1.UpdateOptions{}) 368 if err != nil { 369 // When status fails to be updated, we continue to next pod 370 continue 371 } 372 readyPods++ 373 } 374 return readyPods >= replicas, nil 375 }) 376 if err != nil { 377 t.Fatalf("failed to mark all StatefulSet pods to ready: %v", err) 378 } 379 } 380 381 // add for issue: https://github.com/kubernetes/kubernetes/issues/108837 382 func TestStatefulSetStatusWithPodFail(t *testing.T) { 383 _, ctx := ktesting.NewTestContext(t) 384 ctx, cancel := context.WithCancel(ctx) 385 defer cancel() 386 387 limitedPodNumber := 2 388 c, config, closeFn := framework.StartTestServer(ctx, t, framework.TestServerSetup{ 389 ModifyServerConfig: func(config *controlplane.Config) { 390 config.GenericConfig.AdmissionControl = &fakePodFailAdmission{ 391 limitedPodNumber: limitedPodNumber, 392 } 393 }, 394 }) 395 defer closeFn() 396 397 resyncPeriod := 12 * time.Hour 398 informers := informers.NewSharedInformerFactory(clientset.NewForConfigOrDie(restclient.AddUserAgent(config, "statefulset-informers")), resyncPeriod) 399 ssc := statefulset.NewStatefulSetController( 400 ctx, 401 informers.Core().V1().Pods(), 402 informers.Apps().V1().StatefulSets(), 403 informers.Core().V1().PersistentVolumeClaims(), 404 informers.Apps().V1().ControllerRevisions(), 405 clientset.NewForConfigOrDie(restclient.AddUserAgent(config, "statefulset-controller")), 406 ) 407 408 ns := framework.CreateNamespaceOrDie(c, "test-pod-fail", t) 409 defer framework.DeleteNamespaceOrDie(c, ns, t) 410 411 informers.Start(ctx.Done()) 412 go ssc.Run(ctx, 5) 413 414 sts := newSTS("sts", ns.Name, 4) 415 _, err := c.AppsV1().StatefulSets(sts.Namespace).Create(ctx, sts, metav1.CreateOptions{}) 416 if err != nil { 417 t.Fatalf("Could not create statefulSet %s: %v", sts.Name, err) 418 } 419 420 wantReplicas := limitedPodNumber 421 var gotReplicas int32 422 if err := wait.PollImmediate(pollInterval, pollTimeout, func() (bool, error) { 423 newSTS, err := c.AppsV1().StatefulSets(sts.Namespace).Get(ctx, sts.Name, metav1.GetOptions{}) 424 if err != nil { 425 return false, err 426 } 427 gotReplicas = newSTS.Status.Replicas 428 return gotReplicas == int32(wantReplicas), nil 429 }); err != nil { 430 t.Fatalf("StatefulSet %s status has %d replicas, want replicas %d: %v", sts.Name, gotReplicas, wantReplicas, err) 431 } 432 } 433 434 func TestAutodeleteOwnerRefs(t *testing.T) { 435 tests := []struct { 436 name string 437 policy appsv1.StatefulSetPersistentVolumeClaimRetentionPolicy 438 expectPodOwnerRef bool 439 expectSetOwnerRef bool 440 }{ 441 { 442 name: "always retain", 443 policy: appsv1.StatefulSetPersistentVolumeClaimRetentionPolicy{ 444 WhenDeleted: appsv1.RetainPersistentVolumeClaimRetentionPolicyType, 445 WhenScaled: appsv1.RetainPersistentVolumeClaimRetentionPolicyType, 446 }, 447 expectPodOwnerRef: false, 448 expectSetOwnerRef: false, 449 }, 450 { 451 name: "delete on scaledown only", 452 policy: appsv1.StatefulSetPersistentVolumeClaimRetentionPolicy{ 453 WhenDeleted: appsv1.RetainPersistentVolumeClaimRetentionPolicyType, 454 WhenScaled: appsv1.DeletePersistentVolumeClaimRetentionPolicyType, 455 }, 456 expectPodOwnerRef: true, 457 expectSetOwnerRef: false, 458 }, 459 { 460 name: "delete with set only", 461 policy: appsv1.StatefulSetPersistentVolumeClaimRetentionPolicy{ 462 WhenDeleted: appsv1.DeletePersistentVolumeClaimRetentionPolicyType, 463 WhenScaled: appsv1.RetainPersistentVolumeClaimRetentionPolicyType, 464 }, 465 expectPodOwnerRef: false, 466 expectSetOwnerRef: true, 467 }, 468 { 469 name: "always delete", 470 policy: appsv1.StatefulSetPersistentVolumeClaimRetentionPolicy{ 471 WhenDeleted: appsv1.DeletePersistentVolumeClaimRetentionPolicyType, 472 WhenScaled: appsv1.DeletePersistentVolumeClaimRetentionPolicyType, 473 }, 474 expectPodOwnerRef: true, 475 expectSetOwnerRef: true, 476 }, 477 } 478 for _, test := range tests { 479 t.Run(test.name, func(t *testing.T) { 480 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StatefulSetAutoDeletePVC, true)() 481 _, ctx := ktesting.NewTestContext(t) 482 closeFn, rm, informers, c := scSetup(ctx, t) 483 defer closeFn() 484 ns := framework.CreateNamespaceOrDie(c, "test-autodelete-ownerrefs", t) 485 defer framework.DeleteNamespaceOrDie(c, ns, t) 486 cancel := runControllerAndInformers(rm, informers) 487 defer cancel() 488 489 sts := newSTS("sts", ns.Name, 3) 490 sts.Spec.PersistentVolumeClaimRetentionPolicy = &test.policy 491 stss, _ := createSTSsPods(t, c, []*appsv1.StatefulSet{sts}, []*v1.Pod{}) 492 sts = stss[0] 493 waitSTSStable(t, c, sts) 494 495 // Verify StatefulSet ownerref has been added as appropriate. 496 pvcClient := c.CoreV1().PersistentVolumeClaims(ns.Name) 497 pvcs := getStatefulSetPVCs(t, pvcClient, sts) 498 for _, pvc := range pvcs { 499 verifyOwnerRef(t, pvc, "StatefulSet", test.expectSetOwnerRef) 500 verifyOwnerRef(t, pvc, "Pod", false) 501 } 502 503 // Scale down to 1 pod and verify Pod ownerrefs as appropriate. 504 one := int32(1) 505 sts.Spec.Replicas = &one 506 waitSTSStable(t, c, sts) 507 508 pvcs = getStatefulSetPVCs(t, pvcClient, sts) 509 for i, pvc := range pvcs { 510 verifyOwnerRef(t, pvc, "StatefulSet", test.expectSetOwnerRef) 511 if i == 0 { 512 verifyOwnerRef(t, pvc, "Pod", false) 513 } else { 514 verifyOwnerRef(t, pvc, "Pod", test.expectPodOwnerRef) 515 } 516 } 517 }) 518 } 519 } 520 521 func TestDeletingPodForRollingUpdatePartition(t *testing.T) { 522 _, ctx := ktesting.NewTestContext(t) 523 closeFn, rm, informers, c := scSetup(ctx, t) 524 defer closeFn() 525 ns := framework.CreateNamespaceOrDie(c, "test-deleting-pod-for-rolling-update-partition", t) 526 defer framework.DeleteNamespaceOrDie(c, ns, t) 527 cancel := runControllerAndInformers(rm, informers) 528 defer cancel() 529 530 labelMap := labelMap() 531 sts := newSTS("sts", ns.Name, 2) 532 sts.Spec.UpdateStrategy = appsv1.StatefulSetUpdateStrategy{ 533 Type: appsv1.RollingUpdateStatefulSetStrategyType, 534 RollingUpdate: func() *appsv1.RollingUpdateStatefulSetStrategy { 535 return &appsv1.RollingUpdateStatefulSetStrategy{ 536 Partition: ptr.To[int32](1), 537 } 538 }(), 539 } 540 stss, _ := createSTSsPods(t, c, []*appsv1.StatefulSet{sts}, []*v1.Pod{}) 541 sts = stss[0] 542 waitSTSStable(t, c, sts) 543 544 // Verify STS creates 2 pods 545 podClient := c.CoreV1().Pods(ns.Name) 546 pods := getPods(t, podClient, labelMap) 547 if len(pods.Items) != 2 { 548 t.Fatalf("len(pods) = %d, want 2", len(pods.Items)) 549 } 550 // Setting all pods in Running, Ready, and Available 551 setPodsReadyCondition(t, c, &v1.PodList{Items: pods.Items}, v1.ConditionTrue, time.Now()) 552 553 // 1. Roll out a new image. 554 oldImage := sts.Spec.Template.Spec.Containers[0].Image 555 newImage := "new-image" 556 if oldImage == newImage { 557 t.Fatalf("bad test setup, statefulSet %s roll out with the same image", sts.Name) 558 } 559 // Set finalizers for the pod-0 to trigger pod recreation failure while the status UpdateRevision is bumped 560 pod0 := &pods.Items[0] 561 updatePod(t, podClient, pod0.Name, func(pod *v1.Pod) { 562 pod.Finalizers = []string{"fake.example.com/blockDeletion"} 563 }) 564 565 stsClient := c.AppsV1().StatefulSets(ns.Name) 566 _ = updateSTS(t, stsClient, sts.Name, func(sts *appsv1.StatefulSet) { 567 sts.Spec.Template.Spec.Containers[0].Image = newImage 568 }) 569 570 // Await for the pod-1 to be recreated, while pod-0 remains running 571 if err := wait.PollUntilContextTimeout(ctx, pollInterval, pollTimeout, false, func(ctx context.Context) (bool, error) { 572 ss, err := stsClient.Get(ctx, sts.Name, metav1.GetOptions{}) 573 if err != nil { 574 return false, err 575 } 576 pods := getPods(t, podClient, labelMap) 577 recreatedPods := v1.PodList{} 578 for _, pod := range pods.Items { 579 if pod.Status.Phase == v1.PodPending { 580 recreatedPods.Items = append(recreatedPods.Items, pod) 581 } 582 } 583 setPodsReadyCondition(t, c, &v1.PodList{Items: recreatedPods.Items}, v1.ConditionTrue, time.Now()) 584 return ss.Status.UpdatedReplicas == *ss.Spec.Replicas-*sts.Spec.UpdateStrategy.RollingUpdate.Partition && ss.Status.Replicas == *ss.Spec.Replicas && ss.Status.ReadyReplicas == *ss.Spec.Replicas, nil 585 }); err != nil { 586 t.Fatalf("failed to await for pod-1 to be recreated by sts %s: %v", sts.Name, err) 587 } 588 589 // Mark pod-0 as terminal and not ready 590 updatePodStatus(t, podClient, pod0.Name, func(pod *v1.Pod) { 591 pod.Status.Phase = v1.PodFailed 592 }) 593 594 // Make sure pod-0 gets deletion timestamp so that it is recreated 595 if err := c.CoreV1().Pods(ns.Name).Delete(context.TODO(), pod0.Name, metav1.DeleteOptions{}); err != nil { 596 t.Fatalf("error deleting pod %s: %v", pod0.Name, err) 597 } 598 599 // Await for pod-0 to be not ready 600 if err := wait.PollUntilContextTimeout(ctx, pollInterval, pollTimeout, false, func(ctx context.Context) (bool, error) { 601 ss, err := stsClient.Get(ctx, sts.Name, metav1.GetOptions{}) 602 if err != nil { 603 return false, err 604 } 605 return ss.Status.ReadyReplicas == *ss.Spec.Replicas-1, nil 606 }); err != nil { 607 t.Fatalf("failed to await for pod-0 to be not counted as ready in status of sts %s: %v", sts.Name, err) 608 } 609 610 // Remove the finalizer to allow recreation 611 updatePod(t, podClient, pod0.Name, func(pod *v1.Pod) { 612 pod.Finalizers = []string{} 613 }) 614 615 // Await for pod-0 to be recreated and make it running 616 if err := wait.PollUntilContextTimeout(ctx, pollInterval, pollTimeout, false, func(ctx context.Context) (bool, error) { 617 pods := getPods(t, podClient, labelMap) 618 recreatedPods := v1.PodList{} 619 for _, pod := range pods.Items { 620 if pod.Status.Phase == v1.PodPending { 621 recreatedPods.Items = append(recreatedPods.Items, pod) 622 } 623 } 624 setPodsReadyCondition(t, c, &v1.PodList{Items: recreatedPods.Items}, v1.ConditionTrue, time.Now().Add(-120*time.Minute)) 625 return len(recreatedPods.Items) > 0, nil 626 }); err != nil { 627 t.Fatalf("failed to await for pod-0 to be recreated by sts %s: %v", sts.Name, err) 628 } 629 630 // Await for all stateful set status to record all replicas as ready 631 if err := wait.PollUntilContextTimeout(ctx, pollInterval, pollTimeout, false, func(ctx context.Context) (bool, error) { 632 ss, err := stsClient.Get(ctx, sts.Name, metav1.GetOptions{}) 633 if err != nil { 634 return false, err 635 } 636 return ss.Status.ReadyReplicas == *ss.Spec.Replicas, nil 637 }); err != nil { 638 t.Fatalf("failed to verify .Spec.Template.Spec.Containers[0].Image is updated for sts %s: %v", sts.Name, err) 639 } 640 641 // Verify 3 pods exist 642 pods = getPods(t, podClient, labelMap) 643 if len(pods.Items) != int(*sts.Spec.Replicas) { 644 t.Fatalf("Unexpected number of pods") 645 } 646 647 // Verify pod images 648 for i := range pods.Items { 649 if i < int(*sts.Spec.UpdateStrategy.RollingUpdate.Partition) { 650 if pods.Items[i].Spec.Containers[0].Image != oldImage { 651 t.Fatalf("Pod %s has image %s not equal to old image %s", pods.Items[i].Name, pods.Items[i].Spec.Containers[0].Image, oldImage) 652 } 653 } else { 654 if pods.Items[i].Spec.Containers[0].Image != newImage { 655 t.Fatalf("Pod %s has image %s not equal to new image %s", pods.Items[i].Name, pods.Items[i].Spec.Containers[0].Image, newImage) 656 } 657 } 658 } 659 } 660 661 func TestStatefulSetStartOrdinal(t *testing.T) { 662 tests := []struct { 663 ordinals *appsv1.StatefulSetOrdinals 664 name string 665 namespace string 666 replicas int 667 expectedPodNames []string 668 }{ 669 { 670 name: "default start ordinal, no ordinals set", 671 namespace: "no-ordinals", 672 replicas: 3, 673 expectedPodNames: []string{"sts-0", "sts-1", "sts-2"}, 674 }, 675 { 676 name: "default start ordinal", 677 namespace: "no-start-ordinals", 678 ordinals: &appsv1.StatefulSetOrdinals{}, 679 replicas: 3, 680 expectedPodNames: []string{"sts-0", "sts-1", "sts-2"}, 681 }, 682 { 683 name: "start ordinal 4", 684 namespace: "start-ordinal-4", 685 ordinals: &appsv1.StatefulSetOrdinals{ 686 Start: 4, 687 }, 688 replicas: 4, 689 expectedPodNames: []string{"sts-4", "sts-5", "sts-6", "sts-7"}, 690 }, 691 { 692 name: "start ordinal 5", 693 namespace: "start-ordinal-5", 694 ordinals: &appsv1.StatefulSetOrdinals{ 695 Start: 2, 696 }, 697 replicas: 7, 698 expectedPodNames: []string{"sts-2", "sts-3", "sts-4", "sts-5", "sts-6", "sts-7", "sts-8"}, 699 }, 700 } 701 702 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StatefulSetStartOrdinal, true)() 703 _, ctx := ktesting.NewTestContext(t) 704 closeFn, rm, informers, c := scSetup(ctx, t) 705 defer closeFn() 706 cancel := runControllerAndInformers(rm, informers) 707 defer cancel() 708 709 for _, test := range tests { 710 t.Run(test.name, func(t *testing.T) { 711 ns := framework.CreateNamespaceOrDie(c, test.namespace, t) 712 defer framework.DeleteNamespaceOrDie(c, ns, t) 713 714 // Label map is the map of pod labels used in newSTS() 715 labelMap := labelMap() 716 sts := newSTS("sts", ns.Name, test.replicas) 717 sts.Spec.Ordinals = test.ordinals 718 stss := createSTSs(t, c, []*appsv1.StatefulSet{sts}) 719 sts = stss[0] 720 waitSTSStable(t, c, sts) 721 722 podClient := c.CoreV1().Pods(ns.Name) 723 pods := getPods(t, podClient, labelMap) 724 if len(pods.Items) != test.replicas { 725 t.Errorf("len(pods) = %v, want %v", len(pods.Items), test.replicas) 726 } 727 728 var podNames []string 729 for _, pod := range pods.Items { 730 podNames = append(podNames, pod.Name) 731 } 732 ignoreOrder := cmpopts.SortSlices(func(a, b string) bool { 733 return a < b 734 }) 735 736 // Validate all the expected pods were created. 737 if diff := cmp.Diff(test.expectedPodNames, podNames, ignoreOrder); diff != "" { 738 t.Errorf("Unexpected pod names: (-want +got): %v", diff) 739 } 740 741 // Scale down to 1 pod and verify it matches the first pod. 742 scaleSTS(t, c, sts, 1) 743 waitSTSStable(t, c, sts) 744 745 pods = getPods(t, podClient, labelMap) 746 if len(pods.Items) != 1 { 747 t.Errorf("len(pods) = %v, want %v", len(pods.Items), 1) 748 } 749 if pods.Items[0].Name != test.expectedPodNames[0] { 750 t.Errorf("Unexpected singleton pod name: got = %v, want %v", pods.Items[0].Name, test.expectedPodNames[0]) 751 } 752 }) 753 } 754 }