Skip to content

Commit ebdca53

Browse files
[sample-apiserver] Fix: Use Correct Effective Version for kube (kubernetes#125941)
* Fix slice copy of VersionedSpecs in FeatureGate. Signed-off-by: Siyuan Zhang <[email protected]> * Update wardle to kube version mapping Signed-off-by: Siyuan Zhang <[email protected]> Signed-off-by: Feilian Xie <[email protected]> Co-authored-by: Feilian Xie <[email protected]> * Add cap to wardleEmulationVersionToKubeEmulationVersion. Signed-off-by: Siyuan Zhang <[email protected]> * Add integration test for default BanFlunder behavior in version 1.2 without Wardle feature gate. Signed-off-by: Siyuan Zhang <[email protected]> --------- Signed-off-by: Siyuan Zhang <[email protected]> Signed-off-by: Feilian Xie <[email protected]> Co-authored-by: Siyuan Zhang <[email protected]>
1 parent 86e2e26 commit ebdca53

File tree

5 files changed

+144
-50
lines changed

5 files changed

+144
-50
lines changed

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

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,6 @@ type FeatureGate interface {
115115
// set on the copy without mutating the original. This is useful for validating
116116
// config against potential feature gate changes before committing those changes.
117117
DeepCopy() MutableVersionedFeatureGate
118-
// CopyKnownFeatures returns a partial copy of the FeatureGate object, with all the known features and overrides.
119-
// This is useful for creating a new instance of feature gate without inheriting all the enabled configurations of the base feature gate.
120-
CopyKnownFeatures() MutableVersionedFeatureGate
121118
// Validate checks if the flag gates are valid at the emulated version.
122119
Validate() []error
123120
}
@@ -189,6 +186,10 @@ type MutableVersionedFeatureGate interface {
189186
ExplicitlySet(name Feature) bool
190187
// ResetFeatureValueToDefault resets the value of the feature back to the default value.
191188
ResetFeatureValueToDefault(name Feature) error
189+
// DeepCopyAndReset copies all the registered features of the FeatureGate object, with all the known features and overrides,
190+
// and resets all the enabled status of the new feature gate.
191+
// This is useful for creating a new instance of feature gate without inheriting all the enabled configurations of the base feature gate.
192+
DeepCopyAndReset() MutableVersionedFeatureGate
192193
}
193194

194195
// featureGate implements FeatureGate as well as pflag.Value for flag parsing.
@@ -423,10 +424,7 @@ func (f *featureGate) AddVersioned(features map[Feature]VersionedSpecs) error {
423424
}
424425

425426
// Copy existing state
426-
known := map[Feature]VersionedSpecs{}
427-
for k, v := range f.known.Load().(map[Feature]VersionedSpecs) {
428-
known[k] = v
429-
}
427+
known := f.GetAllVersioned()
430428

431429
for name, specs := range features {
432430
sort.Sort(specs)
@@ -458,11 +456,8 @@ func (f *featureGate) OverrideDefaultAtVersion(name Feature, override bool, ver
458456
return fmt.Errorf("cannot override default for feature %q: gates already added to a flag set", name)
459457
}
460458

461-
known := map[Feature]VersionedSpecs{}
462-
for k, v := range f.known.Load().(map[Feature]VersionedSpecs) {
463-
sort.Sort(v)
464-
known[k] = v
465-
}
459+
// Copy existing state
460+
known := f.GetAllVersioned()
466461

467462
specs, ok := known[name]
468463
if !ok {
@@ -509,7 +504,9 @@ func (f *featureGate) GetAll() map[Feature]FeatureSpec {
509504
func (f *featureGate) GetAllVersioned() map[Feature]VersionedSpecs {
510505
retval := map[Feature]VersionedSpecs{}
511506
for k, v := range f.known.Load().(map[Feature]VersionedSpecs) {
512-
retval[k] = v
507+
vCopy := make([]FeatureSpec, len(v))
508+
_ = copy(vCopy, v)
509+
retval[k] = vCopy
513510
}
514511
return retval
515512
}
@@ -660,9 +657,10 @@ func (f *featureGate) KnownFeatures() []string {
660657
return known
661658
}
662659

663-
// CopyKnownFeatures returns a partial copy of the FeatureGate object, with all the known features and overrides.
660+
// DeepCopyAndReset copies all the registered features of the FeatureGate object, with all the known features and overrides,
661+
// and resets all the enabled status of the new feature gate.
664662
// This is useful for creating a new instance of feature gate without inheriting all the enabled configurations of the base feature gate.
665-
func (f *featureGate) CopyKnownFeatures() MutableVersionedFeatureGate {
663+
func (f *featureGate) DeepCopyAndReset() MutableVersionedFeatureGate {
666664
fg := NewVersionedFeatureGate(f.EmulationVersion())
667665
known := f.GetAllVersioned()
668666
fg.known.Store(known)
@@ -676,10 +674,7 @@ func (f *featureGate) DeepCopy() MutableVersionedFeatureGate {
676674
f.lock.Lock()
677675
defer f.lock.Unlock()
678676
// Copy existing state.
679-
known := map[Feature]VersionedSpecs{}
680-
for k, v := range f.known.Load().(map[Feature]VersionedSpecs) {
681-
known[k] = v
682-
}
677+
known := f.GetAllVersioned()
683678
enabled := map[Feature]bool{}
684679
for k, v := range f.enabled.Load().(map[Feature]bool) {
685680
enabled[k] = v

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

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -600,7 +600,7 @@ func TestFeatureGateOverrideDefault(t *testing.T) {
600600
f := NewFeatureGate()
601601
require.NoError(t, f.Add(map[Feature]FeatureSpec{"TestFeature": {Default: false}}))
602602
require.NoError(t, f.OverrideDefault("TestFeature", true))
603-
fcopy := f.CopyKnownFeatures()
603+
fcopy := f.DeepCopyAndReset()
604604
if !f.Enabled("TestFeature") {
605605
t.Error("TestFeature should be enabled by override")
606606
}
@@ -609,6 +609,19 @@ func TestFeatureGateOverrideDefault(t *testing.T) {
609609
}
610610
})
611611

612+
t.Run("overrides are not passed over after CopyKnownFeatures", func(t *testing.T) {
613+
f := NewFeatureGate()
614+
require.NoError(t, f.Add(map[Feature]FeatureSpec{"TestFeature": {Default: false}}))
615+
fcopy := f.DeepCopyAndReset()
616+
require.NoError(t, f.OverrideDefault("TestFeature", true))
617+
if !f.Enabled("TestFeature") {
618+
t.Error("TestFeature should be enabled by override")
619+
}
620+
if fcopy.Enabled("TestFeature") {
621+
t.Error("default override should not be passed over after CopyKnownFeatures")
622+
}
623+
})
624+
612625
t.Run("reflected in known features", func(t *testing.T) {
613626
f := NewFeatureGate()
614627
if err := f.Add(map[Feature]FeatureSpec{"TestFeature": {
@@ -1351,6 +1364,34 @@ func TestVersionedFeatureGateOverrideDefault(t *testing.T) {
13511364
}
13521365
})
13531366

1367+
t.Run("overrides are not passed over after deep copies", func(t *testing.T) {
1368+
f := NewVersionedFeatureGate(version.MustParse("1.29"))
1369+
require.NoError(t, f.SetEmulationVersion(version.MustParse("1.28")))
1370+
if err := f.AddVersioned(map[Feature]VersionedSpecs{
1371+
"TestFeature": {
1372+
{Version: version.MustParse("1.28"), Default: false},
1373+
{Version: version.MustParse("1.29"), Default: true},
1374+
},
1375+
}); err != nil {
1376+
t.Fatal(err)
1377+
}
1378+
assert.False(t, f.Enabled("TestFeature"))
1379+
1380+
fcopy := f.DeepCopy()
1381+
require.NoError(t, f.OverrideDefault("TestFeature", true))
1382+
require.NoError(t, f.OverrideDefaultAtVersion("TestFeature", false, version.MustParse("1.29")))
1383+
assert.True(t, f.Enabled("TestFeature"))
1384+
assert.False(t, fcopy.Enabled("TestFeature"))
1385+
1386+
require.NoError(t, f.SetEmulationVersion(version.MustParse("1.29")))
1387+
assert.False(t, f.Enabled("TestFeature"))
1388+
assert.False(t, fcopy.Enabled("TestFeature"))
1389+
1390+
require.NoError(t, fcopy.SetEmulationVersion(version.MustParse("1.29")))
1391+
assert.False(t, f.Enabled("TestFeature"))
1392+
assert.True(t, fcopy.Enabled("TestFeature"))
1393+
})
1394+
13541395
t.Run("reflected in known features", func(t *testing.T) {
13551396
f := NewVersionedFeatureGate(version.MustParse("1.29"))
13561397
require.NoError(t, f.SetEmulationVersion(version.MustParse("1.28")))
@@ -1536,7 +1577,7 @@ func TestCopyKnownFeatures(t *testing.T) {
15361577
require.NoError(t, f.Add(map[Feature]FeatureSpec{"FeatureA": {Default: false}, "FeatureB": {Default: false}}))
15371578
require.NoError(t, f.Set("FeatureA=true"))
15381579
require.NoError(t, f.OverrideDefault("FeatureB", true))
1539-
fcopy := f.CopyKnownFeatures()
1580+
fcopy := f.DeepCopyAndReset()
15401581
require.NoError(t, f.Add(map[Feature]FeatureSpec{"FeatureC": {Default: false}}))
15411582

15421583
assert.True(t, f.Enabled("FeatureA"))

staging/src/k8s.io/sample-apiserver/pkg/cmd/server/start.go

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import (
3636
utilfeature "k8s.io/apiserver/pkg/util/feature"
3737
utilversion "k8s.io/apiserver/pkg/util/version"
3838
"k8s.io/component-base/featuregate"
39+
baseversion "k8s.io/component-base/version"
3940
"k8s.io/sample-apiserver/pkg/admission/plugin/banflunder"
4041
"k8s.io/sample-apiserver/pkg/admission/wardleinitializer"
4142
"k8s.io/sample-apiserver/pkg/apis/wardle/v1alpha1"
@@ -59,14 +60,18 @@ type WardleServerOptions struct {
5960
AlternateDNS []string
6061
}
6162

62-
func wardleEmulationVersionToKubeEmulationVersion(ver *version.Version) *version.Version {
63+
func WardleVersionToKubeVersion(ver *version.Version) *version.Version {
6364
if ver.Major() != 1 {
6465
return nil
6566
}
6667
kubeVer := utilversion.DefaultKubeEffectiveVersion().BinaryVersion()
67-
// "1.1" maps to kubeVer
68-
offset := int(ver.Minor()) - 1
69-
return kubeVer.OffsetMinor(offset)
68+
// "1.2" maps to kubeVer
69+
offset := int(ver.Minor()) - 2
70+
mappedVer := kubeVer.OffsetMinor(offset)
71+
if mappedVer.GreaterThan(kubeVer) {
72+
return kubeVer
73+
}
74+
return mappedVer
7075
}
7176

7277
// NewWardleServerOptions returns a new WardleServerOptions
@@ -112,19 +117,44 @@ func NewCommandStartWardleServer(ctx context.Context, defaults *WardleServerOpti
112117
flags := cmd.Flags()
113118
o.RecommendedOptions.AddFlags(flags)
114119

115-
wardleEffectiveVersion := utilversion.NewEffectiveVersion("1.2")
116-
wardleFeatureGate := utilfeature.DefaultFeatureGate.CopyKnownFeatures()
120+
// The following lines demonstrate how to configure version compatibility and feature gates
121+
// for the "Wardle" component, as an example of KEP-4330.
122+
123+
// Create an effective version object for the "Wardle" component.
124+
// This initializes the binary version, the emulation version and the minimum compatibility version.
125+
//
126+
// Note:
127+
// - The binary version represents the actual version of the running source code.
128+
// - The emulation version is the version whose capabilities are being emulated by the binary.
129+
// - The minimum compatibility version specifies the minimum version that the component remains compatible with.
130+
//
131+
// Refer to KEP-4330 for more details: https://github.com/kubernetes/enhancements/blob/master/keps/sig-architecture/4330-compatibility-versions
132+
defaultWardleVersion := "1.2"
133+
// Register the "Wardle" component with the global component registry,
134+
// associating it with its effective version and feature gate configuration.
135+
// Will skip if the component has been registered, like in the integration test.
136+
_, wardleFeatureGate := utilversion.DefaultComponentGlobalsRegistry.ComponentGlobalsOrRegister(
137+
apiserver.WardleComponentName, utilversion.NewEffectiveVersion(defaultWardleVersion),
138+
featuregate.NewVersionedFeatureGate(version.MustParse(defaultWardleVersion)))
139+
140+
// Add versioned feature specifications for the "BanFlunder" feature.
141+
// These specifications, together with the effective version, determine if the feature is enabled.
117142
utilruntime.Must(wardleFeatureGate.AddVersioned(map[featuregate.Feature]featuregate.VersionedSpecs{
118143
"BanFlunder": {
119-
{Version: version.MustParse("1.2"), Default: true, PreRelease: featuregate.GA},
120-
{Version: version.MustParse("1.1"), Default: false, PreRelease: featuregate.Beta},
144+
{Version: version.MustParse("1.2"), Default: true, PreRelease: featuregate.GA, LockToDefault: true},
145+
{Version: version.MustParse("1.1"), Default: true, PreRelease: featuregate.Beta},
121146
{Version: version.MustParse("1.0"), Default: false, PreRelease: featuregate.Alpha},
122147
},
123148
}))
124-
utilruntime.Must(utilversion.DefaultComponentGlobalsRegistry.Register(apiserver.WardleComponentName, wardleEffectiveVersion, wardleFeatureGate))
125-
_, _ = utilversion.DefaultComponentGlobalsRegistry.ComponentGlobalsOrRegister(
126-
utilversion.DefaultKubeComponent, utilversion.DefaultKubeEffectiveVersion(), utilfeature.DefaultMutableFeatureGate)
127-
utilruntime.Must(utilversion.DefaultComponentGlobalsRegistry.SetEmulationVersionMapping(apiserver.WardleComponentName, utilversion.DefaultKubeComponent, wardleEmulationVersionToKubeEmulationVersion))
149+
150+
// Register the default kube component if not already present in the global registry.
151+
_, _ = utilversion.DefaultComponentGlobalsRegistry.ComponentGlobalsOrRegister(utilversion.DefaultKubeComponent,
152+
utilversion.NewEffectiveVersion(baseversion.DefaultKubeBinaryVersion), utilfeature.DefaultMutableFeatureGate)
153+
154+
// Set the emulation version mapping from the "Wardle" component to the kube component.
155+
// This ensures that the emulation version of the latter is determined by the emulation version of the former.
156+
utilruntime.Must(utilversion.DefaultComponentGlobalsRegistry.SetEmulationVersionMapping(apiserver.WardleComponentName, utilversion.DefaultKubeComponent, WardleVersionToKubeVersion))
157+
128158
utilversion.DefaultComponentGlobalsRegistry.AddFlags(flags)
129159

130160
return cmd
@@ -177,7 +207,7 @@ func (o *WardleServerOptions) Config() (*apiserver.Config, error) {
177207
serverConfig.OpenAPIV3Config.Info.Title = "Wardle"
178208
serverConfig.OpenAPIV3Config.Info.Version = "0.1"
179209

180-
serverConfig.FeatureGate = utilversion.DefaultComponentGlobalsRegistry.FeatureGateFor(apiserver.WardleComponentName)
210+
serverConfig.FeatureGate = utilversion.DefaultComponentGlobalsRegistry.FeatureGateFor(utilversion.DefaultKubeComponent)
181211
serverConfig.EffectiveVersion = utilversion.DefaultComponentGlobalsRegistry.EffectiveVersionFor(apiserver.WardleComponentName)
182212

183213
if err := o.RecommendedOptions.ApplyTo(serverConfig); err != nil {

staging/src/k8s.io/sample-apiserver/pkg/cmd/server/start_test.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,23 @@ func TestWardleEmulationVersionToKubeEmulationVersion(t *testing.T) {
3535
}{
3636
{
3737
desc: "same version as than kube binary",
38-
wardleEmulationVer: version.MajorMinor(1, 1),
38+
wardleEmulationVer: version.MajorMinor(1, 2),
3939
expectedKubeEmulationVer: defaultKubeEffectiveVersion.BinaryVersion(),
4040
},
4141
{
42-
desc: "1 version higher than kube binary",
43-
wardleEmulationVer: version.MajorMinor(1, 2),
44-
expectedKubeEmulationVer: defaultKubeEffectiveVersion.BinaryVersion().OffsetMinor(1),
42+
desc: "1 version lower than kube binary",
43+
wardleEmulationVer: version.MajorMinor(1, 1),
44+
expectedKubeEmulationVer: defaultKubeEffectiveVersion.BinaryVersion().OffsetMinor(-1),
45+
},
46+
{
47+
desc: "2 versions lower than kube binary",
48+
wardleEmulationVer: version.MajorMinor(1, 0),
49+
expectedKubeEmulationVer: defaultKubeEffectiveVersion.BinaryVersion().OffsetMinor(-2),
50+
},
51+
{
52+
desc: "capped at kube binary",
53+
wardleEmulationVer: version.MajorMinor(1, 3),
54+
expectedKubeEmulationVer: defaultKubeEffectiveVersion.BinaryVersion(),
4555
},
4656
{
4757
desc: "no mapping",
@@ -51,7 +61,7 @@ func TestWardleEmulationVersionToKubeEmulationVersion(t *testing.T) {
5161

5262
for _, tc := range testCases {
5363
t.Run(tc.desc, func(t *testing.T) {
54-
mappedKubeEmulationVer := wardleEmulationVersionToKubeEmulationVersion(tc.wardleEmulationVer)
64+
mappedKubeEmulationVer := WardleVersionToKubeVersion(tc.wardleEmulationVer)
5565
assert.True(t, mappedKubeEmulationVer.EqualTo(tc.expectedKubeEmulationVer))
5666
})
5767
}

0 commit comments

Comments
 (0)