Skip to content

Commit 00dab9d

Browse files
Add Validation to versioned feature specs.
Co-authored-by: Jordan Liggitt <[email protected]> Co-authored-by: Siyuan Zhang <[email protected]> Signed-off-by: Siyuan Zhang <[email protected]>
1 parent 107be8f commit 00dab9d

File tree

10 files changed

+261
-32
lines changed

10 files changed

+261
-32
lines changed

cmd/kube-controller-manager/app/options/options_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -747,8 +747,8 @@ func TestEmulatedVersion(t *testing.T) {
747747
fg := featuregate.NewVersionedFeatureGate(version.MustParse("1.32"))
748748
utilruntime.Must(fg.AddVersioned(map[featuregate.Feature]featuregate.VersionedSpecs{
749749
"kubeA": {
750-
{Version: version.MustParse("1.32"), Default: true, LockToDefault: true, PreRelease: featuregate.GA},
751750
{Version: version.MustParse("1.30"), Default: false, PreRelease: featuregate.Beta},
751+
{Version: version.MustParse("1.32"), Default: true, LockToDefault: true, PreRelease: featuregate.GA},
752752
},
753753
"kubeB": {
754754
{Version: version.MustParse("1.31"), Default: false, PreRelease: featuregate.Alpha},

cmd/kube-scheduler/app/server_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929

3030
"github.com/google/go-cmp/cmp"
3131
"github.com/spf13/pflag"
32+
3233
v1 "k8s.io/api/core/v1"
3334
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3435
"k8s.io/apimachinery/pkg/runtime"
@@ -446,8 +447,8 @@ leaderElection:
446447
fg := feature.DefaultFeatureGate.DeepCopy()
447448
utilruntime.Must(fg.AddVersioned(map[featuregate.Feature]featuregate.VersionedSpecs{
448449
"kubeA": {
449-
{Version: version.MustParse("1.32"), Default: true, LockToDefault: true, PreRelease: featuregate.GA},
450450
{Version: version.MustParse("1.30"), Default: false, PreRelease: featuregate.Beta},
451+
{Version: version.MustParse("1.32"), Default: true, LockToDefault: true, PreRelease: featuregate.GA},
451452
},
452453
"kubeB": {
453454
{Version: version.MustParse("1.31"), Default: false, PreRelease: featuregate.Alpha},

pkg/features/versioned_kube_features.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,22 @@ import (
3737
// Entries are alphabetized.
3838
var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate.VersionedSpecs{
3939
AllowDNSOnlyNodeCSR: {
40+
{Version: version.MustParse("1.0"), Default: true, PreRelease: featuregate.GA},
4041
{Version: version.MustParse("1.31"), Default: false, PreRelease: featuregate.Deprecated},
4142
},
4243

4344
AllowInsecureKubeletCertificateSigningRequests: {
45+
{Version: version.MustParse("1.0"), Default: true, PreRelease: featuregate.GA},
4446
{Version: version.MustParse("1.31"), Default: false, PreRelease: featuregate.Deprecated},
4547
},
4648

4749
AllowOverwriteTerminationGracePeriodSeconds: {
50+
{Version: version.MustParse("1.0"), Default: true, PreRelease: featuregate.GA},
4851
{Version: version.MustParse("1.32"), Default: false, PreRelease: featuregate.Deprecated},
4952
},
5053

5154
AllowServiceLBStatusOnNonLB: {
55+
{Version: version.MustParse("1.0"), Default: true, PreRelease: featuregate.GA},
5256
{Version: version.MustParse("1.29"), Default: false, PreRelease: featuregate.Deprecated},
5357
{Version: version.MustParse("1.32"), Default: false, PreRelease: featuregate.Deprecated, LockToDefault: true}, // remove in 1.35
5458
},
@@ -291,6 +295,7 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate
291295
},
292296

293297
genericfeatures.KMSv1: {
298+
{Version: version.MustParse("1.0"), Default: true, PreRelease: featuregate.GA},
294299
{Version: version.MustParse("1.28"), Default: true, PreRelease: featuregate.Deprecated},
295300
{Version: version.MustParse("1.29"), Default: false, PreRelease: featuregate.Deprecated},
296301
},
@@ -474,6 +479,7 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate
474479
},
475480

476481
KubeletRegistrationGetOnExistsOnly: {
482+
{Version: version.MustParse("1.0"), Default: true, PreRelease: featuregate.GA},
477483
{Version: version.MustParse("1.32"), Default: false, PreRelease: featuregate.Deprecated},
478484
},
479485

staging/src/k8s.io/apiserver/pkg/features/kube_features.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,7 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate
356356
},
357357

358358
KMSv1: {
359+
{Version: version.MustParse("1.0"), Default: true, PreRelease: featuregate.GA},
359360
{Version: version.MustParse("1.28"), Default: true, PreRelease: featuregate.Deprecated},
360361
{Version: version.MustParse("1.29"), Default: false, PreRelease: featuregate.Deprecated},
361362
},

staging/src/k8s.io/component-base/featuregate/feature_gate.go

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,6 @@ func (f *featureGate) unsafeSetFromMap(enabled map[Feature]bool, m map[string]bo
318318
// Copy existing state
319319
known := map[Feature]VersionedSpecs{}
320320
for k, v := range f.known.Load().(map[Feature]VersionedSpecs) {
321-
sort.Sort(v)
322321
known[k] = v
323322
}
324323

@@ -421,19 +420,52 @@ func (f *featureGate) AddVersioned(features map[Feature]VersionedSpecs) error {
421420
if f.closed {
422421
return fmt.Errorf("cannot add a feature gate after adding it to the flag set")
423422
}
424-
425423
// Copy existing state
426424
known := f.GetAllVersioned()
427425

428426
for name, specs := range features {
429-
sort.Sort(specs)
430427
if existingSpec, found := known[name]; found {
431-
sort.Sort(existingSpec)
432428
if reflect.DeepEqual(existingSpec, specs) {
433429
continue
434430
}
435431
return fmt.Errorf("feature gate %q with different spec already exists: %v", name, existingSpec)
436432
}
433+
// Validate new specs are well-formed
434+
var lastVersion *version.Version
435+
var wasBeta, wasGA, wasDeprecated bool
436+
for i, spec := range specs {
437+
if spec.Version == nil {
438+
return fmt.Errorf("feature %q did not provide a version", name)
439+
}
440+
if len(spec.Version.Components()) != 2 {
441+
return fmt.Errorf("feature %q specified patch version: %s", name, spec.Version.String())
442+
443+
}
444+
// gates that begin as deprecated must indicate their prior state
445+
if i == 0 && spec.PreRelease == Deprecated && spec.Version.Minor() != 0 {
446+
return fmt.Errorf("feature %q introduced as deprecated must provide a 1.0 entry indicating initial state", name)
447+
}
448+
if i > 0 {
449+
// versions must strictly increase
450+
if !lastVersion.LessThan(spec.Version) {
451+
return fmt.Errorf("feature %q lists version transitions in non-increasing order (%s <= %s)", name, spec.Version, lastVersion)
452+
}
453+
// stability must not regress from ga --> {beta,alpha} or beta --> alpha, and
454+
// Deprecated state must be the terminal state
455+
switch {
456+
case spec.PreRelease != Deprecated && wasDeprecated:
457+
return fmt.Errorf("deprecated feature %q must not resurrect from its terminal state", name)
458+
case spec.PreRelease == Alpha && (wasBeta || wasGA):
459+
return fmt.Errorf("feature %q regresses stability from more stable level to %s in %s", name, spec.PreRelease, spec.Version)
460+
case spec.PreRelease == Beta && wasGA:
461+
return fmt.Errorf("feature %q regresses stability from more stable level to %s in %s", name, spec.PreRelease, spec.Version)
462+
}
463+
}
464+
lastVersion = spec.Version
465+
wasBeta = wasBeta || spec.PreRelease == Beta
466+
wasGA = wasGA || spec.PreRelease == GA
467+
wasDeprecated = wasDeprecated || spec.PreRelease == Deprecated
468+
}
437469
known[name] = specs
438470
}
439471

0 commit comments

Comments
 (0)