You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
275 lines
7.1 KiB
275 lines
7.1 KiB
package feature |
|
|
|
import ( |
|
"flag" |
|
"fmt" |
|
"os" |
|
"sort" |
|
"strconv" |
|
"strings" |
|
"sync" |
|
"sync/atomic" |
|
|
|
"github.com/pkg/errors" |
|
) |
|
|
|
// Feature is the feature name |
|
type Feature string |
|
|
|
const ( |
|
flagName = "feature-gates" |
|
) |
|
|
|
var ( |
|
// DefaultGate is a shared global Gate. |
|
DefaultGate = NewGate() |
|
) |
|
|
|
// Spec is the spec of the feature |
|
type Spec struct { |
|
Default bool |
|
} |
|
|
|
// Gate parses and stores flag gates for known features from |
|
// a string like feature1=true,feature2=false,... |
|
type Gate interface { |
|
// AddFlag adds a flag for setting global feature gates to the specified FlagSet. |
|
AddFlag(fs *flag.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 |
|
// Enabled returns true if the key is enabled. |
|
Enabled(key Feature) bool |
|
// Add adds features to the featureGate. |
|
Add(features map[Feature]Spec) error |
|
// KnownFeatures returns a slice of strings describing the Gate's known features. |
|
KnownFeatures() []string |
|
// DeepCopy returns a deep copy of the Gate 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() Gate |
|
} |
|
|
|
// featureGate implements Gate as well as flag.Value for flag parsing. |
|
type featureGate struct { |
|
// lock guards writes to known, enabled, and reads/writes of closed |
|
lock sync.Mutex |
|
// known holds a map[Feature]Spec |
|
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 |
|
} |
|
|
|
// Set, String, and Type implement flag.Value |
|
var _ flag.Value = &featureGate{} |
|
|
|
// NewGate create a feature gate. |
|
func NewGate() *featureGate { |
|
known := map[Feature]Spec{} |
|
knownValue := atomic.Value{} |
|
knownValue.Store(known) |
|
|
|
enabled := map[Feature]bool{} |
|
enabledValue := atomic.Value{} |
|
enabledValue.Store(enabled) |
|
|
|
f := &featureGate{ |
|
known: knownValue, |
|
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 { |
|
f.lock.Lock() |
|
defer f.lock.Unlock() |
|
|
|
// Copy existing state |
|
known := map[Feature]Spec{} |
|
for k, v := range f.known.Load().(map[Feature]Spec) { |
|
known[k] = v |
|
} |
|
enabled := map[Feature]bool{} |
|
for k, v := range f.enabled.Load().(map[Feature]bool) { |
|
enabled[k] = v |
|
} |
|
|
|
for _, s := range strings.Split(value, ",") { |
|
if len(s) == 0 { |
|
continue |
|
} |
|
arr := strings.SplitN(s, "=", 2) |
|
k := Feature(strings.TrimSpace(arr[0])) |
|
_, ok := known[k] |
|
if !ok { |
|
return errors.Errorf("unrecognized key: %s", k) |
|
} |
|
if len(arr) != 2 { |
|
return errors.Errorf("missing bool value for %s", k) |
|
} |
|
v := strings.TrimSpace(arr[1]) |
|
boolValue, err := strconv.ParseBool(v) |
|
if err != nil { |
|
return errors.Errorf("invalid value of %s: %s, err: %v", k, v, err) |
|
} |
|
enabled[k] = boolValue |
|
} |
|
|
|
// Persist changes |
|
f.known.Store(known) |
|
f.enabled.Store(enabled) |
|
|
|
fmt.Fprintf(os.Stderr, "feature gates: %v", enabled) |
|
return nil |
|
} |
|
|
|
// 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]Spec{} |
|
for k, v := range f.known.Load().(map[Feature]Spec) { |
|
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) |
|
_, ok := known[k] |
|
if !ok { |
|
return errors.Errorf("unrecognized key: %s", k) |
|
} |
|
enabled[k] = v |
|
} |
|
|
|
// Persist changes |
|
f.known.Store(known) |
|
f.enabled.Store(enabled) |
|
|
|
fmt.Fprintf(os.Stderr, "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{} |
|
enabled, ok := f.enabled.Load().(map[Feature]bool) |
|
if !ok { |
|
return "" |
|
} |
|
for k, v := range enabled { |
|
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]Spec) error { |
|
f.lock.Lock() |
|
defer f.lock.Unlock() |
|
|
|
if f.closed { |
|
return errors.Errorf("cannot add a feature gate after adding it to the flag set") |
|
} |
|
|
|
// Copy existing state |
|
known := map[Feature]Spec{} |
|
for k, v := range f.known.Load().(map[Feature]Spec) { |
|
known[k] = v |
|
} |
|
|
|
for name, spec := range features { |
|
if existingSpec, found := known[name]; found { |
|
if existingSpec == spec { |
|
continue |
|
} |
|
return errors.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]Spec)[key].Default |
|
} |
|
|
|
// AddFlag adds a flag for setting global feature gates to the specified FlagSet. |
|
func (f *featureGate) AddFlag(fs *flag.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 Gate's known features. |
|
func (f *featureGate) KnownFeatures() []string { |
|
var known []string |
|
for k, v := range f.known.Load().(map[Feature]Spec) { |
|
known = append(known, fmt.Sprintf("%s=true|false (default=%t)", k, v.Default)) |
|
} |
|
sort.Strings(known) |
|
return known |
|
} |
|
|
|
// DeepCopy returns a deep copy of the Gate 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() Gate { |
|
// Copy existing state. |
|
known := map[Feature]Spec{} |
|
for k, v := range f.known.Load().(map[Feature]Spec) { |
|
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. |
|
// We maintain the value of f.closed across the copy. |
|
return &featureGate{ |
|
known: knownValue, |
|
enabled: enabledValue, |
|
closed: f.closed, |
|
} |
|
}
|
|
|