diff --git a/pkg/controller/statefulset/stateful_pod_control_test.go b/pkg/controller/statefulset/stateful_pod_control_test.go index 7fee184387..ef4c68cd9d 100644 --- a/pkg/controller/statefulset/stateful_pod_control_test.go +++ b/pkg/controller/statefulset/stateful_pod_control_test.go @@ -21,14 +21,21 @@ import ( "context" "errors" "fmt" + "reflect" + "strconv" "strings" "testing" "time" + appsv1beta1 "github.com/openkruise/kruise/apis/apps/v1beta1" + "github.com/openkruise/kruise/pkg/features" + "github.com/openkruise/kruise/pkg/util" + utilfeature "github.com/openkruise/kruise/pkg/util/feature" apps "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes/fake" @@ -38,10 +45,7 @@ import ( "k8s.io/client-go/tools/record" _ "k8s.io/kubernetes/pkg/apis/apps/install" _ "k8s.io/kubernetes/pkg/apis/core/install" - - appsv1beta1 "github.com/openkruise/kruise/apis/apps/v1beta1" - "github.com/openkruise/kruise/pkg/features" - utilfeature "github.com/openkruise/kruise/pkg/util/feature" + utilpointer "k8s.io/utils/pointer" ) func TestStatefulPodControlCreatesPods(t *testing.T) { @@ -849,3 +853,402 @@ func collectEvents(source <-chan string) []string { } return events } + +func TestUpdatePodClaimForRetentionPolicy(t *testing.T) { + cases := []struct { + name string + getStatefulSet func() *appsv1beta1.StatefulSet + getPods func(set *appsv1beta1.StatefulSet) []*v1.Pod + expectPvcOwnerRef func(pvcName string) metav1.OwnerReference + }{ + { + name: "reserveOrdinals is nil, scaleDown=false, whenScaled=Retain, whenDeleted=Delete", + getStatefulSet: func() *appsv1beta1.StatefulSet { + set := newStatefulSet(5) + set.Spec.PersistentVolumeClaimRetentionPolicy = &appsv1beta1.StatefulSetPersistentVolumeClaimRetentionPolicy{ + WhenDeleted: appsv1beta1.DeletePersistentVolumeClaimRetentionPolicyType, + WhenScaled: appsv1beta1.RetainPersistentVolumeClaimRetentionPolicyType, + } + return set + }, + getPods: func(set *appsv1beta1.StatefulSet) []*v1.Pod { + replicaCount, reserveOrdinals := getStatefulSetReplicasRange(set) + pods := make([]*v1.Pod, 0) + expectIndex := []int{0, 1, 2, 3, 4} + currentIndex := make([]int, 0) + for i := 0; i < replicaCount; i++ { + if reserveOrdinals.Has(i) { + continue + } + currentIndex = append(currentIndex, i) + pods = append(pods, newStatefulSetPod(set, i)) + } + if !reflect.DeepEqual(expectIndex, currentIndex) { + t.Fatalf("expect(%v), but get(%v)", expectIndex, currentIndex) + } + return pods + }, + expectPvcOwnerRef: func(pvcName string) metav1.OwnerReference { + /*separatorIndex := strings.Index(pvcName, "-") + 1 + podName := pvcName[separatorIndex:]*/ + return metav1.OwnerReference{ + APIVersion: "apps.kruise.io/v1beta1", + Kind: "StatefulSet", + Name: "foo", + } + }, + }, + { + name: "reserveOrdinals is nil, scaleDown=false, whenScaled=Delete, whenDeleted=Delete", + getStatefulSet: func() *appsv1beta1.StatefulSet { + set := newStatefulSet(5) + set.Spec.PersistentVolumeClaimRetentionPolicy = &appsv1beta1.StatefulSetPersistentVolumeClaimRetentionPolicy{ + WhenDeleted: appsv1beta1.DeletePersistentVolumeClaimRetentionPolicyType, + WhenScaled: appsv1beta1.DeletePersistentVolumeClaimRetentionPolicyType, + } + return set + }, + getPods: func(set *appsv1beta1.StatefulSet) []*v1.Pod { + replicaCount, reserveOrdinals := getStatefulSetReplicasRange(set) + pods := make([]*v1.Pod, 0) + expectIndex := []int{0, 1, 2, 3, 4} + currentIndex := make([]int, 0) + for i := 0; i < replicaCount; i++ { + if reserveOrdinals.Has(i) { + continue + } + currentIndex = append(currentIndex, i) + pods = append(pods, newStatefulSetPod(set, i)) + } + if !reflect.DeepEqual(expectIndex, currentIndex) { + t.Fatalf("expect(%v), but get(%v)", expectIndex, currentIndex) + } + return pods + }, + expectPvcOwnerRef: func(pvcName string) metav1.OwnerReference { + /*separatorIndex := strings.Index(pvcName, "-") + 1 + podName := pvcName[separatorIndex:]*/ + return metav1.OwnerReference{ + APIVersion: "apps.kruise.io/v1beta1", + Kind: "StatefulSet", + Name: "foo", + } + }, + }, + { + name: "reserveOrdinals is nil, scaleDown=true, whenScaled=Retain, whenDeleted=Delete", + getStatefulSet: func() *appsv1beta1.StatefulSet { + set := newStatefulSet(3) + set.Spec.PersistentVolumeClaimRetentionPolicy = &appsv1beta1.StatefulSetPersistentVolumeClaimRetentionPolicy{ + WhenDeleted: appsv1beta1.DeletePersistentVolumeClaimRetentionPolicyType, + WhenScaled: appsv1beta1.RetainPersistentVolumeClaimRetentionPolicyType, + } + return set + }, + getPods: func(set *appsv1beta1.StatefulSet) []*v1.Pod { + setClone := set.DeepCopy() + setClone.Spec.Replicas = utilpointer.Int32(5) + replicaCount, reserveOrdinals := getStatefulSetReplicasRange(setClone) + pods := make([]*v1.Pod, 0) + expectIndex := []int{0, 1, 2, 3, 4} + currentIndex := make([]int, 0) + for i := 0; i < replicaCount; i++ { + if reserveOrdinals.Has(i) { + continue + } + currentIndex = append(currentIndex, i) + pods = append(pods, newStatefulSetPod(set, i)) + } + if !reflect.DeepEqual(expectIndex, currentIndex) { + t.Fatalf("expect(%v), but get(%v)", expectIndex, currentIndex) + } + return pods + }, + expectPvcOwnerRef: func(pvcName string) metav1.OwnerReference { + sIndex1 := strings.Index(pvcName, "-") + 1 + podName := pvcName[sIndex1:] + sIndex2 := strings.LastIndex(pvcName, "-") + 1 + index, _ := strconv.Atoi(pvcName[sIndex2:]) + if index < 5 { + return metav1.OwnerReference{ + APIVersion: "apps.kruise.io/v1beta1", + Kind: "StatefulSet", + Name: "foo", + } + } + return metav1.OwnerReference{ + APIVersion: "v1", + Kind: "Pod", + Name: podName, + } + }, + }, + { + name: "reserveOrdinals is nil, scaleDown=true, whenScaled=Delete, whenDeleted=Delete", + getStatefulSet: func() *appsv1beta1.StatefulSet { + set := newStatefulSet(3) + set.Spec.PersistentVolumeClaimRetentionPolicy = &appsv1beta1.StatefulSetPersistentVolumeClaimRetentionPolicy{ + WhenDeleted: appsv1beta1.DeletePersistentVolumeClaimRetentionPolicyType, + WhenScaled: appsv1beta1.DeletePersistentVolumeClaimRetentionPolicyType, + } + return set + }, + getPods: func(set *appsv1beta1.StatefulSet) []*v1.Pod { + setClone := set.DeepCopy() + setClone.Spec.Replicas = utilpointer.Int32(5) + replicaCount, reserveOrdinals := getStatefulSetReplicasRange(setClone) + pods := make([]*v1.Pod, 0) + expectIndex := []int{0, 1, 2, 3, 4} + currentIndex := make([]int, 0) + for i := 0; i < replicaCount; i++ { + if reserveOrdinals.Has(i) { + continue + } + currentIndex = append(currentIndex, i) + pods = append(pods, newStatefulSetPod(set, i)) + } + if !reflect.DeepEqual(expectIndex, currentIndex) { + t.Fatalf("expect(%v), but get(%v)", expectIndex, currentIndex) + } + return pods + }, + expectPvcOwnerRef: func(pvcName string) metav1.OwnerReference { + sIndex1 := strings.Index(pvcName, "-") + 1 + podName := pvcName[sIndex1:] + sIndex2 := strings.LastIndex(pvcName, "-") + 1 + index, _ := strconv.Atoi(pvcName[sIndex2:]) + if index < 3 { + return metav1.OwnerReference{ + APIVersion: "apps.kruise.io/v1beta1", + Kind: "StatefulSet", + Name: "foo", + } + } + return metav1.OwnerReference{ + APIVersion: "v1", + Kind: "Pod", + Name: podName, + } + }, + }, + { + name: "reserveOrdinals is [2,4], scaleDown=false, whenScaled=Retain, whenDeleted=Delete", + getStatefulSet: func() *appsv1beta1.StatefulSet { + set := newStatefulSet(5) + set.Spec.PersistentVolumeClaimRetentionPolicy = &appsv1beta1.StatefulSetPersistentVolumeClaimRetentionPolicy{ + WhenDeleted: appsv1beta1.DeletePersistentVolumeClaimRetentionPolicyType, + WhenScaled: appsv1beta1.RetainPersistentVolumeClaimRetentionPolicyType, + } + set.Spec.ReserveOrdinals = []int{2, 4} + return set + }, + getPods: func(set *appsv1beta1.StatefulSet) []*v1.Pod { + replicaCount, reserveOrdinals := getStatefulSetReplicasRange(set) + pods := make([]*v1.Pod, 0) + expectIndex := []int{0, 1, 3, 5, 6} + currentIndex := make([]int, 0) + for i := 0; i < replicaCount; i++ { + if reserveOrdinals.Has(i) { + continue + } + currentIndex = append(currentIndex, i) + pods = append(pods, newStatefulSetPod(set, i)) + } + if !reflect.DeepEqual(expectIndex, currentIndex) { + t.Fatalf("expect(%v), but get(%v)", expectIndex, currentIndex) + } + return pods + }, + expectPvcOwnerRef: func(pvcName string) metav1.OwnerReference { + /*separatorIndex := strings.Index(pvcName, "-") + 1 + podName := pvcName[separatorIndex:]*/ + return metav1.OwnerReference{ + APIVersion: "apps.kruise.io/v1beta1", + Kind: "StatefulSet", + Name: "foo", + } + }, + }, + { + name: "reserveOrdinals is [2,4], scaleDown=false, whenScaled=Delete, whenDeleted=Delete", + getStatefulSet: func() *appsv1beta1.StatefulSet { + set := newStatefulSet(5) + set.Spec.PersistentVolumeClaimRetentionPolicy = &appsv1beta1.StatefulSetPersistentVolumeClaimRetentionPolicy{ + WhenDeleted: appsv1beta1.DeletePersistentVolumeClaimRetentionPolicyType, + WhenScaled: appsv1beta1.DeletePersistentVolumeClaimRetentionPolicyType, + } + set.Spec.ReserveOrdinals = []int{2, 4} + return set + }, + getPods: func(set *appsv1beta1.StatefulSet) []*v1.Pod { + replicaCount, reserveOrdinals := getStatefulSetReplicasRange(set) + pods := make([]*v1.Pod, 0) + expectIndex := []int{0, 1, 3, 5, 6} + currentIndex := make([]int, 0) + for i := 0; i < replicaCount; i++ { + if reserveOrdinals.Has(i) { + continue + } + currentIndex = append(currentIndex, i) + pods = append(pods, newStatefulSetPod(set, i)) + } + if !reflect.DeepEqual(expectIndex, currentIndex) { + t.Fatalf("expect(%v), but get(%v)", expectIndex, currentIndex) + } + return pods + }, + expectPvcOwnerRef: func(pvcName string) metav1.OwnerReference { + /*separatorIndex := strings.Index(pvcName, "-") + 1 + podName := pvcName[separatorIndex:]*/ + return metav1.OwnerReference{ + APIVersion: "apps.kruise.io/v1beta1", + Kind: "StatefulSet", + Name: "foo", + } + }, + }, + { + name: "reserveOrdinals is [2,4], scaleDown=true, whenScaled=Delete, whenDeleted=Delete", + getStatefulSet: func() *appsv1beta1.StatefulSet { + set := newStatefulSet(3) + set.Spec.PersistentVolumeClaimRetentionPolicy = &appsv1beta1.StatefulSetPersistentVolumeClaimRetentionPolicy{ + WhenDeleted: appsv1beta1.DeletePersistentVolumeClaimRetentionPolicyType, + WhenScaled: appsv1beta1.DeletePersistentVolumeClaimRetentionPolicyType, + } + set.Spec.ReserveOrdinals = []int{2, 4} + return set + }, + getPods: func(set *appsv1beta1.StatefulSet) []*v1.Pod { + setClone := set.DeepCopy() + setClone.Spec.Replicas = utilpointer.Int32(5) + replicaCount, reserveOrdinals := getStatefulSetReplicasRange(setClone) + pods := make([]*v1.Pod, 0) + expectIndex := []int{0, 1, 3, 5, 6} + currentIndex := make([]int, 0) + for i := 0; i < replicaCount; i++ { + if reserveOrdinals.Has(i) { + continue + } + currentIndex = append(currentIndex, i) + pods = append(pods, newStatefulSetPod(set, i)) + } + if !reflect.DeepEqual(expectIndex, currentIndex) { + t.Fatalf("expect(%v), but get(%v)", expectIndex, currentIndex) + } + return pods + }, + expectPvcOwnerRef: func(pvcName string) metav1.OwnerReference { + sIndex1 := strings.Index(pvcName, "-") + 1 + podName := pvcName[sIndex1:] + sIndex2 := strings.LastIndex(pvcName, "-") + 1 + index, _ := strconv.Atoi(pvcName[sIndex2:]) + if index < 5 { + return metav1.OwnerReference{ + APIVersion: "apps.kruise.io/v1beta1", + Kind: "StatefulSet", + Name: "foo", + } + } + return metav1.OwnerReference{ + APIVersion: "v1", + Kind: "Pod", + Name: podName, + } + }, + }, + { + name: "reserveOrdinals is [2,4], scaleDown=true, whenScaled=Retain, whenDeleted=Delete", + getStatefulSet: func() *appsv1beta1.StatefulSet { + set := newStatefulSet(3) + set.Spec.PersistentVolumeClaimRetentionPolicy = &appsv1beta1.StatefulSetPersistentVolumeClaimRetentionPolicy{ + WhenDeleted: appsv1beta1.DeletePersistentVolumeClaimRetentionPolicyType, + WhenScaled: appsv1beta1.RetainPersistentVolumeClaimRetentionPolicyType, + } + set.Spec.ReserveOrdinals = []int{2, 4} + return set + }, + getPods: func(set *appsv1beta1.StatefulSet) []*v1.Pod { + setClone := set.DeepCopy() + setClone.Spec.Replicas = utilpointer.Int32(5) + replicaCount, reserveOrdinals := getStatefulSetReplicasRange(setClone) + pods := make([]*v1.Pod, 0) + expectIndex := []int{0, 1, 3, 5, 6} + currentIndex := make([]int, 0) + for i := 0; i < replicaCount; i++ { + if reserveOrdinals.Has(i) { + continue + } + currentIndex = append(currentIndex, i) + pods = append(pods, newStatefulSetPod(set, i)) + } + if !reflect.DeepEqual(expectIndex, currentIndex) { + t.Fatalf("expect(%v), but get(%v)", expectIndex, currentIndex) + } + return pods + }, + expectPvcOwnerRef: func(pvcName string) metav1.OwnerReference { + sIndex1 := strings.Index(pvcName, "-") + 1 + podName := pvcName[sIndex1:] + sIndex2 := strings.LastIndex(pvcName, "-") + 1 + index, _ := strconv.Atoi(pvcName[sIndex2:]) + if index < 7 { + return metav1.OwnerReference{ + APIVersion: "apps.kruise.io/v1beta1", + Kind: "StatefulSet", + Name: "foo", + } + } + return metav1.OwnerReference{ + APIVersion: "v1", + Kind: "Pod", + Name: podName, + } + }, + }, + } + + for _, cs := range cases { + t.Run(cs.name, func(t *testing.T) { + set := cs.getStatefulSet() + pods := cs.getPods(set) + claimIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) + claimLister := corelisters.NewPersistentVolumeClaimLister(claimIndexer) + claimObjects := make([]runtime.Object, 0) + for _, pod := range pods { + for _, claim := range getPersistentVolumeClaims(set, pod) { + clone := claim.DeepCopy() + claimObjects = append(claimObjects, clone) + _ = claimIndexer.Add(clone) + } + } + fakeClient := fake.NewSimpleClientset(claimObjects...) + control := NewStatefulPodControl(fakeClient, nil, claimLister, nil) + for _, pod := range pods { + err := control.UpdatePodClaimForRetentionPolicy(set, pod) + if err != nil { + t.Fatalf(err.Error()) + } + } + + claims, err := claimLister.List(labels.Everything()) + if err != nil { + t.Fatalf(err.Error()) + } + + for _, claim := range claims { + obj, err := fakeClient.CoreV1().PersistentVolumeClaims(claim.Namespace).Get(context.TODO(), claim.Name, metav1.GetOptions{}) + if err != nil { + t.Fatalf(err.Error()) + } + ownerRef := obj.GetOwnerReferences() + if len(ownerRef) != 1 { + t.Fatalf("claim ownerRef is nil") + } + expect := cs.expectPvcOwnerRef(claim.Name) + if expect.Kind != ownerRef[0].Kind || expect.Name != ownerRef[0].Name { + t.Fatalf("expect(%s), but get(%s)", util.DumpJSON(expect), util.DumpJSON(ownerRef[0])) + } + } + }) + } +} diff --git a/pkg/controller/statefulset/stateful_set_control.go b/pkg/controller/statefulset/stateful_set_control.go index 8580f731e0..c9ad93ea1c 100644 --- a/pkg/controller/statefulset/stateful_set_control.go +++ b/pkg/controller/statefulset/stateful_set_control.go @@ -373,14 +373,7 @@ func (ssc *defaultStatefulSetControl) updateStatefulSet( status.CollisionCount = utilpointer.Int32Ptr(collisionCount) status.LabelSelector = selector.String() - reserveOrdinals := sets.NewInt(set.Spec.ReserveOrdinals...) - replicaCount := 0 - for realReplicaCount := 0; realReplicaCount < int(*set.Spec.Replicas); replicaCount++ { - if reserveOrdinals.Has(replicaCount) { - continue - } - realReplicaCount++ - } + replicaCount, reserveOrdinals := getStatefulSetReplicasRange(set) // slice that will contain all Pods such that 0 <= getOrdinal(pod) < replicaCount and not in reserveOrdinals replicas := make([]*v1.Pod, replicaCount) // slice that will contain all Pods such that replicaCount <= getOrdinal(pod) or in reserveOrdinals diff --git a/pkg/controller/statefulset/stateful_set_utils.go b/pkg/controller/statefulset/stateful_set_utils.go index 2db97e4f22..69149c1dab 100644 --- a/pkg/controller/statefulset/stateful_set_utils.go +++ b/pkg/controller/statefulset/stateful_set_utils.go @@ -30,6 +30,7 @@ import ( v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/strategicpatch" "k8s.io/client-go/kubernetes/scheme" "k8s.io/klog/v2" @@ -142,6 +143,8 @@ func getPersistentVolumeClaimRetentionPolicy(set *appsv1beta1.StatefulSet) appsv // PVC deletion policy for the StatefulSet. func claimOwnerMatchesSetAndPod(claim *v1.PersistentVolumeClaim, set *appsv1beta1.StatefulSet, pod *v1.Pod) bool { policy := getPersistentVolumeClaimRetentionPolicy(set) + replicaCount, reserveOrdinals := getStatefulSetReplicasRange(set) + ord := getOrdinal(pod) const retain = appsv1beta1.RetainPersistentVolumeClaimRetentionPolicyType const delete = appsv1beta1.DeletePersistentVolumeClaimRetentionPolicyType switch { @@ -162,12 +165,12 @@ func claimOwnerMatchesSetAndPod(claim *v1.PersistentVolumeClaim, set *appsv1beta if hasOwnerRef(claim, set) { return false } - podScaledDown := getOrdinal(pod) >= int(*set.Spec.Replicas) + podScaledDown := ord >= replicaCount || reserveOrdinals.Has(ord) if podScaledDown != hasOwnerRef(claim, pod) { return false } case policy.WhenScaled == delete && policy.WhenDeleted == delete: - podScaledDown := getOrdinal(pod) >= int(*set.Spec.Replicas) + podScaledDown := ord >= replicaCount || reserveOrdinals.Has(ord) // If a pod is scaled down, there should be no set ref and a pod ref; // if the pod is not scaled down it's the other way around. if podScaledDown == hasOwnerRef(claim, set) { @@ -203,6 +206,8 @@ func updateClaimOwnerRefForSetAndPod(claim *v1.PersistentVolumeClaim, set *appsv updateMeta(&podMeta, "Pod") setMeta := set.TypeMeta updateMeta(&setMeta, "StatefulSet") + replicaCount, reserveOrdinals := getStatefulSetReplicasRange(set) + ord := getOrdinal(pod) policy := getPersistentVolumeClaimRetentionPolicy(set) const retain = appsv1beta1.RetainPersistentVolumeClaimRetentionPolicyType const delete = appsv1beta1.DeletePersistentVolumeClaimRetentionPolicyType @@ -218,7 +223,7 @@ func updateClaimOwnerRefForSetAndPod(claim *v1.PersistentVolumeClaim, set *appsv needsUpdate = removeOwnerRef(claim, pod) || needsUpdate case policy.WhenScaled == delete && policy.WhenDeleted == retain: needsUpdate = removeOwnerRef(claim, set) || needsUpdate - podScaledDown := getOrdinal(pod) >= int(*set.Spec.Replicas) + podScaledDown := ord >= replicaCount || reserveOrdinals.Has(ord) if podScaledDown { needsUpdate = setOwnerRef(claim, pod, &podMeta) || needsUpdate } @@ -226,7 +231,7 @@ func updateClaimOwnerRefForSetAndPod(claim *v1.PersistentVolumeClaim, set *appsv needsUpdate = removeOwnerRef(claim, pod) || needsUpdate } case policy.WhenScaled == delete && policy.WhenDeleted == delete: - podScaledDown := getOrdinal(pod) >= int(*set.Spec.Replicas) + podScaledDown := ord >= replicaCount || reserveOrdinals.Has(ord) if podScaledDown { needsUpdate = removeOwnerRef(claim, set) || needsUpdate needsUpdate = setOwnerRef(claim, pod, &podMeta) || needsUpdate @@ -693,3 +698,29 @@ func decreaseAndCheckMaxUnavailable(maxUnavailable *int) bool { *maxUnavailable = val return val <= 0 } + +// return parameters is replicaCount and reserveOrdinals, and they are used to support reserveOrdinals scenarios. +// When configured as follows: +/* + apiVersion: apps.kruise.io/v1beta1 + kind: StatefulSet + spec: + # ... + replicas: 4 + reserveOrdinals: + - 1 + - 3 +*/ +// return replicaCount=6, reserveOrdinals={1, 3} + +func getStatefulSetReplicasRange(set *appsv1beta1.StatefulSet) (int, sets.Int) { + reserveOrdinals := sets.NewInt(set.Spec.ReserveOrdinals...) + replicaCount := 0 + for realReplicaCount := 0; realReplicaCount < int(*set.Spec.Replicas); replicaCount++ { + if reserveOrdinals.Has(replicaCount) { + continue + } + realReplicaCount++ + } + return replicaCount, reserveOrdinals +} diff --git a/test/e2e/apps/statefulset.go b/test/e2e/apps/statefulset.go index fe4cf78885..76c0ec0135 100644 --- a/test/e2e/apps/statefulset.go +++ b/test/e2e/apps/statefulset.go @@ -1299,6 +1299,32 @@ var _ = SIGDescribe("StatefulSet", func() { framework.ExpectNoError(err) }) + ginkgo.It("should delete PVCs with a OnScaledown policy and reserveOrdinals=[0,1]", func() { + if framework.SkipIfNoDefaultStorageClass(c) { + return + } + ginkgo.By("Creating statefulset " + ssName + " in namespace " + ns) + *(ss.Spec.Replicas) = 3 + ss.Spec.PersistentVolumeClaimRetentionPolicy = &appsv1beta1.StatefulSetPersistentVolumeClaimRetentionPolicy{ + WhenScaled: appsv1beta1.DeletePersistentVolumeClaimRetentionPolicyType, + } + ss.Spec.ReserveOrdinals = []int{0, 1} + _, err := kc.AppsV1beta1().StatefulSets(ns).Create(context.TODO(), ss, metav1.CreateOptions{}) + framework.ExpectNoError(err) + + ginkgo.By("Confirming all 3 PVCs exist") + err = verifyStatefulSetPVCsExist(c, ss, []int{2, 3, 4}) + framework.ExpectNoError(err) + + ginkgo.By("Scaling stateful set " + ss.Name + " to one replica") + ss, err = framework.NewStatefulSetTester(c, kc).Scale(ss, 1) + framework.ExpectNoError(err) + + ginkgo.By("Verifying all but one PVC deleted") + err = verifyStatefulSetPVCsExist(c, ss, []int{2}) + framework.ExpectNoError(err) + }) + ginkgo.It("should delete PVCs after adopting pod (WhenDeleted)", func() { if framework.SkipIfNoDefaultStorageClass(c) { return