...

Source file src/k8s.io/kubernetes/pkg/controller/statefulset/stateful_set_utils_test.go

Documentation: k8s.io/kubernetes/pkg/controller/statefulset

     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  }
   980  

View as plain text