From b2831a686c31b5db72f3106b3688f00c40ba1b00 Mon Sep 17 00:00:00 2001 From: Andrew Kim Date: Mon, 4 Mar 2019 12:35:31 -0500 Subject: [PATCH] move generic feature gate code from k8s.io/apiserver to k8s.io/component-base --- hack/.golint_failures | 2 +- .../k8s.io/apiserver/pkg/util/feature/BUILD | 26 +- .../pkg/util/feature/feature_gate.go | 318 +---------------- .../k8s.io/component-base/featuregate/BUILD | 40 +++ .../featuregate/feature_gate.go | 333 ++++++++++++++++++ .../featuregate}/feature_gate_test.go | 2 +- .../featuregate}/testing/BUILD | 8 +- .../featuregate/testing/feature_gate.go} | 10 +- 8 files changed, 391 insertions(+), 348 deletions(-) create mode 100644 staging/src/k8s.io/component-base/featuregate/BUILD create mode 100644 staging/src/k8s.io/component-base/featuregate/feature_gate.go rename staging/src/k8s.io/{apiserver/pkg/util/feature => component-base/featuregate}/feature_gate_test.go (99%) rename staging/src/k8s.io/{apiserver/pkg/util/feature => component-base/featuregate}/testing/BUILD (63%) rename staging/src/k8s.io/{apiserver/pkg/util/feature/testing/feature_gate_testing.go => component-base/featuregate/testing/feature_gate.go} (71%) diff --git a/hack/.golint_failures b/hack/.golint_failures index 9ef59605506..c53673bb7b5 100644 --- a/hack/.golint_failures +++ b/hack/.golint_failures @@ -500,7 +500,6 @@ staging/src/k8s.io/apiserver/pkg/storage/storagebackend staging/src/k8s.io/apiserver/pkg/storage/testing staging/src/k8s.io/apiserver/pkg/storage/tests staging/src/k8s.io/apiserver/pkg/storage/value -staging/src/k8s.io/apiserver/pkg/util/feature staging/src/k8s.io/apiserver/pkg/util/proxy staging/src/k8s.io/apiserver/pkg/util/webhook staging/src/k8s.io/apiserver/pkg/util/wsstream @@ -574,6 +573,7 @@ staging/src/k8s.io/code-generator/cmd/go-to-protobuf/protobuf staging/src/k8s.io/code-generator/cmd/lister-gen/generators staging/src/k8s.io/component-base/cli/flag staging/src/k8s.io/component-base/config/v1alpha1 +staging/src/k8s.io/component-base/featuregate staging/src/k8s.io/cri-api/pkg/apis/testing staging/src/k8s.io/kube-aggregator/pkg/apis/apiregistration/v1 staging/src/k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1 diff --git a/staging/src/k8s.io/apiserver/pkg/util/feature/BUILD b/staging/src/k8s.io/apiserver/pkg/util/feature/BUILD index 2b0386ab76b..55b9f010e03 100644 --- a/staging/src/k8s.io/apiserver/pkg/util/feature/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/util/feature/BUILD @@ -1,30 +1,13 @@ package(default_visibility = ["//visibility:public"]) -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_test( - name = "go_default_test", - srcs = ["feature_gate_test.go"], - embed = [":go_default_library"], - deps = [ - "//vendor/github.com/spf13/pflag:go_default_library", - "//vendor/github.com/stretchr/testify/assert:go_default_library", - ], -) +load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = ["feature_gate.go"], importmap = "k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/util/feature", importpath = "k8s.io/apiserver/pkg/util/feature", - deps = [ - "//vendor/github.com/spf13/pflag:go_default_library", - "//vendor/k8s.io/klog:go_default_library", - ], + deps = ["//staging/src/k8s.io/component-base/featuregate:go_default_library"], ) filegroup( @@ -36,9 +19,6 @@ filegroup( filegroup( name = "all-srcs", - srcs = [ - ":package-srcs", - "//staging/src/k8s.io/apiserver/pkg/util/feature/testing:all-srcs", - ], + srcs = [":package-srcs"], tags = ["automanaged"], ) diff --git a/staging/src/k8s.io/apiserver/pkg/util/feature/feature_gate.go b/staging/src/k8s.io/apiserver/pkg/util/feature/feature_gate.go index 90f8300bd32..a001cfa70c6 100644 --- a/staging/src/k8s.io/apiserver/pkg/util/feature/feature_gate.go +++ b/staging/src/k8s.io/apiserver/pkg/util/feature/feature_gate.go @@ -17,327 +17,17 @@ limitations under the License. package feature import ( - "fmt" - "sort" - "strconv" - "strings" - "sync" - "sync/atomic" - - "github.com/spf13/pflag" - "k8s.io/klog" -) - -type Feature string - -const ( - flagName = "feature-gates" - - // allAlphaGate is a global toggle for alpha features. Per-feature key - // values override the default set by allAlphaGate. Examples: - // AllAlpha=false,NewFeature=true will result in newFeature=true - // AllAlpha=true,NewFeature=false will result in newFeature=false - allAlphaGate Feature = "AllAlpha" + "k8s.io/component-base/featuregate" ) var ( - // The generic features. - defaultFeatures = map[Feature]FeatureSpec{ - allAlphaGate: {Default: false, PreRelease: Alpha}, - } - - // Special handling for a few gates. - specialFeatures = map[Feature]func(known map[Feature]FeatureSpec, enabled map[Feature]bool, val bool){ - allAlphaGate: setUnsetAlphaGates, - } - // DefaultMutableFeatureGate is a mutable version of DefaultFeatureGate. // Only top-level commands/options setup and the k8s.io/apiserver/pkg/util/feature/testing package should make use of this. // Tests that need to modify feature gates for the duration of their test should use: - // defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features., )() - DefaultMutableFeatureGate MutableFeatureGate = NewFeatureGate() + // defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features., )() + DefaultMutableFeatureGate featuregate.MutableFeatureGate = featuregate.NewFeatureGate() // DefaultFeatureGate is a shared global FeatureGate. // Top-level commands/options setup that needs to modify this feature gate should use DefaultMutableFeatureGate. - DefaultFeatureGate FeatureGate = DefaultMutableFeatureGate + DefaultFeatureGate featuregate.FeatureGate = DefaultMutableFeatureGate ) - -type FeatureSpec struct { - // Default is the default enablement state for the feature - Default bool - // LockToDefault indicates that the feature is locked to its default and cannot be changed - LockToDefault bool - // PreRelease indicates the maturity level of the feature - PreRelease prerelease -} - -type prerelease string - -const ( - // Values for PreRelease. - Alpha = prerelease("ALPHA") - Beta = prerelease("BETA") - GA = prerelease("") - - // Deprecated - Deprecated = prerelease("DEPRECATED") -) - -// FeatureGate indicates whether a given feature is enabled or not -type FeatureGate interface { - // Enabled returns true if the key is enabled. - Enabled(key Feature) bool - // KnownFeatures returns a slice of strings describing the FeatureGate's known features. - KnownFeatures() []string - // DeepCopy returns a deep copy of the FeatureGate object, such that gates can be - // set on the copy without mutating the original. This is useful for validating - // config against potential feature gate changes before committing those changes. - DeepCopy() MutableFeatureGate -} - -// MutableFeatureGate parses and stores flag gates for known features from -// a string like feature1=true,feature2=false,... -type MutableFeatureGate interface { - FeatureGate - - // AddFlag adds a flag for setting global feature gates to the specified FlagSet. - AddFlag(fs *pflag.FlagSet) - // Set parses and stores flag gates for known features - // from a string like feature1=true,feature2=false,... - Set(value string) error - // SetFromMap stores flag gates for known features from a map[string]bool or returns an error - SetFromMap(m map[string]bool) error - // Add adds features to the featureGate. - Add(features map[Feature]FeatureSpec) error -} - -// featureGate implements FeatureGate as well as pflag.Value for flag parsing. -type featureGate struct { - special map[Feature]func(map[Feature]FeatureSpec, map[Feature]bool, bool) - - // lock guards writes to known, enabled, and reads/writes of closed - lock sync.Mutex - // known holds a map[Feature]FeatureSpec - known *atomic.Value - // enabled holds a map[Feature]bool - enabled *atomic.Value - // closed is set to true when AddFlag is called, and prevents subsequent calls to Add - closed bool -} - -func setUnsetAlphaGates(known map[Feature]FeatureSpec, enabled map[Feature]bool, val bool) { - for k, v := range known { - if v.PreRelease == Alpha { - if _, found := enabled[k]; !found { - enabled[k] = val - } - } - } -} - -// Set, String, and Type implement pflag.Value -var _ pflag.Value = &featureGate{} - -func NewFeatureGate() *featureGate { - known := map[Feature]FeatureSpec{} - for k, v := range defaultFeatures { - known[k] = v - } - - knownValue := &atomic.Value{} - knownValue.Store(known) - - enabled := map[Feature]bool{} - enabledValue := &atomic.Value{} - enabledValue.Store(enabled) - - f := &featureGate{ - known: knownValue, - special: specialFeatures, - enabled: enabledValue, - } - return f -} - -// Set parses a string of the form "key1=value1,key2=value2,..." into a -// map[string]bool of known keys or returns an error. -func (f *featureGate) Set(value string) error { - m := make(map[string]bool) - for _, s := range strings.Split(value, ",") { - if len(s) == 0 { - continue - } - arr := strings.SplitN(s, "=", 2) - k := strings.TrimSpace(arr[0]) - if len(arr) != 2 { - return fmt.Errorf("missing bool value for %s", k) - } - v := strings.TrimSpace(arr[1]) - boolValue, err := strconv.ParseBool(v) - if err != nil { - return fmt.Errorf("invalid value of %s=%s, err: %v", k, v, err) - } - m[k] = boolValue - } - return f.SetFromMap(m) -} - -// SetFromMap stores flag gates for known features from a map[string]bool or returns an error -func (f *featureGate) SetFromMap(m map[string]bool) error { - f.lock.Lock() - defer f.lock.Unlock() - - // Copy existing state - known := map[Feature]FeatureSpec{} - for k, v := range f.known.Load().(map[Feature]FeatureSpec) { - known[k] = v - } - enabled := map[Feature]bool{} - for k, v := range f.enabled.Load().(map[Feature]bool) { - enabled[k] = v - } - - for k, v := range m { - k := Feature(k) - featureSpec, ok := known[k] - if !ok { - return fmt.Errorf("unrecognized feature gate: %s", k) - } - if featureSpec.LockToDefault && featureSpec.Default != v { - return fmt.Errorf("cannot set feature gate %v to %v, feature is locked to %v", k, v, featureSpec.Default) - } - enabled[k] = v - // Handle "special" features like "all alpha gates" - if fn, found := f.special[k]; found { - fn(known, enabled, v) - } - - if featureSpec.PreRelease == Deprecated { - klog.Warningf("Setting deprecated feature gate %s=%t. It will be removed in a future release.", k, v) - } else if featureSpec.PreRelease == GA { - klog.Warningf("Setting GA feature gate %s=%t. It will be removed in a future release.", k, v) - } - } - - // Persist changes - f.known.Store(known) - f.enabled.Store(enabled) - - klog.V(1).Infof("feature gates: %v", f.enabled) - return nil -} - -// String returns a string containing all enabled feature gates, formatted as "key1=value1,key2=value2,...". -func (f *featureGate) String() string { - pairs := []string{} - for k, v := range f.enabled.Load().(map[Feature]bool) { - pairs = append(pairs, fmt.Sprintf("%s=%t", k, v)) - } - sort.Strings(pairs) - return strings.Join(pairs, ",") -} - -func (f *featureGate) Type() string { - return "mapStringBool" -} - -// Add adds features to the featureGate. -func (f *featureGate) Add(features map[Feature]FeatureSpec) error { - f.lock.Lock() - defer f.lock.Unlock() - - if f.closed { - return fmt.Errorf("cannot add a feature gate after adding it to the flag set") - } - - // Copy existing state - known := map[Feature]FeatureSpec{} - for k, v := range f.known.Load().(map[Feature]FeatureSpec) { - known[k] = v - } - - for name, spec := range features { - if existingSpec, found := known[name]; found { - if existingSpec == spec { - continue - } - return fmt.Errorf("feature gate %q with different spec already exists: %v", name, existingSpec) - } - - known[name] = spec - } - - // Persist updated state - f.known.Store(known) - - return nil -} - -// Enabled returns true if the key is enabled. -func (f *featureGate) Enabled(key Feature) bool { - if v, ok := f.enabled.Load().(map[Feature]bool)[key]; ok { - return v - } - return f.known.Load().(map[Feature]FeatureSpec)[key].Default -} - -// AddFlag adds a flag for setting global feature gates to the specified FlagSet. -func (f *featureGate) AddFlag(fs *pflag.FlagSet) { - f.lock.Lock() - // TODO(mtaufen): Shouldn't we just close it on the first Set/SetFromMap instead? - // Not all components expose a feature gates flag using this AddFlag method, and - // in the future, all components will completely stop exposing a feature gates flag, - // in favor of componentconfig. - f.closed = true - f.lock.Unlock() - - known := f.KnownFeatures() - fs.Var(f, flagName, ""+ - "A set of key=value pairs that describe feature gates for alpha/experimental features. "+ - "Options are:\n"+strings.Join(known, "\n")) -} - -// KnownFeatures returns a slice of strings describing the FeatureGate's known features. -// Deprecated and GA features are hidden from the list. -func (f *featureGate) KnownFeatures() []string { - var known []string - for k, v := range f.known.Load().(map[Feature]FeatureSpec) { - if v.PreRelease == GA || v.PreRelease == Deprecated { - continue - } - known = append(known, fmt.Sprintf("%s=true|false (%s - default=%t)", k, v.PreRelease, v.Default)) - } - sort.Strings(known) - return known -} - -// DeepCopy returns a deep copy of the FeatureGate object, such that gates can be -// set on the copy without mutating the original. This is useful for validating -// config against potential feature gate changes before committing those changes. -func (f *featureGate) DeepCopy() MutableFeatureGate { - // Copy existing state. - known := map[Feature]FeatureSpec{} - for k, v := range f.known.Load().(map[Feature]FeatureSpec) { - known[k] = v - } - enabled := map[Feature]bool{} - for k, v := range f.enabled.Load().(map[Feature]bool) { - enabled[k] = v - } - - // Store copied state in new atomics. - knownValue := &atomic.Value{} - knownValue.Store(known) - enabledValue := &atomic.Value{} - enabledValue.Store(enabled) - - // Construct a new featureGate around the copied state. - // Note that specialFeatures is treated as immutable by convention, - // and we maintain the value of f.closed across the copy. - return &featureGate{ - special: specialFeatures, - known: knownValue, - enabled: enabledValue, - closed: f.closed, - } -} diff --git a/staging/src/k8s.io/component-base/featuregate/BUILD b/staging/src/k8s.io/component-base/featuregate/BUILD new file mode 100644 index 00000000000..0c742fe1ed9 --- /dev/null +++ b/staging/src/k8s.io/component-base/featuregate/BUILD @@ -0,0 +1,40 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["feature_gate.go"], + importmap = "k8s.io/kubernetes/vendor/k8s.io/component-base/featuregate", + importpath = "k8s.io/component-base/featuregate", + visibility = ["//visibility:public"], + deps = [ + "//vendor/github.com/spf13/pflag:go_default_library", + "//vendor/k8s.io/klog:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["feature_gate_test.go"], + embed = [":go_default_library"], + deps = [ + "//vendor/github.com/spf13/pflag:go_default_library", + "//vendor/github.com/stretchr/testify/assert:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [ + ":package-srcs", + "//staging/src/k8s.io/component-base/featuregate/testing:all-srcs", + ], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/staging/src/k8s.io/component-base/featuregate/feature_gate.go b/staging/src/k8s.io/component-base/featuregate/feature_gate.go new file mode 100644 index 00000000000..0243fb371a0 --- /dev/null +++ b/staging/src/k8s.io/component-base/featuregate/feature_gate.go @@ -0,0 +1,333 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package featuregate + +import ( + "fmt" + "sort" + "strconv" + "strings" + "sync" + "sync/atomic" + + "github.com/spf13/pflag" + "k8s.io/klog" +) + +type Feature string + +const ( + flagName = "feature-gates" + + // allAlphaGate is a global toggle for alpha features. Per-feature key + // values override the default set by allAlphaGate. Examples: + // AllAlpha=false,NewFeature=true will result in newFeature=true + // AllAlpha=true,NewFeature=false will result in newFeature=false + allAlphaGate Feature = "AllAlpha" +) + +var ( + // The generic features. + defaultFeatures = map[Feature]FeatureSpec{ + allAlphaGate: {Default: false, PreRelease: Alpha}, + } + + // Special handling for a few gates. + specialFeatures = map[Feature]func(known map[Feature]FeatureSpec, enabled map[Feature]bool, val bool){ + allAlphaGate: setUnsetAlphaGates, + } +) + +type FeatureSpec struct { + // Default is the default enablement state for the feature + Default bool + // LockToDefault indicates that the feature is locked to its default and cannot be changed + LockToDefault bool + // PreRelease indicates the maturity level of the feature + PreRelease prerelease +} + +type prerelease string + +const ( + // Values for PreRelease. + Alpha = prerelease("ALPHA") + Beta = prerelease("BETA") + GA = prerelease("") + + // Deprecated + Deprecated = prerelease("DEPRECATED") +) + +// FeatureGate indicates whether a given feature is enabled or not +type FeatureGate interface { + // Enabled returns true if the key is enabled. + Enabled(key Feature) bool + // KnownFeatures returns a slice of strings describing the FeatureGate's known features. + KnownFeatures() []string + // DeepCopy returns a deep copy of the FeatureGate object, such that gates can be + // set on the copy without mutating the original. This is useful for validating + // config against potential feature gate changes before committing those changes. + DeepCopy() MutableFeatureGate +} + +// MutableFeatureGate parses and stores flag gates for known features from +// a string like feature1=true,feature2=false,... +type MutableFeatureGate interface { + FeatureGate + + // AddFlag adds a flag for setting global feature gates to the specified FlagSet. + AddFlag(fs *pflag.FlagSet) + // Set parses and stores flag gates for known features + // from a string like feature1=true,feature2=false,... + Set(value string) error + // SetFromMap stores flag gates for known features from a map[string]bool or returns an error + SetFromMap(m map[string]bool) error + // Add adds features to the featureGate. + Add(features map[Feature]FeatureSpec) error +} + +// featureGate implements FeatureGate as well as pflag.Value for flag parsing. +type featureGate struct { + special map[Feature]func(map[Feature]FeatureSpec, map[Feature]bool, bool) + + // lock guards writes to known, enabled, and reads/writes of closed + lock sync.Mutex + // known holds a map[Feature]FeatureSpec + known *atomic.Value + // enabled holds a map[Feature]bool + enabled *atomic.Value + // closed is set to true when AddFlag is called, and prevents subsequent calls to Add + closed bool +} + +func setUnsetAlphaGates(known map[Feature]FeatureSpec, enabled map[Feature]bool, val bool) { + for k, v := range known { + if v.PreRelease == Alpha { + if _, found := enabled[k]; !found { + enabled[k] = val + } + } + } +} + +// Set, String, and Type implement pflag.Value +var _ pflag.Value = &featureGate{} + +func NewFeatureGate() *featureGate { + known := map[Feature]FeatureSpec{} + for k, v := range defaultFeatures { + known[k] = v + } + + knownValue := &atomic.Value{} + knownValue.Store(known) + + enabled := map[Feature]bool{} + enabledValue := &atomic.Value{} + enabledValue.Store(enabled) + + f := &featureGate{ + known: knownValue, + special: specialFeatures, + enabled: enabledValue, + } + return f +} + +// Set parses a string of the form "key1=value1,key2=value2,..." into a +// map[string]bool of known keys or returns an error. +func (f *featureGate) Set(value string) error { + m := make(map[string]bool) + for _, s := range strings.Split(value, ",") { + if len(s) == 0 { + continue + } + arr := strings.SplitN(s, "=", 2) + k := strings.TrimSpace(arr[0]) + if len(arr) != 2 { + return fmt.Errorf("missing bool value for %s", k) + } + v := strings.TrimSpace(arr[1]) + boolValue, err := strconv.ParseBool(v) + if err != nil { + return fmt.Errorf("invalid value of %s=%s, err: %v", k, v, err) + } + m[k] = boolValue + } + return f.SetFromMap(m) +} + +// SetFromMap stores flag gates for known features from a map[string]bool or returns an error +func (f *featureGate) SetFromMap(m map[string]bool) error { + f.lock.Lock() + defer f.lock.Unlock() + + // Copy existing state + known := map[Feature]FeatureSpec{} + for k, v := range f.known.Load().(map[Feature]FeatureSpec) { + known[k] = v + } + enabled := map[Feature]bool{} + for k, v := range f.enabled.Load().(map[Feature]bool) { + enabled[k] = v + } + + for k, v := range m { + k := Feature(k) + featureSpec, ok := known[k] + if !ok { + return fmt.Errorf("unrecognized feature gate: %s", k) + } + if featureSpec.LockToDefault && featureSpec.Default != v { + return fmt.Errorf("cannot set feature gate %v to %v, feature is locked to %v", k, v, featureSpec.Default) + } + enabled[k] = v + // Handle "special" features like "all alpha gates" + if fn, found := f.special[k]; found { + fn(known, enabled, v) + } + + if featureSpec.PreRelease == Deprecated { + klog.Warningf("Setting deprecated feature gate %s=%t. It will be removed in a future release.", k, v) + } else if featureSpec.PreRelease == GA { + klog.Warningf("Setting GA feature gate %s=%t. It will be removed in a future release.", k, v) + } + } + + // Persist changes + f.known.Store(known) + f.enabled.Store(enabled) + + klog.V(1).Infof("feature gates: %v", f.enabled) + return nil +} + +// String returns a string containing all enabled feature gates, formatted as "key1=value1,key2=value2,...". +func (f *featureGate) String() string { + pairs := []string{} + for k, v := range f.enabled.Load().(map[Feature]bool) { + pairs = append(pairs, fmt.Sprintf("%s=%t", k, v)) + } + sort.Strings(pairs) + return strings.Join(pairs, ",") +} + +func (f *featureGate) Type() string { + return "mapStringBool" +} + +// Add adds features to the featureGate. +func (f *featureGate) Add(features map[Feature]FeatureSpec) error { + f.lock.Lock() + defer f.lock.Unlock() + + if f.closed { + return fmt.Errorf("cannot add a feature gate after adding it to the flag set") + } + + // Copy existing state + known := map[Feature]FeatureSpec{} + for k, v := range f.known.Load().(map[Feature]FeatureSpec) { + known[k] = v + } + + for name, spec := range features { + if existingSpec, found := known[name]; found { + if existingSpec == spec { + continue + } + return fmt.Errorf("feature gate %q with different spec already exists: %v", name, existingSpec) + } + + known[name] = spec + } + + // Persist updated state + f.known.Store(known) + + return nil +} + +// Enabled returns true if the key is enabled. +func (f *featureGate) Enabled(key Feature) bool { + if v, ok := f.enabled.Load().(map[Feature]bool)[key]; ok { + return v + } + return f.known.Load().(map[Feature]FeatureSpec)[key].Default +} + +// AddFlag adds a flag for setting global feature gates to the specified FlagSet. +func (f *featureGate) AddFlag(fs *pflag.FlagSet) { + f.lock.Lock() + // TODO(mtaufen): Shouldn't we just close it on the first Set/SetFromMap instead? + // Not all components expose a feature gates flag using this AddFlag method, and + // in the future, all components will completely stop exposing a feature gates flag, + // in favor of componentconfig. + f.closed = true + f.lock.Unlock() + + known := f.KnownFeatures() + fs.Var(f, flagName, ""+ + "A set of key=value pairs that describe feature gates for alpha/experimental features. "+ + "Options are:\n"+strings.Join(known, "\n")) +} + +// KnownFeatures returns a slice of strings describing the FeatureGate's known features. +// Deprecated and GA features are hidden from the list. +func (f *featureGate) KnownFeatures() []string { + var known []string + for k, v := range f.known.Load().(map[Feature]FeatureSpec) { + if v.PreRelease == GA || v.PreRelease == Deprecated { + continue + } + known = append(known, fmt.Sprintf("%s=true|false (%s - default=%t)", k, v.PreRelease, v.Default)) + } + sort.Strings(known) + return known +} + +// DeepCopy returns a deep copy of the FeatureGate object, such that gates can be +// set on the copy without mutating the original. This is useful for validating +// config against potential feature gate changes before committing those changes. +func (f *featureGate) DeepCopy() MutableFeatureGate { + // Copy existing state. + known := map[Feature]FeatureSpec{} + for k, v := range f.known.Load().(map[Feature]FeatureSpec) { + known[k] = v + } + enabled := map[Feature]bool{} + for k, v := range f.enabled.Load().(map[Feature]bool) { + enabled[k] = v + } + + // Store copied state in new atomics. + knownValue := &atomic.Value{} + knownValue.Store(known) + enabledValue := &atomic.Value{} + enabledValue.Store(enabled) + + // Construct a new featureGate around the copied state. + // Note that specialFeatures is treated as immutable by convention, + // and we maintain the value of f.closed across the copy. + return &featureGate{ + special: specialFeatures, + known: knownValue, + enabled: enabledValue, + closed: f.closed, + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/util/feature/feature_gate_test.go b/staging/src/k8s.io/component-base/featuregate/feature_gate_test.go similarity index 99% rename from staging/src/k8s.io/apiserver/pkg/util/feature/feature_gate_test.go rename to staging/src/k8s.io/component-base/featuregate/feature_gate_test.go index aa42b3d3e94..18c4e3547a6 100644 --- a/staging/src/k8s.io/apiserver/pkg/util/feature/feature_gate_test.go +++ b/staging/src/k8s.io/component-base/featuregate/feature_gate_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package feature +package featuregate import ( "fmt" diff --git a/staging/src/k8s.io/apiserver/pkg/util/feature/testing/BUILD b/staging/src/k8s.io/component-base/featuregate/testing/BUILD similarity index 63% rename from staging/src/k8s.io/apiserver/pkg/util/feature/testing/BUILD rename to staging/src/k8s.io/component-base/featuregate/testing/BUILD index be0e4db3032..8b5497d273a 100644 --- a/staging/src/k8s.io/apiserver/pkg/util/feature/testing/BUILD +++ b/staging/src/k8s.io/component-base/featuregate/testing/BUILD @@ -2,11 +2,11 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", - srcs = ["feature_gate_testing.go"], - importmap = "k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/util/feature/testing", - importpath = "k8s.io/apiserver/pkg/util/feature/testing", + srcs = ["feature_gate.go"], + importmap = "k8s.io/kubernetes/vendor/k8s.io/component-base/featuregate/testing", + importpath = "k8s.io/component-base/featuregate/testing", visibility = ["//visibility:public"], - deps = ["//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library"], + deps = ["//staging/src/k8s.io/component-base/featuregate:go_default_library"], ) filegroup( diff --git a/staging/src/k8s.io/apiserver/pkg/util/feature/testing/feature_gate_testing.go b/staging/src/k8s.io/component-base/featuregate/testing/feature_gate.go similarity index 71% rename from staging/src/k8s.io/apiserver/pkg/util/feature/testing/feature_gate_testing.go rename to staging/src/k8s.io/component-base/featuregate/testing/feature_gate.go index b4afc9ae716..1e0b650e270 100644 --- a/staging/src/k8s.io/apiserver/pkg/util/feature/testing/feature_gate_testing.go +++ b/staging/src/k8s.io/component-base/featuregate/testing/feature_gate.go @@ -20,7 +20,7 @@ import ( "fmt" "testing" - "k8s.io/apiserver/pkg/util/feature" + "k8s.io/component-base/featuregate" ) // SetFeatureGateDuringTest sets the specified gate to the specified value, and returns a function that restores the original value. @@ -28,16 +28,16 @@ import ( // // Example use: // -// defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features., true)() -func SetFeatureGateDuringTest(tb testing.TB, gate feature.FeatureGate, f feature.Feature, value bool) func() { +// defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features., true)() +func SetFeatureGateDuringTest(tb testing.TB, gate featuregate.FeatureGate, f featuregate.Feature, value bool) func() { originalValue := gate.Enabled(f) - if err := gate.(feature.MutableFeatureGate).Set(fmt.Sprintf("%s=%v", f, value)); err != nil { + if err := gate.(featuregate.MutableFeatureGate).Set(fmt.Sprintf("%s=%v", f, value)); err != nil { tb.Errorf("error setting %s=%v: %v", f, value, err) } return func() { - if err := gate.(feature.MutableFeatureGate).Set(fmt.Sprintf("%s=%v", f, originalValue)); err != nil { + if err := gate.(featuregate.MutableFeatureGate).Set(fmt.Sprintf("%s=%v", f, originalValue)); err != nil { tb.Errorf("error restoring %s=%v: %v", f, originalValue, err) } }