1
16
17 package featuregate
18
19 import (
20 "fmt"
21 "strings"
22 "testing"
23
24 "github.com/spf13/pflag"
25 "github.com/stretchr/testify/assert"
26
27 "k8s.io/component-base/metrics/legacyregistry"
28 featuremetrics "k8s.io/component-base/metrics/prometheus/feature"
29 "k8s.io/component-base/metrics/testutil"
30 )
31
32 func TestFeatureGateFlag(t *testing.T) {
33
34 const testAlphaGate Feature = "TestAlpha"
35 const testBetaGate Feature = "TestBeta"
36
37 tests := []struct {
38 arg string
39 expect map[Feature]bool
40 parseError string
41 }{
42 {
43 arg: "",
44 expect: map[Feature]bool{
45 allAlphaGate: false,
46 allBetaGate: false,
47 testAlphaGate: false,
48 testBetaGate: false,
49 },
50 },
51 {
52 arg: "fooBarBaz=true",
53 expect: map[Feature]bool{
54 allAlphaGate: false,
55 allBetaGate: false,
56 testAlphaGate: false,
57 testBetaGate: false,
58 },
59 parseError: "unrecognized feature gate: fooBarBaz",
60 },
61 {
62 arg: "AllAlpha=false",
63 expect: map[Feature]bool{
64 allAlphaGate: false,
65 allBetaGate: false,
66 testAlphaGate: false,
67 testBetaGate: false,
68 },
69 },
70 {
71 arg: "AllAlpha=true",
72 expect: map[Feature]bool{
73 allAlphaGate: true,
74 allBetaGate: false,
75 testAlphaGate: true,
76 testBetaGate: false,
77 },
78 },
79 {
80 arg: "AllAlpha=banana",
81 expect: map[Feature]bool{
82 allAlphaGate: false,
83 allBetaGate: false,
84 testAlphaGate: false,
85 testBetaGate: false,
86 },
87 parseError: "invalid value of AllAlpha",
88 },
89 {
90 arg: "AllAlpha=false,TestAlpha=true",
91 expect: map[Feature]bool{
92 allAlphaGate: false,
93 allBetaGate: false,
94 testAlphaGate: true,
95 testBetaGate: false,
96 },
97 },
98 {
99 arg: "TestAlpha=true,AllAlpha=false",
100 expect: map[Feature]bool{
101 allAlphaGate: false,
102 allBetaGate: false,
103 testAlphaGate: true,
104 testBetaGate: false,
105 },
106 },
107 {
108 arg: "AllAlpha=true,TestAlpha=false",
109 expect: map[Feature]bool{
110 allAlphaGate: true,
111 allBetaGate: false,
112 testAlphaGate: false,
113 testBetaGate: false,
114 },
115 },
116 {
117 arg: "TestAlpha=false,AllAlpha=true",
118 expect: map[Feature]bool{
119 allAlphaGate: true,
120 allBetaGate: false,
121 testAlphaGate: false,
122 testBetaGate: false,
123 },
124 },
125 {
126 arg: "TestBeta=true,AllAlpha=false",
127 expect: map[Feature]bool{
128 allAlphaGate: false,
129 allBetaGate: false,
130 testAlphaGate: false,
131 testBetaGate: true,
132 },
133 },
134
135 {
136 arg: "AllBeta=false",
137 expect: map[Feature]bool{
138 allAlphaGate: false,
139 allBetaGate: false,
140 testAlphaGate: false,
141 testBetaGate: false,
142 },
143 },
144 {
145 arg: "AllBeta=true",
146 expect: map[Feature]bool{
147 allAlphaGate: false,
148 allBetaGate: true,
149 testAlphaGate: false,
150 testBetaGate: true,
151 },
152 },
153 {
154 arg: "AllBeta=banana",
155 expect: map[Feature]bool{
156 allAlphaGate: false,
157 allBetaGate: false,
158 testAlphaGate: false,
159 testBetaGate: false,
160 },
161 parseError: "invalid value of AllBeta",
162 },
163 {
164 arg: "AllBeta=false,TestBeta=true",
165 expect: map[Feature]bool{
166 allAlphaGate: false,
167 allBetaGate: false,
168 testAlphaGate: false,
169 testBetaGate: true,
170 },
171 },
172 {
173 arg: "TestBeta=true,AllBeta=false",
174 expect: map[Feature]bool{
175 allAlphaGate: false,
176 allBetaGate: false,
177 testAlphaGate: false,
178 testBetaGate: true,
179 },
180 },
181 {
182 arg: "AllBeta=true,TestBeta=false",
183 expect: map[Feature]bool{
184 allAlphaGate: false,
185 allBetaGate: true,
186 testAlphaGate: false,
187 testBetaGate: false,
188 },
189 },
190 {
191 arg: "TestBeta=false,AllBeta=true",
192 expect: map[Feature]bool{
193 allAlphaGate: false,
194 allBetaGate: true,
195 testAlphaGate: false,
196 testBetaGate: false,
197 },
198 },
199 {
200 arg: "TestAlpha=true,AllBeta=false",
201 expect: map[Feature]bool{
202 allAlphaGate: false,
203 allBetaGate: false,
204 testAlphaGate: true,
205 testBetaGate: false,
206 },
207 },
208 }
209 for i, test := range tests {
210 t.Run(test.arg, func(t *testing.T) {
211 fs := pflag.NewFlagSet("testfeaturegateflag", pflag.ContinueOnError)
212 f := NewFeatureGate()
213 f.Add(map[Feature]FeatureSpec{
214 testAlphaGate: {Default: false, PreRelease: Alpha},
215 testBetaGate: {Default: false, PreRelease: Beta},
216 })
217 f.AddFlag(fs)
218
219 err := fs.Parse([]string{fmt.Sprintf("--%s=%s", flagName, test.arg)})
220 if test.parseError != "" {
221 if !strings.Contains(err.Error(), test.parseError) {
222 t.Errorf("%d: Parse() Expected %v, Got %v", i, test.parseError, err)
223 }
224 } else if err != nil {
225 t.Errorf("%d: Parse() Expected nil, Got %v", i, err)
226 }
227 for k, v := range test.expect {
228 if actual := f.enabled.Load().(map[Feature]bool)[k]; actual != v {
229 t.Errorf("%d: expected %s=%v, Got %v", i, k, v, actual)
230 }
231 }
232 })
233 }
234 }
235
236 func TestFeatureGateOverride(t *testing.T) {
237 const testAlphaGate Feature = "TestAlpha"
238 const testBetaGate Feature = "TestBeta"
239
240
241 var f *featureGate = NewFeatureGate()
242 f.Add(map[Feature]FeatureSpec{
243 testAlphaGate: {Default: false, PreRelease: Alpha},
244 testBetaGate: {Default: false, PreRelease: Beta},
245 })
246
247 f.Set("TestAlpha=true,TestBeta=true")
248 if f.Enabled(testAlphaGate) != true {
249 t.Errorf("Expected true")
250 }
251 if f.Enabled(testBetaGate) != true {
252 t.Errorf("Expected true")
253 }
254
255 f.Set("TestAlpha=false")
256 if f.Enabled(testAlphaGate) != false {
257 t.Errorf("Expected false")
258 }
259 if f.Enabled(testBetaGate) != true {
260 t.Errorf("Expected true")
261 }
262 }
263
264 func TestFeatureGateFlagDefaults(t *testing.T) {
265
266 const testAlphaGate Feature = "TestAlpha"
267 const testBetaGate Feature = "TestBeta"
268
269
270 var f *featureGate = NewFeatureGate()
271 f.Add(map[Feature]FeatureSpec{
272 testAlphaGate: {Default: false, PreRelease: Alpha},
273 testBetaGate: {Default: true, PreRelease: Beta},
274 })
275
276 if f.Enabled(testAlphaGate) != false {
277 t.Errorf("Expected false")
278 }
279 if f.Enabled(testBetaGate) != true {
280 t.Errorf("Expected true")
281 }
282 }
283
284 func TestFeatureGateKnownFeatures(t *testing.T) {
285
286 const (
287 testAlphaGate Feature = "TestAlpha"
288 testBetaGate Feature = "TestBeta"
289 testGAGate Feature = "TestGA"
290 testDeprecatedGate Feature = "TestDeprecated"
291 )
292
293
294 var f *featureGate = NewFeatureGate()
295 f.Add(map[Feature]FeatureSpec{
296 testAlphaGate: {Default: false, PreRelease: Alpha},
297 testBetaGate: {Default: true, PreRelease: Beta},
298 testGAGate: {Default: true, PreRelease: GA},
299 testDeprecatedGate: {Default: false, PreRelease: Deprecated},
300 })
301
302 known := strings.Join(f.KnownFeatures(), " ")
303
304 assert.Contains(t, known, testAlphaGate)
305 assert.Contains(t, known, testBetaGate)
306 assert.NotContains(t, known, testGAGate)
307 assert.NotContains(t, known, testDeprecatedGate)
308 }
309
310 func TestFeatureGateSetFromMap(t *testing.T) {
311
312 const testAlphaGate Feature = "TestAlpha"
313 const testBetaGate Feature = "TestBeta"
314 const testLockedTrueGate Feature = "TestLockedTrue"
315 const testLockedFalseGate Feature = "TestLockedFalse"
316
317 tests := []struct {
318 name string
319 setmap map[string]bool
320 expect map[Feature]bool
321 setmapError string
322 }{
323 {
324 name: "set TestAlpha and TestBeta true",
325 setmap: map[string]bool{
326 "TestAlpha": true,
327 "TestBeta": true,
328 },
329 expect: map[Feature]bool{
330 testAlphaGate: true,
331 testBetaGate: true,
332 },
333 },
334 {
335 name: "set TestBeta true",
336 setmap: map[string]bool{
337 "TestBeta": true,
338 },
339 expect: map[Feature]bool{
340 testAlphaGate: false,
341 testBetaGate: true,
342 },
343 },
344 {
345 name: "set TestAlpha false",
346 setmap: map[string]bool{
347 "TestAlpha": false,
348 },
349 expect: map[Feature]bool{
350 testAlphaGate: false,
351 testBetaGate: false,
352 },
353 },
354 {
355 name: "set TestInvaild true",
356 setmap: map[string]bool{
357 "TestInvaild": true,
358 },
359 expect: map[Feature]bool{
360 testAlphaGate: false,
361 testBetaGate: false,
362 },
363 setmapError: "unrecognized feature gate:",
364 },
365 {
366 name: "set locked gates",
367 setmap: map[string]bool{
368 "TestLockedTrue": true,
369 "TestLockedFalse": false,
370 },
371 expect: map[Feature]bool{
372 testAlphaGate: false,
373 testBetaGate: false,
374 },
375 },
376 {
377 name: "set locked gates",
378 setmap: map[string]bool{
379 "TestLockedTrue": false,
380 },
381 expect: map[Feature]bool{
382 testAlphaGate: false,
383 testBetaGate: false,
384 },
385 setmapError: "cannot set feature gate TestLockedTrue to false, feature is locked to true",
386 },
387 {
388 name: "set locked gates",
389 setmap: map[string]bool{
390 "TestLockedFalse": true,
391 },
392 expect: map[Feature]bool{
393 testAlphaGate: false,
394 testBetaGate: false,
395 },
396 setmapError: "cannot set feature gate TestLockedFalse to true, feature is locked to false",
397 },
398 }
399 for i, test := range tests {
400 t.Run(fmt.Sprintf("SetFromMap %s", test.name), func(t *testing.T) {
401 f := NewFeatureGate()
402 f.Add(map[Feature]FeatureSpec{
403 testAlphaGate: {Default: false, PreRelease: Alpha},
404 testBetaGate: {Default: false, PreRelease: Beta},
405 testLockedTrueGate: {Default: true, PreRelease: GA, LockToDefault: true},
406 testLockedFalseGate: {Default: false, PreRelease: GA, LockToDefault: true},
407 })
408 err := f.SetFromMap(test.setmap)
409 if test.setmapError != "" {
410 if err == nil {
411 t.Errorf("expected error, got none")
412 } else if !strings.Contains(err.Error(), test.setmapError) {
413 t.Errorf("%d: SetFromMap(%#v) Expected err:%v, Got err:%v", i, test.setmap, test.setmapError, err)
414 }
415 } else if err != nil {
416 t.Errorf("%d: SetFromMap(%#v) Expected success, Got err:%v", i, test.setmap, err)
417 }
418 for k, v := range test.expect {
419 if actual := f.Enabled(k); actual != v {
420 t.Errorf("%d: SetFromMap(%#v) Expected %s=%v, Got %s=%v", i, test.setmap, k, v, k, actual)
421 }
422 }
423 })
424 }
425 }
426
427 func TestFeatureGateMetrics(t *testing.T) {
428
429 featuremetrics.ResetFeatureInfoMetric()
430 const testAlphaGate Feature = "TestAlpha"
431 const testBetaGate Feature = "TestBeta"
432 const testAlphaEnabled Feature = "TestAlphaEnabled"
433 const testBetaDisabled Feature = "TestBetaDisabled"
434 testedMetrics := []string{"kubernetes_feature_enabled"}
435 expectedOutput := `
436 # HELP kubernetes_feature_enabled [BETA] This metric records the data about the stage and enablement of a k8s feature.
437 # TYPE kubernetes_feature_enabled gauge
438 kubernetes_feature_enabled{name="TestAlpha",stage="ALPHA"} 0
439 kubernetes_feature_enabled{name="TestBeta",stage="BETA"} 1
440 kubernetes_feature_enabled{name="TestAlphaEnabled",stage="ALPHA"} 1
441 kubernetes_feature_enabled{name="AllAlpha",stage="ALPHA"} 0
442 kubernetes_feature_enabled{name="AllBeta",stage="BETA"} 0
443 kubernetes_feature_enabled{name="TestBetaDisabled",stage="ALPHA"} 0
444 `
445
446 f := NewFeatureGate()
447 fMap := map[Feature]FeatureSpec{
448 testAlphaGate: {Default: false, PreRelease: Alpha},
449 testAlphaEnabled: {Default: false, PreRelease: Alpha},
450 testBetaGate: {Default: true, PreRelease: Beta},
451 testBetaDisabled: {Default: true, PreRelease: Alpha},
452 }
453 f.Add(fMap)
454 f.SetFromMap(map[string]bool{"TestAlphaEnabled": true, "TestBetaDisabled": false})
455 f.AddMetrics()
456 if err := testutil.GatherAndCompare(legacyregistry.DefaultGatherer, strings.NewReader(expectedOutput), testedMetrics...); err != nil {
457 t.Fatal(err)
458 }
459 }
460
461 func TestFeatureGateString(t *testing.T) {
462
463 const testAlphaGate Feature = "TestAlpha"
464 const testBetaGate Feature = "TestBeta"
465 const testGAGate Feature = "TestGA"
466
467 featuremap := map[Feature]FeatureSpec{
468 testGAGate: {Default: true, PreRelease: GA},
469 testAlphaGate: {Default: false, PreRelease: Alpha},
470 testBetaGate: {Default: true, PreRelease: Beta},
471 }
472
473 tests := []struct {
474 setmap map[string]bool
475 expect string
476 }{
477 {
478 setmap: map[string]bool{
479 "TestAlpha": false,
480 },
481 expect: "TestAlpha=false",
482 },
483 {
484 setmap: map[string]bool{
485 "TestAlpha": false,
486 "TestBeta": true,
487 },
488 expect: "TestAlpha=false,TestBeta=true",
489 },
490 {
491 setmap: map[string]bool{
492 "TestGA": true,
493 "TestAlpha": false,
494 "TestBeta": true,
495 },
496 expect: "TestAlpha=false,TestBeta=true,TestGA=true",
497 },
498 }
499 for i, test := range tests {
500 t.Run(fmt.Sprintf("SetFromMap %s", test.expect), func(t *testing.T) {
501 f := NewFeatureGate()
502 f.Add(featuremap)
503 f.SetFromMap(test.setmap)
504 result := f.String()
505 if result != test.expect {
506 t.Errorf("%d: SetFromMap(%#v) Expected %s, Got %s", i, test.setmap, test.expect, result)
507 }
508 })
509 }
510 }
511
512 func TestFeatureGateOverrideDefault(t *testing.T) {
513 t.Run("overrides take effect", func(t *testing.T) {
514 f := NewFeatureGate()
515 if err := f.Add(map[Feature]FeatureSpec{
516 "TestFeature1": {Default: true},
517 "TestFeature2": {Default: false},
518 }); err != nil {
519 t.Fatal(err)
520 }
521 if err := f.OverrideDefault("TestFeature1", false); err != nil {
522 t.Fatal(err)
523 }
524 if err := f.OverrideDefault("TestFeature2", true); err != nil {
525 t.Fatal(err)
526 }
527 if f.Enabled("TestFeature1") {
528 t.Error("expected TestFeature1 to have effective default of false")
529 }
530 if !f.Enabled("TestFeature2") {
531 t.Error("expected TestFeature2 to have effective default of true")
532 }
533 })
534
535 t.Run("overrides are preserved across deep copies", func(t *testing.T) {
536 f := NewFeatureGate()
537 if err := f.Add(map[Feature]FeatureSpec{"TestFeature": {Default: false}}); err != nil {
538 t.Fatal(err)
539 }
540 if err := f.OverrideDefault("TestFeature", true); err != nil {
541 t.Fatal(err)
542 }
543 fcopy := f.DeepCopy()
544 if !fcopy.Enabled("TestFeature") {
545 t.Error("default override was not preserved by deep copy")
546 }
547 })
548
549 t.Run("reflected in known features", func(t *testing.T) {
550 f := NewFeatureGate()
551 if err := f.Add(map[Feature]FeatureSpec{"TestFeature": {
552 Default: false,
553 PreRelease: Alpha,
554 }}); err != nil {
555 t.Fatal(err)
556 }
557 if err := f.OverrideDefault("TestFeature", true); err != nil {
558 t.Fatal(err)
559 }
560 var found bool
561 for _, s := range f.KnownFeatures() {
562 if !strings.Contains(s, "TestFeature") {
563 continue
564 }
565 found = true
566 if !strings.Contains(s, "default=true") {
567 t.Errorf("expected override of default to be reflected in known feature description %q", s)
568 }
569 }
570 if !found {
571 t.Error("found no entry for TestFeature in known features")
572 }
573 })
574
575 t.Run("may not change default for specs with locked defaults", func(t *testing.T) {
576 f := NewFeatureGate()
577 if err := f.Add(map[Feature]FeatureSpec{
578 "LockedFeature": {
579 Default: true,
580 LockToDefault: true,
581 },
582 }); err != nil {
583 t.Fatal(err)
584 }
585 if f.OverrideDefault("LockedFeature", false) == nil {
586 t.Error("expected error when attempting to override the default for a feature with a locked default")
587 }
588 if f.OverrideDefault("LockedFeature", true) == nil {
589 t.Error("expected error when attempting to override the default for a feature with a locked default")
590 }
591 })
592
593 t.Run("does not supersede explicitly-set value", func(t *testing.T) {
594 f := NewFeatureGate()
595 if err := f.Add(map[Feature]FeatureSpec{"TestFeature": {Default: true}}); err != nil {
596 t.Fatal(err)
597 }
598 if err := f.OverrideDefault("TestFeature", false); err != nil {
599 t.Fatal(err)
600 }
601 if err := f.SetFromMap(map[string]bool{"TestFeature": true}); err != nil {
602 t.Fatal(err)
603 }
604 if !f.Enabled("TestFeature") {
605 t.Error("expected feature to be effectively enabled despite default override")
606 }
607 })
608
609 t.Run("prevents re-registration of feature spec after overriding default", func(t *testing.T) {
610 f := NewFeatureGate()
611 if err := f.Add(map[Feature]FeatureSpec{
612 "TestFeature": {
613 Default: true,
614 PreRelease: Alpha,
615 },
616 }); err != nil {
617 t.Fatal(err)
618 }
619 if err := f.OverrideDefault("TestFeature", false); err != nil {
620 t.Fatal(err)
621 }
622 if err := f.Add(map[Feature]FeatureSpec{
623 "TestFeature": {
624 Default: true,
625 PreRelease: Alpha,
626 },
627 }); err == nil {
628 t.Error("expected re-registration to return a non-nil error after overriding its default")
629 }
630 })
631
632 t.Run("does not allow override for an unknown feature", func(t *testing.T) {
633 f := NewFeatureGate()
634 if err := f.OverrideDefault("TestFeature", true); err == nil {
635 t.Error("expected an error to be returned in attempt to override default for unregistered feature")
636 }
637 })
638
639 t.Run("returns error if already added to flag set", func(t *testing.T) {
640 f := NewFeatureGate()
641 fs := pflag.NewFlagSet("test", pflag.ContinueOnError)
642 f.AddFlag(fs)
643
644 if err := f.OverrideDefault("TestFeature", true); err == nil {
645 t.Error("expected a non-nil error to be returned")
646 }
647 })
648 }
649
View as plain text