k8s.io/apiserver@v0.31.1/pkg/util/version/registry_test.go (about) 1 /* 2 Copyright 2024 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 version 18 19 import ( 20 "fmt" 21 "strings" 22 "testing" 23 24 "github.com/spf13/pflag" 25 utilruntime "k8s.io/apimachinery/pkg/util/runtime" 26 "k8s.io/apimachinery/pkg/util/version" 27 "k8s.io/component-base/featuregate" 28 ) 29 30 const ( 31 testComponent = "test" 32 ) 33 34 func TestEffectiveVersionRegistry(t *testing.T) { 35 r := NewComponentGlobalsRegistry() 36 ver1 := NewEffectiveVersion("1.31") 37 ver2 := NewEffectiveVersion("1.28") 38 39 if r.EffectiveVersionFor(testComponent) != nil { 40 t.Fatalf("expected nil EffectiveVersion initially") 41 } 42 if err := r.Register(testComponent, ver1, nil); err != nil { 43 t.Fatalf("expected no error to register new component, but got err: %v", err) 44 } 45 if !r.EffectiveVersionFor(testComponent).EqualTo(ver1) { 46 t.Fatalf("expected EffectiveVersionFor to return the version registered") 47 } 48 // overwrite 49 if err := r.Register(testComponent, ver2, nil); err == nil { 50 t.Fatalf("expected error to register existing component when override is false") 51 } 52 if !r.EffectiveVersionFor(testComponent).EqualTo(ver1) { 53 t.Fatalf("expected EffectiveVersionFor to return the version overridden") 54 } 55 } 56 57 func testRegistry(t *testing.T) *componentGlobalsRegistry { 58 r := NewComponentGlobalsRegistry() 59 verKube := NewEffectiveVersion("1.31") 60 fgKube := featuregate.NewVersionedFeatureGate(version.MustParse("0.0")) 61 err := fgKube.AddVersioned(map[featuregate.Feature]featuregate.VersionedSpecs{ 62 "kubeA": { 63 {Version: version.MustParse("1.31"), Default: true, LockToDefault: true, PreRelease: featuregate.GA}, 64 {Version: version.MustParse("1.28"), Default: false, PreRelease: featuregate.Beta}, 65 {Version: version.MustParse("1.27"), Default: false, PreRelease: featuregate.Alpha}, 66 }, 67 "kubeB": { 68 {Version: version.MustParse("1.30"), Default: false, PreRelease: featuregate.Alpha}, 69 }, 70 "commonC": { 71 {Version: version.MustParse("1.29"), Default: true, PreRelease: featuregate.Beta}, 72 {Version: version.MustParse("1.27"), Default: false, PreRelease: featuregate.Alpha}, 73 }, 74 }) 75 if err != nil { 76 t.Fatal(err) 77 } 78 79 verTest := NewEffectiveVersion("2.8") 80 fgTest := featuregate.NewVersionedFeatureGate(version.MustParse("0.0")) 81 err = fgTest.AddVersioned(map[featuregate.Feature]featuregate.VersionedSpecs{ 82 "testA": { 83 {Version: version.MustParse("2.10"), Default: true, PreRelease: featuregate.GA}, 84 {Version: version.MustParse("2.8"), Default: false, PreRelease: featuregate.Beta}, 85 {Version: version.MustParse("2.7"), Default: false, PreRelease: featuregate.Alpha}, 86 }, 87 "testB": { 88 {Version: version.MustParse("2.9"), Default: false, PreRelease: featuregate.Alpha}, 89 }, 90 "commonC": { 91 {Version: version.MustParse("2.9"), Default: true, PreRelease: featuregate.Beta}, 92 {Version: version.MustParse("2.7"), Default: false, PreRelease: featuregate.Alpha}, 93 }, 94 }) 95 if err != nil { 96 t.Fatal(err) 97 } 98 utilruntime.Must(r.Register(DefaultKubeComponent, verKube, fgKube)) 99 utilruntime.Must(r.Register(testComponent, verTest, fgTest)) 100 return r 101 } 102 103 func TestVersionFlagOptions(t *testing.T) { 104 r := testRegistry(t) 105 emuVers := strings.Join(r.unsafeVersionFlagOptions(true), "\n") 106 expectedEmuVers := "kube=1.31..1.31 (default=1.31)\ntest=2.8..2.8 (default=2.8)" 107 if emuVers != expectedEmuVers { 108 t.Errorf("wanted emulation version flag options to be: %s, got %s", expectedEmuVers, emuVers) 109 } 110 minCompVers := strings.Join(r.unsafeVersionFlagOptions(false), "\n") 111 expectedMinCompVers := "kube=1.30..1.31 (default=1.30)\ntest=2.7..2.8 (default=2.7)" 112 if minCompVers != expectedMinCompVers { 113 t.Errorf("wanted min compatibility version flag options to be: %s, got %s", expectedMinCompVers, minCompVers) 114 } 115 } 116 117 func TestVersionFlagOptionsWithMapping(t *testing.T) { 118 r := testRegistry(t) 119 utilruntime.Must(r.SetEmulationVersionMapping(testComponent, DefaultKubeComponent, 120 func(from *version.Version) *version.Version { return from.OffsetMinor(3) })) 121 emuVers := strings.Join(r.unsafeVersionFlagOptions(true), "\n") 122 expectedEmuVers := "test=2.8..2.8 (default=2.8)" 123 if emuVers != expectedEmuVers { 124 t.Errorf("wanted emulation version flag options to be: %s, got %s", expectedEmuVers, emuVers) 125 } 126 minCompVers := strings.Join(r.unsafeVersionFlagOptions(false), "\n") 127 expectedMinCompVers := "kube=1.30..1.31 (default=1.30)\ntest=2.7..2.8 (default=2.7)" 128 if minCompVers != expectedMinCompVers { 129 t.Errorf("wanted min compatibility version flag options to be: %s, got %s", expectedMinCompVers, minCompVers) 130 } 131 } 132 133 func TestVersionedFeatureGateFlag(t *testing.T) { 134 r := testRegistry(t) 135 known := strings.Join(r.unsafeKnownFeatures(), "\n") 136 expectedKnown := "kube:AllAlpha=true|false (ALPHA - default=false)\n" + 137 "kube:AllBeta=true|false (BETA - default=false)\n" + 138 "kube:commonC=true|false (BETA - default=true)\n" + 139 "kube:kubeB=true|false (ALPHA - default=false)\n" + 140 "test:AllAlpha=true|false (ALPHA - default=false)\n" + 141 "test:AllBeta=true|false (BETA - default=false)\n" + 142 "test:commonC=true|false (ALPHA - default=false)\n" + 143 "test:testA=true|false (BETA - default=false)" 144 if known != expectedKnown { 145 t.Errorf("wanted min compatibility version flag options to be:\n%s, got:\n%s", expectedKnown, known) 146 } 147 } 148 149 func TestFlags(t *testing.T) { 150 tests := []struct { 151 name string 152 flags []string 153 parseError string 154 expectedKubeEmulationVersion string 155 expectedTestEmulationVersion string 156 expectedKubeFeatureValues map[featuregate.Feature]bool 157 expectedTestFeatureValues map[featuregate.Feature]bool 158 }{ 159 { 160 name: "setting kube emulation version", 161 flags: []string{"--emulated-version=kube=1.30"}, 162 expectedKubeEmulationVersion: "1.30", 163 }, 164 { 165 name: "setting kube emulation version twice", 166 flags: []string{ 167 "--emulated-version=kube=1.30", 168 "--emulated-version=kube=1.32", 169 }, 170 parseError: "duplicate version flag, kube=1.30 and kube=1.32", 171 }, 172 { 173 name: "prefix v ok", 174 flags: []string{"--emulated-version=kube=v1.30"}, 175 expectedKubeEmulationVersion: "1.30", 176 }, 177 { 178 name: "patch version not ok", 179 flags: []string{"--emulated-version=kube=1.30.2"}, 180 parseError: "patch version not allowed, got: kube=1.30.2", 181 }, 182 { 183 name: "setting test emulation version", 184 flags: []string{"--emulated-version=test=2.7"}, 185 expectedKubeEmulationVersion: "1.31", 186 expectedTestEmulationVersion: "2.7", 187 }, 188 { 189 name: "version missing component default to kube", 190 flags: []string{"--emulated-version=1.30"}, 191 expectedKubeEmulationVersion: "1.30", 192 }, 193 { 194 name: "version missing component default to kube with duplicate", 195 flags: []string{"--emulated-version=1.30", "--emulated-version=kube=1.30"}, 196 parseError: "duplicate version flag, kube=1.30 and kube=1.30", 197 }, 198 { 199 name: "version unregistered component", 200 flags: []string{"--emulated-version=test3=1.31"}, 201 parseError: "component not registered: test3", 202 }, 203 { 204 name: "invalid version", 205 flags: []string{"--emulated-version=test=1.foo"}, 206 parseError: "illegal version string \"1.foo\"", 207 }, 208 { 209 name: "setting test feature flag", 210 flags: []string{ 211 "--emulated-version=test=2.7", 212 "--feature-gates=test:testA=true", 213 }, 214 expectedKubeEmulationVersion: "1.31", 215 expectedTestEmulationVersion: "2.7", 216 expectedKubeFeatureValues: map[featuregate.Feature]bool{"kubeA": true, "kubeB": false, "commonC": true}, 217 expectedTestFeatureValues: map[featuregate.Feature]bool{"testA": true, "testB": false, "commonC": false}, 218 }, 219 { 220 name: "setting future test feature flag", 221 flags: []string{ 222 "--emulated-version=test=2.7", 223 "--feature-gates=test:testA=true,test:testB=true", 224 }, 225 parseError: "cannot set feature gate testB to true, feature is PreAlpha at emulated version 2.7", 226 }, 227 { 228 name: "setting kube feature flag", 229 flags: []string{ 230 "--emulated-version=test=2.7", 231 "--emulated-version=kube=1.30", 232 "--feature-gates=kubeB=false,test:commonC=true", 233 "--feature-gates=commonC=false,kubeB=true", 234 }, 235 expectedKubeEmulationVersion: "1.30", 236 expectedTestEmulationVersion: "2.7", 237 expectedKubeFeatureValues: map[featuregate.Feature]bool{"kubeA": false, "kubeB": true, "commonC": false}, 238 expectedTestFeatureValues: map[featuregate.Feature]bool{"testA": false, "testB": false, "commonC": true}, 239 }, 240 { 241 name: "setting kube feature flag with different prefix", 242 flags: []string{ 243 "--emulated-version=test=2.7", 244 "--emulated-version=kube=1.30", 245 "--feature-gates=kube:kubeB=false,test:commonC=true", 246 "--feature-gates=commonC=false,kubeB=true", 247 }, 248 parseError: "set kube feature gates with default empty prefix or kube: prefix consistently, do not mix use", 249 }, 250 { 251 name: "setting locked kube feature flag", 252 flags: []string{ 253 "--emulated-version=test=2.7", 254 "--feature-gates=kubeA=false", 255 }, 256 parseError: "cannot set feature gate kubeA to false, feature is locked to true", 257 }, 258 { 259 name: "setting unknown test feature flag", 260 flags: []string{ 261 "--emulated-version=test=2.7", 262 "--feature-gates=test:testD=true", 263 }, 264 parseError: "unrecognized feature gate: testD", 265 }, 266 { 267 name: "setting unknown component feature flag", 268 flags: []string{ 269 "--emulated-version=test=2.7", 270 "--feature-gates=test3:commonC=true", 271 }, 272 parseError: "component not registered: test3", 273 }, 274 } 275 for i, test := range tests { 276 t.Run(test.name, func(t *testing.T) { 277 fs := pflag.NewFlagSet("testflag", pflag.ContinueOnError) 278 r := testRegistry(t) 279 r.AddFlags(fs) 280 err := fs.Parse(test.flags) 281 if err == nil { 282 err = r.Set() 283 } 284 if test.parseError != "" { 285 if err == nil || !strings.Contains(err.Error(), test.parseError) { 286 t.Fatalf("%d: Parse() expected: %v, got: %v", i, test.parseError, err) 287 } 288 return 289 } 290 if err != nil { 291 t.Fatalf("%d: Parse() expected: nil, got: %v", i, err) 292 } 293 if len(test.expectedKubeEmulationVersion) > 0 { 294 assertVersionEqualTo(t, r.EffectiveVersionFor(DefaultKubeComponent).EmulationVersion(), test.expectedKubeEmulationVersion) 295 } 296 if len(test.expectedTestEmulationVersion) > 0 { 297 assertVersionEqualTo(t, r.EffectiveVersionFor(testComponent).EmulationVersion(), test.expectedTestEmulationVersion) 298 } 299 for f, v := range test.expectedKubeFeatureValues { 300 if r.FeatureGateFor(DefaultKubeComponent).Enabled(f) != v { 301 t.Errorf("%d: expected kube feature Enabled(%s)=%v", i, f, v) 302 } 303 } 304 for f, v := range test.expectedTestFeatureValues { 305 if r.FeatureGateFor(testComponent).Enabled(f) != v { 306 t.Errorf("%d: expected test feature Enabled(%s)=%v", i, f, v) 307 } 308 } 309 }) 310 } 311 } 312 313 func TestVersionMapping(t *testing.T) { 314 r := NewComponentGlobalsRegistry() 315 ver1 := NewEffectiveVersion("0.58") 316 ver2 := NewEffectiveVersion("1.28") 317 ver3 := NewEffectiveVersion("2.10") 318 319 utilruntime.Must(r.Register("test1", ver1, nil)) 320 utilruntime.Must(r.Register("test2", ver2, nil)) 321 utilruntime.Must(r.Register("test3", ver3, nil)) 322 323 assertVersionEqualTo(t, r.EffectiveVersionFor("test1").EmulationVersion(), "0.58") 324 assertVersionEqualTo(t, r.EffectiveVersionFor("test2").EmulationVersion(), "1.28") 325 assertVersionEqualTo(t, r.EffectiveVersionFor("test3").EmulationVersion(), "2.10") 326 327 utilruntime.Must(r.SetEmulationVersionMapping("test2", "test3", 328 func(from *version.Version) *version.Version { 329 return version.MajorMinor(from.Major()+1, from.Minor()-19) 330 })) 331 utilruntime.Must(r.SetEmulationVersionMapping("test1", "test2", 332 func(from *version.Version) *version.Version { 333 return version.MajorMinor(from.Major()+1, from.Minor()-28) 334 })) 335 assertVersionEqualTo(t, r.EffectiveVersionFor("test1").EmulationVersion(), "0.58") 336 assertVersionEqualTo(t, r.EffectiveVersionFor("test2").EmulationVersion(), "1.30") 337 assertVersionEqualTo(t, r.EffectiveVersionFor("test3").EmulationVersion(), "2.11") 338 339 fs := pflag.NewFlagSet("testflag", pflag.ContinueOnError) 340 r.AddFlags(fs) 341 342 if err := fs.Parse([]string{fmt.Sprintf("--emulated-version=%s", "test1=0.56")}); err != nil { 343 t.Fatal(err) 344 return 345 } 346 if err := r.Set(); err != nil { 347 t.Fatal(err) 348 return 349 } 350 assertVersionEqualTo(t, r.EffectiveVersionFor("test1").EmulationVersion(), "0.56") 351 assertVersionEqualTo(t, r.EffectiveVersionFor("test2").EmulationVersion(), "1.28") 352 assertVersionEqualTo(t, r.EffectiveVersionFor("test3").EmulationVersion(), "2.09") 353 } 354 355 func TestVersionMappingWithMultipleDependency(t *testing.T) { 356 r := NewComponentGlobalsRegistry() 357 ver1 := NewEffectiveVersion("0.58") 358 ver2 := NewEffectiveVersion("1.28") 359 ver3 := NewEffectiveVersion("2.10") 360 361 utilruntime.Must(r.Register("test1", ver1, nil)) 362 utilruntime.Must(r.Register("test2", ver2, nil)) 363 utilruntime.Must(r.Register("test3", ver3, nil)) 364 365 assertVersionEqualTo(t, r.EffectiveVersionFor("test1").EmulationVersion(), "0.58") 366 assertVersionEqualTo(t, r.EffectiveVersionFor("test2").EmulationVersion(), "1.28") 367 assertVersionEqualTo(t, r.EffectiveVersionFor("test3").EmulationVersion(), "2.10") 368 369 utilruntime.Must(r.SetEmulationVersionMapping("test1", "test2", 370 func(from *version.Version) *version.Version { 371 return version.MajorMinor(from.Major()+1, from.Minor()-28) 372 })) 373 err := r.SetEmulationVersionMapping("test3", "test2", 374 func(from *version.Version) *version.Version { 375 return version.MajorMinor(from.Major()-1, from.Minor()+19) 376 }) 377 if err == nil { 378 t.Errorf("expect error when setting 2nd mapping to test2") 379 } 380 } 381 382 func TestVersionMappingWithCyclicDependency(t *testing.T) { 383 r := NewComponentGlobalsRegistry() 384 ver1 := NewEffectiveVersion("0.58") 385 ver2 := NewEffectiveVersion("1.28") 386 ver3 := NewEffectiveVersion("2.10") 387 388 utilruntime.Must(r.Register("test1", ver1, nil)) 389 utilruntime.Must(r.Register("test2", ver2, nil)) 390 utilruntime.Must(r.Register("test3", ver3, nil)) 391 392 assertVersionEqualTo(t, r.EffectiveVersionFor("test1").EmulationVersion(), "0.58") 393 assertVersionEqualTo(t, r.EffectiveVersionFor("test2").EmulationVersion(), "1.28") 394 assertVersionEqualTo(t, r.EffectiveVersionFor("test3").EmulationVersion(), "2.10") 395 396 utilruntime.Must(r.SetEmulationVersionMapping("test1", "test2", 397 func(from *version.Version) *version.Version { 398 return version.MajorMinor(from.Major()+1, from.Minor()-28) 399 })) 400 utilruntime.Must(r.SetEmulationVersionMapping("test2", "test3", 401 func(from *version.Version) *version.Version { 402 return version.MajorMinor(from.Major()+1, from.Minor()-19) 403 })) 404 err := r.SetEmulationVersionMapping("test3", "test1", 405 func(from *version.Version) *version.Version { 406 return version.MajorMinor(from.Major()-2, from.Minor()+48) 407 }) 408 if err == nil { 409 t.Errorf("expect cyclic version mapping error") 410 } 411 } 412 413 func assertVersionEqualTo(t *testing.T, ver *version.Version, expectedVer string) { 414 if ver.EqualTo(version.MustParse(expectedVer)) { 415 return 416 } 417 t.Errorf("expected: %s, got %s", expectedVer, ver.String()) 418 }