Skip to content

Commit 7c88741

Browse files
authored
Add validation for revisionHistoryLimit in sts to prevent negative value (kubernetes#129017)
* Add validation for revisionHistoryLimit in sts to prevent negative value * Add unit tests to verify warning messages
1 parent 4114a9b commit 7c88741

File tree

3 files changed

+48
-6
lines changed

3 files changed

+48
-6
lines changed

pkg/apis/apps/validation/validation.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -182,15 +182,16 @@ func ValidateStatefulSetUpdate(statefulSet, oldStatefulSet *apps.StatefulSet, op
182182
// statefulset updates aren't super common and general updates are likely to be touching spec, so we'll do this
183183
// deep copy right away. This avoids mutating our inputs
184184
newStatefulSetClone := statefulSet.DeepCopy()
185-
newStatefulSetClone.Spec.Replicas = oldStatefulSet.Spec.Replicas // +k8s:verify-mutation:reason=clone
186-
newStatefulSetClone.Spec.Template = oldStatefulSet.Spec.Template // +k8s:verify-mutation:reason=clone
187-
newStatefulSetClone.Spec.UpdateStrategy = oldStatefulSet.Spec.UpdateStrategy // +k8s:verify-mutation:reason=clone
188-
newStatefulSetClone.Spec.MinReadySeconds = oldStatefulSet.Spec.MinReadySeconds // +k8s:verify-mutation:reason=clone
189-
newStatefulSetClone.Spec.Ordinals = oldStatefulSet.Spec.Ordinals // +k8s:verify-mutation:reason=clone
185+
newStatefulSetClone.Spec.Replicas = oldStatefulSet.Spec.Replicas // +k8s:verify-mutation:reason=clone
186+
newStatefulSetClone.Spec.Template = oldStatefulSet.Spec.Template // +k8s:verify-mutation:reason=clone
187+
newStatefulSetClone.Spec.UpdateStrategy = oldStatefulSet.Spec.UpdateStrategy // +k8s:verify-mutation:reason=clone
188+
newStatefulSetClone.Spec.MinReadySeconds = oldStatefulSet.Spec.MinReadySeconds // +k8s:verify-mutation:reason=clone
189+
newStatefulSetClone.Spec.Ordinals = oldStatefulSet.Spec.Ordinals // +k8s:verify-mutation:reason=clone
190+
newStatefulSetClone.Spec.RevisionHistoryLimit = oldStatefulSet.Spec.RevisionHistoryLimit // +k8s:verify-mutation:reason=clone
190191

191192
newStatefulSetClone.Spec.PersistentVolumeClaimRetentionPolicy = oldStatefulSet.Spec.PersistentVolumeClaimRetentionPolicy // +k8s:verify-mutation:reason=clone
192193
if !apiequality.Semantic.DeepEqual(newStatefulSetClone.Spec, oldStatefulSet.Spec) {
193-
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), "updates to statefulset spec for fields other than 'replicas', 'ordinals', 'template', 'updateStrategy', 'persistentVolumeClaimRetentionPolicy' and 'minReadySeconds' are forbidden"))
194+
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), "updates to statefulset spec for fields other than 'replicas', 'ordinals', 'template', 'updateStrategy', 'revisionHistoryLimit', 'persistentVolumeClaimRetentionPolicy' and 'minReadySeconds' are forbidden"))
194195
}
195196

196197
return allErrs

pkg/registry/apps/statefulset/strategy.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,10 @@ func (statefulSetStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Obj
150150
for i, pvc := range newStatefulSet.Spec.VolumeClaimTemplates {
151151
warnings = append(warnings, pvcutil.GetWarningsForPersistentVolumeClaimSpec(field.NewPath("spec", "volumeClaimTemplates").Index(i), pvc.Spec)...)
152152
}
153+
154+
if newStatefulSet.Spec.RevisionHistoryLimit != nil && *newStatefulSet.Spec.RevisionHistoryLimit < 0 {
155+
warnings = append(warnings, "spec.revisionHistoryLimit: a negative value retains all historical revisions; a value >= 0 is recommended")
156+
}
153157
return warnings
154158
}
155159

@@ -182,6 +186,9 @@ func (statefulSetStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtim
182186
for i, pvc := range newStatefulSet.Spec.VolumeClaimTemplates {
183187
warnings = append(warnings, pvcutil.GetWarningsForPersistentVolumeClaimSpec(field.NewPath("spec", "volumeClaimTemplates").Index(i).Child("Spec"), pvc.Spec)...)
184188
}
189+
if newStatefulSet.Spec.RevisionHistoryLimit != nil && *newStatefulSet.Spec.RevisionHistoryLimit < 0 {
190+
warnings = append(warnings, "spec.revisionHistoryLimit: a negative value retains all historical revisions; a value >= 0 is recommended")
191+
}
185192

186193
return warnings
187194
}

pkg/registry/apps/statefulset/strategy_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"testing"
2121

2222
"github.com/google/go-cmp/cmp"
23+
2324
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2425
"k8s.io/apimachinery/pkg/util/intstr"
2526
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
@@ -28,6 +29,7 @@ import (
2829
"k8s.io/kubernetes/pkg/apis/apps"
2930
api "k8s.io/kubernetes/pkg/apis/core"
3031
"k8s.io/kubernetes/pkg/features"
32+
"k8s.io/utils/ptr"
3133
)
3234

3335
func TestStatefulSetStrategy(t *testing.T) {
@@ -137,6 +139,38 @@ func TestStatefulSetStrategy(t *testing.T) {
137139
}
138140
})
139141

142+
t.Run("StatefulSet revisionHistoryLimit field validations on creation and updation", func(t *testing.T) {
143+
// Test creation
144+
oldSts := &apps.StatefulSet{
145+
ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
146+
Spec: apps.StatefulSetSpec{
147+
PodManagementPolicy: apps.OrderedReadyPodManagement,
148+
Selector: &metav1.LabelSelector{MatchLabels: validSelector},
149+
Template: validPodTemplate.Template,
150+
UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType},
151+
RevisionHistoryLimit: ptr.To(int32(-2)),
152+
},
153+
}
154+
155+
warnings := Strategy.WarningsOnCreate(ctx, oldSts)
156+
if len(warnings) != 1 {
157+
t.Errorf("expected one warning got %v", warnings)
158+
}
159+
if warnings[0] != "spec.revisionHistoryLimit: a negative value retains all historical revisions; a value >= 0 is recommended" {
160+
t.Errorf("unexpected warning: %v", warnings)
161+
}
162+
oldSts.Spec.RevisionHistoryLimit = ptr.To(int32(2))
163+
newSts := oldSts.DeepCopy()
164+
newSts.Spec.RevisionHistoryLimit = ptr.To(int32(-2))
165+
warnings = Strategy.WarningsOnUpdate(ctx, newSts, oldSts)
166+
if len(warnings) != 1 {
167+
t.Errorf("expected one warning got %v", warnings)
168+
}
169+
if warnings[0] != "spec.revisionHistoryLimit: a negative value retains all historical revisions; a value >= 0 is recommended" {
170+
t.Errorf("unexpected warning: %v", warnings)
171+
}
172+
})
173+
140174
validPs = &apps.StatefulSet{
141175
ObjectMeta: metav1.ObjectMeta{Name: ps.Name, Namespace: ps.Namespace, ResourceVersion: "1", Generation: 1},
142176
Spec: apps.StatefulSetSpec{

0 commit comments

Comments
 (0)