k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/controller/statefulset/stateful_set_utils_test.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package statefulset 18 19 import ( 20 "fmt" 21 "math/rand" 22 "reflect" 23 "regexp" 24 "sort" 25 "strconv" 26 "testing" 27 "time" 28 29 "k8s.io/apimachinery/pkg/api/resource" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/runtime" 32 "k8s.io/apimachinery/pkg/types" 33 "k8s.io/apimachinery/pkg/util/intstr" 34 "k8s.io/klog/v2" 35 "k8s.io/klog/v2/ktesting" 36 37 apps "k8s.io/api/apps/v1" 38 v1 "k8s.io/api/core/v1" 39 podutil "k8s.io/kubernetes/pkg/api/v1/pod" 40 "k8s.io/kubernetes/pkg/controller/history" 41 "k8s.io/utils/ptr" 42 ) 43 44 // noopRecorder is an EventRecorder that does nothing. record.FakeRecorder has a fixed 45 // buffer size, which causes tests to hang if that buffer's exceeded. 46 type noopRecorder struct{} 47 48 func (r *noopRecorder) Event(object runtime.Object, eventtype, reason, message string) {} 49 func (r *noopRecorder) Eventf(object runtime.Object, eventtype, reason, messageFmt string, args ...interface{}) { 50 } 51 func (r *noopRecorder) AnnotatedEventf(object runtime.Object, annotations map[string]string, eventtype, reason, messageFmt string, args ...interface{}) { 52 } 53 54 // getClaimPodName gets the name of the Pod associated with the Claim, or an empty string if this doesn't look matching. 55 func getClaimPodName(set *apps.StatefulSet, claim *v1.PersistentVolumeClaim) string { 56 podName := "" 57 58 statefulClaimRegex := regexp.MustCompile(fmt.Sprintf(".*-(%s-[0-9]+)$", set.Name)) 59 matches := statefulClaimRegex.FindStringSubmatch(claim.Name) 60 if len(matches) != 2 { 61 return podName 62 } 63 return matches[1] 64 } 65 66 func TestGetParentNameAndOrdinal(t *testing.T) { 67 set := newStatefulSet(3) 68 pod := newStatefulSetPod(set, 1) 69 if parent, ordinal := getParentNameAndOrdinal(pod); parent != set.Name { 70 t.Errorf("Extracted the wrong parent name expected %s found %s", set.Name, parent) 71 } else if ordinal != 1 { 72 t.Errorf("Extracted the wrong ordinal expected %d found %d", 1, ordinal) 73 } 74 pod.Name = "1-bar" 75 if parent, ordinal := getParentNameAndOrdinal(pod); parent != "" { 76 t.Error("Expected empty string for non-member Pod parent") 77 } else if ordinal != -1 { 78 t.Error("Expected -1 for non member Pod ordinal") 79 } 80 } 81 82 func TestGetClaimPodName(t *testing.T) { 83 set := apps.StatefulSet{} 84 set.Name = "my-set" 85 claim := v1.PersistentVolumeClaim{} 86 claim.Name = "volume-my-set-2" 87 if pod := getClaimPodName(&set, &claim); pod != "my-set-2" { 88 t.Errorf("Expected my-set-2 found %s", pod) 89 } 90 claim.Name = "long-volume-my-set-20" 91 if pod := getClaimPodName(&set, &claim); pod != "my-set-20" { 92 t.Errorf("Expected my-set-20 found %s", pod) 93 } 94 claim.Name = "volume-2-my-set" 95 if pod := getClaimPodName(&set, &claim); pod != "" { 96 t.Errorf("Expected empty string found %s", pod) 97 } 98 claim.Name = "volume-pod-2" 99 if pod := getClaimPodName(&set, &claim); pod != "" { 100 t.Errorf("Expected empty string found %s", pod) 101 } 102 } 103 104 func TestIsMemberOf(t *testing.T) { 105 set := newStatefulSet(3) 106 set2 := newStatefulSet(3) 107 set2.Name = "foo2" 108 pod := newStatefulSetPod(set, 1) 109 if !isMemberOf(set, pod) { 110 t.Error("isMemberOf returned false negative") 111 } 112 if isMemberOf(set2, pod) { 113 t.Error("isMemberOf returned false positive") 114 } 115 } 116 117 func TestIdentityMatches(t *testing.T) { 118 set := newStatefulSet(3) 119 pod := newStatefulSetPod(set, 1) 120 if !identityMatches(set, pod) { 121 t.Error("Newly created Pod has a bad identity") 122 } 123 pod.Name = "foo" 124 if identityMatches(set, pod) { 125 t.Error("identity matches for a Pod with the wrong name") 126 } 127 pod = newStatefulSetPod(set, 1) 128 pod.Namespace = "" 129 if identityMatches(set, pod) { 130 t.Error("identity matches for a Pod with the wrong namespace") 131 } 132 pod = newStatefulSetPod(set, 1) 133 delete(pod.Labels, apps.StatefulSetPodNameLabel) 134 if identityMatches(set, pod) { 135 t.Error("identity matches for a Pod with the wrong statefulSetPodNameLabel") 136 } 137 } 138 139 func TestStorageMatches(t *testing.T) { 140 set := newStatefulSet(3) 141 pod := newStatefulSetPod(set, 1) 142 if !storageMatches(set, pod) { 143 t.Error("Newly created Pod has a invalid storage") 144 } 145 pod.Spec.Volumes = nil 146 if storageMatches(set, pod) { 147 t.Error("Pod with invalid Volumes has valid storage") 148 } 149 pod = newStatefulSetPod(set, 1) 150 for i := range pod.Spec.Volumes { 151 pod.Spec.Volumes[i].PersistentVolumeClaim = nil 152 } 153 if storageMatches(set, pod) { 154 t.Error("Pod with invalid Volumes claim valid storage") 155 } 156 pod = newStatefulSetPod(set, 1) 157 for i := range pod.Spec.Volumes { 158 if pod.Spec.Volumes[i].PersistentVolumeClaim != nil { 159 pod.Spec.Volumes[i].PersistentVolumeClaim.ClaimName = "foo" 160 } 161 } 162 if storageMatches(set, pod) { 163 t.Error("Pod with invalid Volumes claim valid storage") 164 } 165 pod = newStatefulSetPod(set, 1) 166 pod.Name = "bar" 167 if storageMatches(set, pod) { 168 t.Error("Pod with invalid ordinal has valid storage") 169 } 170 } 171 172 func TestUpdateIdentity(t *testing.T) { 173 set := newStatefulSet(3) 174 pod := newStatefulSetPod(set, 1) 175 if !identityMatches(set, pod) { 176 t.Error("Newly created Pod has a bad identity") 177 } 178 pod.Namespace = "" 179 if identityMatches(set, pod) { 180 t.Error("identity matches for a Pod with the wrong namespace") 181 } 182 updateIdentity(set, pod) 183 if !identityMatches(set, pod) { 184 t.Error("updateIdentity failed to update the Pods namespace") 185 } 186 delete(pod.Labels, apps.StatefulSetPodNameLabel) 187 updateIdentity(set, pod) 188 if !identityMatches(set, pod) { 189 t.Error("updateIdentity failed to restore the statefulSetPodName label") 190 } 191 } 192 193 func TestUpdateStorage(t *testing.T) { 194 set := newStatefulSet(3) 195 pod := newStatefulSetPod(set, 1) 196 if !storageMatches(set, pod) { 197 t.Error("Newly created Pod has a invalid storage") 198 } 199 pod.Spec.Volumes = nil 200 if storageMatches(set, pod) { 201 t.Error("Pod with invalid Volumes has valid storage") 202 } 203 updateStorage(set, pod) 204 if !storageMatches(set, pod) { 205 t.Error("updateStorage failed to recreate volumes") 206 } 207 pod = newStatefulSetPod(set, 1) 208 for i := range pod.Spec.Volumes { 209 pod.Spec.Volumes[i].PersistentVolumeClaim = nil 210 } 211 if storageMatches(set, pod) { 212 t.Error("Pod with invalid Volumes claim valid storage") 213 } 214 updateStorage(set, pod) 215 if !storageMatches(set, pod) { 216 t.Error("updateStorage failed to recreate volume claims") 217 } 218 pod = newStatefulSetPod(set, 1) 219 for i := range pod.Spec.Volumes { 220 if pod.Spec.Volumes[i].PersistentVolumeClaim != nil { 221 pod.Spec.Volumes[i].PersistentVolumeClaim.ClaimName = "foo" 222 } 223 } 224 if storageMatches(set, pod) { 225 t.Error("Pod with invalid Volumes claim valid storage") 226 } 227 updateStorage(set, pod) 228 if !storageMatches(set, pod) { 229 t.Error("updateStorage failed to recreate volume claim names") 230 } 231 } 232 233 func TestGetPersistentVolumeClaimRetentionPolicy(t *testing.T) { 234 retainPolicy := apps.StatefulSetPersistentVolumeClaimRetentionPolicy{ 235 WhenScaled: apps.RetainPersistentVolumeClaimRetentionPolicyType, 236 WhenDeleted: apps.RetainPersistentVolumeClaimRetentionPolicyType, 237 } 238 scaledownPolicy := apps.StatefulSetPersistentVolumeClaimRetentionPolicy{ 239 WhenScaled: apps.DeletePersistentVolumeClaimRetentionPolicyType, 240 WhenDeleted: apps.RetainPersistentVolumeClaimRetentionPolicyType, 241 } 242 243 set := apps.StatefulSet{} 244 set.Spec.PersistentVolumeClaimRetentionPolicy = &retainPolicy 245 got := getPersistentVolumeClaimRetentionPolicy(&set) 246 if got.WhenScaled != apps.RetainPersistentVolumeClaimRetentionPolicyType || got.WhenDeleted != apps.RetainPersistentVolumeClaimRetentionPolicyType { 247 t.Errorf("Expected retain policy") 248 } 249 set.Spec.PersistentVolumeClaimRetentionPolicy = &scaledownPolicy 250 got = getPersistentVolumeClaimRetentionPolicy(&set) 251 if got.WhenScaled != apps.DeletePersistentVolumeClaimRetentionPolicyType || got.WhenDeleted != apps.RetainPersistentVolumeClaimRetentionPolicyType { 252 t.Errorf("Expected scaledown policy") 253 } 254 } 255 256 func TestClaimOwnerMatchesSetAndPod(t *testing.T) { 257 testCases := []struct { 258 name string 259 scaleDownPolicy apps.PersistentVolumeClaimRetentionPolicyType 260 setDeletePolicy apps.PersistentVolumeClaimRetentionPolicyType 261 needsPodRef bool 262 needsSetRef bool 263 replicas int32 264 ordinal int 265 }{ 266 { 267 name: "retain", 268 scaleDownPolicy: apps.RetainPersistentVolumeClaimRetentionPolicyType, 269 setDeletePolicy: apps.RetainPersistentVolumeClaimRetentionPolicyType, 270 needsPodRef: false, 271 needsSetRef: false, 272 }, 273 { 274 name: "on SS delete", 275 scaleDownPolicy: apps.RetainPersistentVolumeClaimRetentionPolicyType, 276 setDeletePolicy: apps.DeletePersistentVolumeClaimRetentionPolicyType, 277 needsPodRef: false, 278 needsSetRef: true, 279 }, 280 { 281 name: "on scaledown only, condemned", 282 scaleDownPolicy: apps.DeletePersistentVolumeClaimRetentionPolicyType, 283 setDeletePolicy: apps.RetainPersistentVolumeClaimRetentionPolicyType, 284 needsPodRef: true, 285 needsSetRef: false, 286 replicas: 2, 287 ordinal: 2, 288 }, 289 { 290 name: "on scaledown only, remains", 291 scaleDownPolicy: apps.DeletePersistentVolumeClaimRetentionPolicyType, 292 setDeletePolicy: apps.RetainPersistentVolumeClaimRetentionPolicyType, 293 needsPodRef: false, 294 needsSetRef: false, 295 replicas: 2, 296 ordinal: 1, 297 }, 298 { 299 name: "on both, condemned", 300 scaleDownPolicy: apps.DeletePersistentVolumeClaimRetentionPolicyType, 301 setDeletePolicy: apps.DeletePersistentVolumeClaimRetentionPolicyType, 302 needsPodRef: true, 303 needsSetRef: false, 304 replicas: 2, 305 ordinal: 2, 306 }, 307 { 308 name: "on both, remains", 309 scaleDownPolicy: apps.DeletePersistentVolumeClaimRetentionPolicyType, 310 setDeletePolicy: apps.DeletePersistentVolumeClaimRetentionPolicyType, 311 needsPodRef: false, 312 needsSetRef: true, 313 replicas: 2, 314 ordinal: 1, 315 }, 316 } 317 318 for _, tc := range testCases { 319 for _, useOtherRefs := range []bool{false, true} { 320 for _, setPodRef := range []bool{false, true} { 321 for _, setSetRef := range []bool{false, true} { 322 _, ctx := ktesting.NewTestContext(t) 323 logger := klog.FromContext(ctx) 324 claim := v1.PersistentVolumeClaim{} 325 claim.Name = "target-claim" 326 pod := v1.Pod{} 327 pod.Name = fmt.Sprintf("pod-%d", tc.ordinal) 328 pod.GetObjectMeta().SetUID("pod-123") 329 set := apps.StatefulSet{} 330 set.Name = "stateful-set" 331 set.GetObjectMeta().SetUID("ss-456") 332 set.Spec.PersistentVolumeClaimRetentionPolicy = &apps.StatefulSetPersistentVolumeClaimRetentionPolicy{ 333 WhenScaled: tc.scaleDownPolicy, 334 WhenDeleted: tc.setDeletePolicy, 335 } 336 set.Spec.Replicas = &tc.replicas 337 if setPodRef { 338 setOwnerRef(&claim, &pod, &pod.TypeMeta) 339 } 340 if setSetRef { 341 setOwnerRef(&claim, &set, &set.TypeMeta) 342 } 343 if useOtherRefs { 344 randomObject1 := v1.Pod{} 345 randomObject1.Name = "rand1" 346 randomObject1.GetObjectMeta().SetUID("rand1-abc") 347 randomObject2 := v1.Pod{} 348 randomObject2.Name = "rand2" 349 randomObject2.GetObjectMeta().SetUID("rand2-def") 350 setOwnerRef(&claim, &randomObject1, &randomObject1.TypeMeta) 351 setOwnerRef(&claim, &randomObject2, &randomObject2.TypeMeta) 352 } 353 shouldMatch := setPodRef == tc.needsPodRef && setSetRef == tc.needsSetRef 354 if claimOwnerMatchesSetAndPod(logger, &claim, &set, &pod) != shouldMatch { 355 t.Errorf("Bad match for %s with pod=%v,set=%v,others=%v", tc.name, setPodRef, setSetRef, useOtherRefs) 356 } 357 } 358 } 359 } 360 } 361 } 362 363 func TestUpdateClaimOwnerRefForSetAndPod(t *testing.T) { 364 testCases := []struct { 365 name string 366 scaleDownPolicy apps.PersistentVolumeClaimRetentionPolicyType 367 setDeletePolicy apps.PersistentVolumeClaimRetentionPolicyType 368 condemned bool 369 needsPodRef bool 370 needsSetRef bool 371 }{ 372 { 373 name: "retain", 374 scaleDownPolicy: apps.RetainPersistentVolumeClaimRetentionPolicyType, 375 setDeletePolicy: apps.RetainPersistentVolumeClaimRetentionPolicyType, 376 condemned: false, 377 needsPodRef: false, 378 needsSetRef: false, 379 }, 380 { 381 name: "delete with set", 382 scaleDownPolicy: apps.RetainPersistentVolumeClaimRetentionPolicyType, 383 setDeletePolicy: apps.DeletePersistentVolumeClaimRetentionPolicyType, 384 condemned: false, 385 needsPodRef: false, 386 needsSetRef: true, 387 }, 388 { 389 name: "delete with scaledown, not condemned", 390 scaleDownPolicy: apps.DeletePersistentVolumeClaimRetentionPolicyType, 391 setDeletePolicy: apps.RetainPersistentVolumeClaimRetentionPolicyType, 392 condemned: false, 393 needsPodRef: false, 394 needsSetRef: false, 395 }, 396 { 397 name: "delete on scaledown, condemned", 398 scaleDownPolicy: apps.DeletePersistentVolumeClaimRetentionPolicyType, 399 setDeletePolicy: apps.RetainPersistentVolumeClaimRetentionPolicyType, 400 condemned: true, 401 needsPodRef: true, 402 needsSetRef: false, 403 }, 404 { 405 name: "delete on both, not condemned", 406 scaleDownPolicy: apps.DeletePersistentVolumeClaimRetentionPolicyType, 407 setDeletePolicy: apps.DeletePersistentVolumeClaimRetentionPolicyType, 408 condemned: false, 409 needsPodRef: false, 410 needsSetRef: true, 411 }, 412 { 413 name: "delete on both, condemned", 414 scaleDownPolicy: apps.DeletePersistentVolumeClaimRetentionPolicyType, 415 setDeletePolicy: apps.DeletePersistentVolumeClaimRetentionPolicyType, 416 condemned: true, 417 needsPodRef: true, 418 needsSetRef: false, 419 }, 420 } 421 for _, tc := range testCases { 422 for _, hasPodRef := range []bool{true, false} { 423 for _, hasSetRef := range []bool{true, false} { 424 _, ctx := ktesting.NewTestContext(t) 425 logger := klog.FromContext(ctx) 426 set := apps.StatefulSet{} 427 set.Name = "ss" 428 numReplicas := int32(5) 429 set.Spec.Replicas = &numReplicas 430 set.SetUID("ss-123") 431 set.Spec.PersistentVolumeClaimRetentionPolicy = &apps.StatefulSetPersistentVolumeClaimRetentionPolicy{ 432 WhenScaled: tc.scaleDownPolicy, 433 WhenDeleted: tc.setDeletePolicy, 434 } 435 pod := v1.Pod{} 436 if tc.condemned { 437 pod.Name = "pod-8" 438 } else { 439 pod.Name = "pod-1" 440 } 441 pod.SetUID("pod-456") 442 claim := v1.PersistentVolumeClaim{} 443 if hasPodRef { 444 setOwnerRef(&claim, &pod, &pod.TypeMeta) 445 } 446 if hasSetRef { 447 setOwnerRef(&claim, &set, &set.TypeMeta) 448 } 449 needsUpdate := hasPodRef != tc.needsPodRef || hasSetRef != tc.needsSetRef 450 shouldUpdate := updateClaimOwnerRefForSetAndPod(logger, &claim, &set, &pod) 451 if shouldUpdate != needsUpdate { 452 t.Errorf("Bad update for %s hasPodRef=%v hasSetRef=%v", tc.name, hasPodRef, hasSetRef) 453 } 454 if hasOwnerRef(&claim, &pod) != tc.needsPodRef { 455 t.Errorf("Bad pod ref for %s hasPodRef=%v hasSetRef=%v", tc.name, hasPodRef, hasSetRef) 456 } 457 if hasOwnerRef(&claim, &set) != tc.needsSetRef { 458 t.Errorf("Bad set ref for %s hasPodRef=%v hasSetRef=%v", tc.name, hasPodRef, hasSetRef) 459 } 460 } 461 } 462 } 463 } 464 465 func TestHasOwnerRef(t *testing.T) { 466 target := v1.Pod{} 467 target.SetOwnerReferences([]metav1.OwnerReference{ 468 {UID: "123"}, {UID: "456"}}) 469 ownerA := v1.Pod{} 470 ownerA.GetObjectMeta().SetUID("123") 471 ownerB := v1.Pod{} 472 ownerB.GetObjectMeta().SetUID("789") 473 if !hasOwnerRef(&target, &ownerA) { 474 t.Error("Missing owner") 475 } 476 if hasOwnerRef(&target, &ownerB) { 477 t.Error("Unexpected owner") 478 } 479 } 480 481 func TestHasStaleOwnerRef(t *testing.T) { 482 target := v1.Pod{} 483 target.SetOwnerReferences([]metav1.OwnerReference{ 484 {Name: "bob", UID: "123"}, {Name: "shirley", UID: "456"}}) 485 ownerA := v1.Pod{} 486 ownerA.SetUID("123") 487 ownerA.Name = "bob" 488 ownerB := v1.Pod{} 489 ownerB.Name = "shirley" 490 ownerB.SetUID("789") 491 ownerC := v1.Pod{} 492 ownerC.Name = "yvonne" 493 ownerC.SetUID("345") 494 if hasStaleOwnerRef(&target, &ownerA) { 495 t.Error("ownerA should not be stale") 496 } 497 if !hasStaleOwnerRef(&target, &ownerB) { 498 t.Error("ownerB should be stale") 499 } 500 if hasStaleOwnerRef(&target, &ownerC) { 501 t.Error("ownerC should not be stale") 502 } 503 } 504 505 func TestSetOwnerRef(t *testing.T) { 506 target := v1.Pod{} 507 ownerA := v1.Pod{} 508 ownerA.Name = "A" 509 ownerA.GetObjectMeta().SetUID("ABC") 510 if setOwnerRef(&target, &ownerA, &ownerA.TypeMeta) != true { 511 t.Errorf("Unexpected lack of update") 512 } 513 ownerRefs := target.GetObjectMeta().GetOwnerReferences() 514 if len(ownerRefs) != 1 { 515 t.Errorf("Unexpected owner ref count: %d", len(ownerRefs)) 516 } 517 if ownerRefs[0].UID != "ABC" { 518 t.Errorf("Unexpected owner UID %v", ownerRefs[0].UID) 519 } 520 if setOwnerRef(&target, &ownerA, &ownerA.TypeMeta) != false { 521 t.Errorf("Unexpected update") 522 } 523 if len(target.GetObjectMeta().GetOwnerReferences()) != 1 { 524 t.Error("Unexpected duplicate reference") 525 } 526 ownerB := v1.Pod{} 527 ownerB.Name = "B" 528 ownerB.GetObjectMeta().SetUID("BCD") 529 if setOwnerRef(&target, &ownerB, &ownerB.TypeMeta) != true { 530 t.Error("Unexpected lack of second update") 531 } 532 ownerRefs = target.GetObjectMeta().GetOwnerReferences() 533 if len(ownerRefs) != 2 { 534 t.Errorf("Unexpected owner ref count: %d", len(ownerRefs)) 535 } 536 if ownerRefs[0].UID != "ABC" || ownerRefs[1].UID != "BCD" { 537 t.Errorf("Bad second ownerRefs: %v", ownerRefs) 538 } 539 } 540 541 func TestRemoveOwnerRef(t *testing.T) { 542 target := v1.Pod{} 543 ownerA := v1.Pod{} 544 ownerA.Name = "A" 545 ownerA.GetObjectMeta().SetUID("ABC") 546 if removeOwnerRef(&target, &ownerA) != false { 547 t.Error("Unexpected update on empty remove") 548 } 549 setOwnerRef(&target, &ownerA, &ownerA.TypeMeta) 550 if removeOwnerRef(&target, &ownerA) != true { 551 t.Error("Unexpected lack of update") 552 } 553 if len(target.GetObjectMeta().GetOwnerReferences()) != 0 { 554 t.Error("Unexpected owner reference remains") 555 } 556 557 ownerB := v1.Pod{} 558 ownerB.Name = "B" 559 ownerB.GetObjectMeta().SetUID("BCD") 560 561 setOwnerRef(&target, &ownerA, &ownerA.TypeMeta) 562 if removeOwnerRef(&target, &ownerB) != false { 563 t.Error("Unexpected update for mismatched owner") 564 } 565 if len(target.GetObjectMeta().GetOwnerReferences()) != 1 { 566 t.Error("Missing ref after no-op remove") 567 } 568 setOwnerRef(&target, &ownerB, &ownerB.TypeMeta) 569 if removeOwnerRef(&target, &ownerA) != true { 570 t.Error("Missing update for second remove") 571 } 572 ownerRefs := target.GetObjectMeta().GetOwnerReferences() 573 if len(ownerRefs) != 1 { 574 t.Error("Extra ref after second remove") 575 } 576 if ownerRefs[0].UID != "BCD" { 577 t.Error("Bad UID after second remove") 578 } 579 } 580 581 func TestIsRunningAndReady(t *testing.T) { 582 set := newStatefulSet(3) 583 pod := newStatefulSetPod(set, 1) 584 if isRunningAndReady(pod) { 585 t.Error("isRunningAndReady does not respect Pod phase") 586 } 587 pod.Status.Phase = v1.PodRunning 588 if isRunningAndReady(pod) { 589 t.Error("isRunningAndReady does not respect Pod condition") 590 } 591 condition := v1.PodCondition{Type: v1.PodReady, Status: v1.ConditionTrue} 592 podutil.UpdatePodCondition(&pod.Status, &condition) 593 if !isRunningAndReady(pod) { 594 t.Error("Pod should be running and ready") 595 } 596 } 597 598 func TestAscendingOrdinal(t *testing.T) { 599 set := newStatefulSet(10) 600 pods := make([]*v1.Pod, 10) 601 perm := rand.Perm(10) 602 for i, v := range perm { 603 pods[i] = newStatefulSetPod(set, v) 604 } 605 sort.Sort(ascendingOrdinal(pods)) 606 if !sort.IsSorted(ascendingOrdinal(pods)) { 607 t.Error("ascendingOrdinal fails to sort Pods") 608 } 609 } 610 611 func TestOverlappingStatefulSets(t *testing.T) { 612 sets := make([]*apps.StatefulSet, 10) 613 perm := rand.Perm(10) 614 for i, v := range perm { 615 sets[i] = newStatefulSet(10) 616 sets[i].CreationTimestamp = metav1.NewTime(sets[i].CreationTimestamp.Add(time.Duration(v) * time.Second)) 617 } 618 sort.Sort(overlappingStatefulSets(sets)) 619 if !sort.IsSorted(overlappingStatefulSets(sets)) { 620 t.Error("ascendingOrdinal fails to sort Pods") 621 } 622 for i, v := range perm { 623 sets[i] = newStatefulSet(10) 624 sets[i].Name = strconv.FormatInt(int64(v), 10) 625 } 626 sort.Sort(overlappingStatefulSets(sets)) 627 if !sort.IsSorted(overlappingStatefulSets(sets)) { 628 t.Error("ascendingOrdinal fails to sort Pods") 629 } 630 } 631 632 func TestNewPodControllerRef(t *testing.T) { 633 set := newStatefulSet(1) 634 pod := newStatefulSetPod(set, 0) 635 controllerRef := metav1.GetControllerOf(pod) 636 if controllerRef == nil { 637 t.Fatalf("No ControllerRef found on new pod") 638 } 639 if got, want := controllerRef.APIVersion, apps.SchemeGroupVersion.String(); got != want { 640 t.Errorf("controllerRef.APIVersion = %q, want %q", got, want) 641 } 642 if got, want := controllerRef.Kind, "StatefulSet"; got != want { 643 t.Errorf("controllerRef.Kind = %q, want %q", got, want) 644 } 645 if got, want := controllerRef.Name, set.Name; got != want { 646 t.Errorf("controllerRef.Name = %q, want %q", got, want) 647 } 648 if got, want := controllerRef.UID, set.UID; got != want { 649 t.Errorf("controllerRef.UID = %q, want %q", got, want) 650 } 651 if got, want := *controllerRef.Controller, true; got != want { 652 t.Errorf("controllerRef.Controller = %v, want %v", got, want) 653 } 654 } 655 656 func TestCreateApplyRevision(t *testing.T) { 657 set := newStatefulSet(1) 658 set.Status.CollisionCount = new(int32) 659 revision, err := newRevision(set, 1, set.Status.CollisionCount) 660 if err != nil { 661 t.Fatal(err) 662 } 663 set.Spec.Template.Spec.Containers[0].Name = "foo" 664 if set.Annotations == nil { 665 set.Annotations = make(map[string]string) 666 } 667 key := "foo" 668 expectedValue := "bar" 669 set.Annotations[key] = expectedValue 670 restoredSet, err := ApplyRevision(set, revision) 671 if err != nil { 672 t.Fatal(err) 673 } 674 restoredRevision, err := newRevision(restoredSet, 2, restoredSet.Status.CollisionCount) 675 if err != nil { 676 t.Fatal(err) 677 } 678 if !history.EqualRevision(revision, restoredRevision) { 679 t.Errorf("wanted %v got %v", string(revision.Data.Raw), string(restoredRevision.Data.Raw)) 680 } 681 value, ok := restoredRevision.Annotations[key] 682 if !ok { 683 t.Errorf("missing annotation %s", key) 684 } 685 if value != expectedValue { 686 t.Errorf("for annotation %s wanted %s got %s", key, expectedValue, value) 687 } 688 } 689 690 func TestRollingUpdateApplyRevision(t *testing.T) { 691 set := newStatefulSet(1) 692 set.Status.CollisionCount = new(int32) 693 currentSet := set.DeepCopy() 694 currentRevision, err := newRevision(set, 1, set.Status.CollisionCount) 695 if err != nil { 696 t.Fatal(err) 697 } 698 699 set.Spec.Template.Spec.Containers[0].Env = []v1.EnvVar{{Name: "foo", Value: "bar"}} 700 updateSet := set.DeepCopy() 701 updateRevision, err := newRevision(set, 2, set.Status.CollisionCount) 702 if err != nil { 703 t.Fatal(err) 704 } 705 706 restoredCurrentSet, err := ApplyRevision(set, currentRevision) 707 if err != nil { 708 t.Fatal(err) 709 } 710 if !reflect.DeepEqual(currentSet.Spec.Template, restoredCurrentSet.Spec.Template) { 711 t.Errorf("want %v got %v", currentSet.Spec.Template, restoredCurrentSet.Spec.Template) 712 } 713 714 restoredUpdateSet, err := ApplyRevision(set, updateRevision) 715 if err != nil { 716 t.Fatal(err) 717 } 718 if !reflect.DeepEqual(updateSet.Spec.Template, restoredUpdateSet.Spec.Template) { 719 t.Errorf("want %v got %v", updateSet.Spec.Template, restoredUpdateSet.Spec.Template) 720 } 721 } 722 723 func TestGetPersistentVolumeClaims(t *testing.T) { 724 725 // nil inherits statefulset labels 726 pod := newPod() 727 statefulSet := newStatefulSet(1) 728 statefulSet.Spec.Selector.MatchLabels = nil 729 claims := getPersistentVolumeClaims(statefulSet, pod) 730 pvc := newPVC("datadir-foo-0") 731 resultClaims := map[string]v1.PersistentVolumeClaim{"datadir": pvc} 732 733 if !reflect.DeepEqual(claims, resultClaims) { 734 t.Fatalf("Unexpected pvc:\n %+v\n, desired pvc:\n %+v", claims, resultClaims) 735 } 736 737 // nil inherits statefulset labels 738 statefulSet.Spec.Selector.MatchLabels = map[string]string{"test": "test"} 739 claims = getPersistentVolumeClaims(statefulSet, pod) 740 pvc.SetLabels(map[string]string{"test": "test"}) 741 resultClaims = map[string]v1.PersistentVolumeClaim{"datadir": pvc} 742 if !reflect.DeepEqual(claims, resultClaims) { 743 t.Fatalf("Unexpected pvc:\n %+v\n, desired pvc:\n %+v", claims, resultClaims) 744 } 745 746 // non-nil with non-overlapping labels merge pvc and statefulset labels 747 statefulSet.Spec.Selector.MatchLabels = map[string]string{"name": "foo"} 748 statefulSet.Spec.VolumeClaimTemplates[0].ObjectMeta.Labels = map[string]string{"test": "test"} 749 claims = getPersistentVolumeClaims(statefulSet, pod) 750 pvc.SetLabels(map[string]string{"test": "test", "name": "foo"}) 751 resultClaims = map[string]v1.PersistentVolumeClaim{"datadir": pvc} 752 if !reflect.DeepEqual(claims, resultClaims) { 753 t.Fatalf("Unexpected pvc:\n %+v\n, desired pvc:\n %+v", claims, resultClaims) 754 } 755 756 // non-nil with overlapping labels merge pvc and statefulset labels and prefer statefulset labels 757 statefulSet.Spec.Selector.MatchLabels = map[string]string{"test": "foo"} 758 statefulSet.Spec.VolumeClaimTemplates[0].ObjectMeta.Labels = map[string]string{"test": "test"} 759 claims = getPersistentVolumeClaims(statefulSet, pod) 760 pvc.SetLabels(map[string]string{"test": "foo"}) 761 resultClaims = map[string]v1.PersistentVolumeClaim{"datadir": pvc} 762 if !reflect.DeepEqual(claims, resultClaims) { 763 t.Fatalf("Unexpected pvc:\n %+v\n, desired pvc:\n %+v", claims, resultClaims) 764 } 765 } 766 767 func newPod() *v1.Pod { 768 return &v1.Pod{ 769 ObjectMeta: metav1.ObjectMeta{ 770 Name: "foo-0", 771 Namespace: v1.NamespaceDefault, 772 }, 773 Spec: v1.PodSpec{ 774 Containers: []v1.Container{ 775 { 776 Name: "nginx", 777 Image: "nginx", 778 }, 779 }, 780 }, 781 } 782 } 783 784 func newPVC(name string) v1.PersistentVolumeClaim { 785 return v1.PersistentVolumeClaim{ 786 ObjectMeta: metav1.ObjectMeta{ 787 Namespace: v1.NamespaceDefault, 788 Name: name, 789 }, 790 Spec: v1.PersistentVolumeClaimSpec{ 791 Resources: v1.VolumeResourceRequirements{ 792 Requests: v1.ResourceList{ 793 v1.ResourceStorage: *resource.NewQuantity(1, resource.BinarySI), 794 }, 795 }, 796 }, 797 } 798 } 799 800 func newStatefulSetWithVolumes(replicas int32, name string, petMounts []v1.VolumeMount, podMounts []v1.VolumeMount) *apps.StatefulSet { 801 mounts := append(petMounts, podMounts...) 802 claims := []v1.PersistentVolumeClaim{} 803 for _, m := range petMounts { 804 claims = append(claims, newPVC(m.Name)) 805 } 806 807 vols := []v1.Volume{} 808 for _, m := range podMounts { 809 vols = append(vols, v1.Volume{ 810 Name: m.Name, 811 VolumeSource: v1.VolumeSource{ 812 HostPath: &v1.HostPathVolumeSource{ 813 Path: fmt.Sprintf("/tmp/%v", m.Name), 814 }, 815 }, 816 }) 817 } 818 819 template := v1.PodTemplateSpec{ 820 Spec: v1.PodSpec{ 821 Containers: []v1.Container{ 822 { 823 Name: "nginx", 824 Image: "nginx", 825 VolumeMounts: mounts, 826 }, 827 }, 828 Volumes: vols, 829 }, 830 } 831 832 template.Labels = map[string]string{"foo": "bar"} 833 834 return &apps.StatefulSet{ 835 TypeMeta: metav1.TypeMeta{ 836 Kind: "StatefulSet", 837 APIVersion: "apps/v1", 838 }, 839 ObjectMeta: metav1.ObjectMeta{ 840 Name: name, 841 Namespace: v1.NamespaceDefault, 842 UID: types.UID("test"), 843 }, 844 Spec: apps.StatefulSetSpec{ 845 Selector: &metav1.LabelSelector{ 846 MatchLabels: map[string]string{"foo": "bar"}, 847 }, 848 Replicas: ptr.To(replicas), 849 Template: template, 850 VolumeClaimTemplates: claims, 851 ServiceName: "governingsvc", 852 UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, 853 PersistentVolumeClaimRetentionPolicy: &apps.StatefulSetPersistentVolumeClaimRetentionPolicy{ 854 WhenScaled: apps.RetainPersistentVolumeClaimRetentionPolicyType, 855 WhenDeleted: apps.RetainPersistentVolumeClaimRetentionPolicyType, 856 }, 857 RevisionHistoryLimit: func() *int32 { 858 limit := int32(2) 859 return &limit 860 }(), 861 }, 862 } 863 } 864 865 func newStatefulSet(replicas int32) *apps.StatefulSet { 866 petMounts := []v1.VolumeMount{ 867 {Name: "datadir", MountPath: "/tmp/zookeeper"}, 868 } 869 podMounts := []v1.VolumeMount{ 870 {Name: "home", MountPath: "/home"}, 871 } 872 return newStatefulSetWithVolumes(replicas, "foo", petMounts, podMounts) 873 } 874 875 func newStatefulSetWithLabels(replicas int32, name string, uid types.UID, labels map[string]string) *apps.StatefulSet { 876 // Converting all the map-only selectors to set-based selectors. 877 var testMatchExpressions []metav1.LabelSelectorRequirement 878 for key, value := range labels { 879 sel := metav1.LabelSelectorRequirement{ 880 Key: key, 881 Operator: metav1.LabelSelectorOpIn, 882 Values: []string{value}, 883 } 884 testMatchExpressions = append(testMatchExpressions, sel) 885 } 886 return &apps.StatefulSet{ 887 TypeMeta: metav1.TypeMeta{ 888 Kind: "StatefulSet", 889 APIVersion: "apps/v1", 890 }, 891 ObjectMeta: metav1.ObjectMeta{ 892 Name: name, 893 Namespace: v1.NamespaceDefault, 894 UID: uid, 895 }, 896 Spec: apps.StatefulSetSpec{ 897 Selector: &metav1.LabelSelector{ 898 // Purposely leaving MatchLabels nil, so to ensure it will break if any link 899 // in the chain ignores the set-based MatchExpressions. 900 MatchLabels: nil, 901 MatchExpressions: testMatchExpressions, 902 }, 903 Replicas: ptr.To(replicas), 904 PersistentVolumeClaimRetentionPolicy: &apps.StatefulSetPersistentVolumeClaimRetentionPolicy{ 905 WhenScaled: apps.RetainPersistentVolumeClaimRetentionPolicyType, 906 WhenDeleted: apps.RetainPersistentVolumeClaimRetentionPolicyType, 907 }, 908 Template: v1.PodTemplateSpec{ 909 ObjectMeta: metav1.ObjectMeta{ 910 Labels: labels, 911 }, 912 Spec: v1.PodSpec{ 913 Containers: []v1.Container{ 914 { 915 Name: "nginx", 916 Image: "nginx", 917 VolumeMounts: []v1.VolumeMount{ 918 {Name: "datadir", MountPath: "/tmp/"}, 919 {Name: "home", MountPath: "/home"}, 920 }, 921 }, 922 }, 923 Volumes: []v1.Volume{{ 924 Name: "home", 925 VolumeSource: v1.VolumeSource{ 926 HostPath: &v1.HostPathVolumeSource{ 927 Path: fmt.Sprintf("/tmp/%v", "home"), 928 }, 929 }}}, 930 }, 931 }, 932 VolumeClaimTemplates: []v1.PersistentVolumeClaim{ 933 { 934 ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "datadir"}, 935 Spec: v1.PersistentVolumeClaimSpec{ 936 Resources: v1.VolumeResourceRequirements{ 937 Requests: v1.ResourceList{ 938 v1.ResourceStorage: *resource.NewQuantity(1, resource.BinarySI), 939 }, 940 }, 941 }, 942 }, 943 }, 944 ServiceName: "governingsvc", 945 }, 946 } 947 } 948 949 func TestGetStatefulSetMaxUnavailable(t *testing.T) { 950 testCases := []struct { 951 maxUnavailable *intstr.IntOrString 952 replicaCount int 953 expectedMaxUnavailable int 954 }{ 955 // it wouldn't hurt to also test 0 and 0%, even if they should have been forbidden by API validation. 956 {maxUnavailable: nil, replicaCount: 10, expectedMaxUnavailable: 1}, 957 {maxUnavailable: ptr.To(intstr.FromInt32(3)), replicaCount: 10, expectedMaxUnavailable: 3}, 958 {maxUnavailable: ptr.To(intstr.FromInt32(3)), replicaCount: 0, expectedMaxUnavailable: 3}, 959 {maxUnavailable: ptr.To(intstr.FromInt32(0)), replicaCount: 0, expectedMaxUnavailable: 1}, 960 {maxUnavailable: ptr.To(intstr.FromString("10%")), replicaCount: 25, expectedMaxUnavailable: 2}, 961 {maxUnavailable: ptr.To(intstr.FromString("100%")), replicaCount: 5, expectedMaxUnavailable: 5}, 962 {maxUnavailable: ptr.To(intstr.FromString("50%")), replicaCount: 5, expectedMaxUnavailable: 2}, 963 {maxUnavailable: ptr.To(intstr.FromString("10%")), replicaCount: 5, expectedMaxUnavailable: 1}, 964 {maxUnavailable: ptr.To(intstr.FromString("1%")), replicaCount: 0, expectedMaxUnavailable: 1}, 965 {maxUnavailable: ptr.To(intstr.FromString("0%")), replicaCount: 0, expectedMaxUnavailable: 1}, 966 } 967 968 for i, tc := range testCases { 969 t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { 970 gotMaxUnavailable, err := getStatefulSetMaxUnavailable(tc.maxUnavailable, tc.replicaCount) 971 if err != nil { 972 t.Fatal(err) 973 } 974 if gotMaxUnavailable != tc.expectedMaxUnavailable { 975 t.Errorf("Expected maxUnavailable %v, got pods %v", tc.expectedMaxUnavailable, gotMaxUnavailable) 976 } 977 }) 978 } 979 }