1
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
45
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
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
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
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
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
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
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
899
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
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 }
980
View as plain text