| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188 |
- /*
- Copyright 2024 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 features
- import (
- "fmt"
- "os"
- "strconv"
- "sync"
- "sync/atomic"
- "k8s.io/apimachinery/pkg/util/naming"
- utilruntime "k8s.io/apimachinery/pkg/util/runtime"
- "k8s.io/klog/v2"
- )
- // internalPackages are packages that ignored when creating a name for featureGates. These packages are in the common
- // call chains, so they'd be unhelpful as names.
- var internalPackages = []string{"k8s.io/client-go/features/envvar.go"}
- var _ Gates = &envVarFeatureGates{}
- // newEnvVarFeatureGates creates a feature gate that allows for registration
- // of features and checking if the features are enabled.
- //
- // On the first call to Enabled, the effective state of all known features is loaded from
- // environment variables. The environment variable read for a given feature is formed by
- // concatenating the prefix "KUBE_FEATURE_" with the feature's name.
- //
- // For example, if you have a feature named "MyFeature"
- // setting an environmental variable "KUBE_FEATURE_MyFeature"
- // will allow you to configure the state of that feature.
- //
- // Please note that environmental variables can only be set to the boolean value.
- // Incorrect values will be ignored and logged.
- //
- // Features can also be set directly via the Set method.
- // In that case, these features take precedence over
- // features set via environmental variables.
- func newEnvVarFeatureGates(features map[Feature]FeatureSpec) *envVarFeatureGates {
- known := map[Feature]FeatureSpec{}
- for name, spec := range features {
- known[name] = spec
- }
- fg := &envVarFeatureGates{
- callSiteName: naming.GetNameFromCallsite(internalPackages...),
- known: known,
- }
- fg.enabledViaEnvVar.Store(map[Feature]bool{})
- fg.enabledViaSetMethod = map[Feature]bool{}
- return fg
- }
- // envVarFeatureGates implements Gates and allows for feature registration.
- type envVarFeatureGates struct {
- // callSiteName holds the name of the file
- // that created this instance
- callSiteName string
- // readEnvVarsOnce guards reading environmental variables
- readEnvVarsOnce sync.Once
- // known holds known feature gates
- known map[Feature]FeatureSpec
- // enabledViaEnvVar holds a map[Feature]bool
- // with values explicitly set via env var
- enabledViaEnvVar atomic.Value
- // lockEnabledViaSetMethod protects enabledViaSetMethod
- lockEnabledViaSetMethod sync.RWMutex
- // enabledViaSetMethod holds values explicitly set
- // via Set method, features stored in this map take
- // precedence over features stored in enabledViaEnvVar
- enabledViaSetMethod map[Feature]bool
- // readEnvVars holds the boolean value which
- // indicates whether readEnvVarsOnce has been called.
- readEnvVars atomic.Bool
- }
- // Enabled returns true if the key is enabled. If the key is not known, this call will panic.
- func (f *envVarFeatureGates) Enabled(key Feature) bool {
- if v, ok := f.wasFeatureEnabledViaSetMethod(key); ok {
- // ensue that the state of all known features
- // is loaded from environment variables
- // on the first call to Enabled method.
- if !f.hasAlreadyReadEnvVar() {
- _ = f.getEnabledMapFromEnvVar()
- }
- return v
- }
- if v, ok := f.getEnabledMapFromEnvVar()[key]; ok {
- return v
- }
- if v, ok := f.known[key]; ok {
- return v.Default
- }
- panic(fmt.Errorf("feature %q is not registered in FeatureGates %q", key, f.callSiteName))
- }
- // Set sets the given feature to the given value.
- //
- // Features set via this method take precedence over
- // the features set via environment variables.
- func (f *envVarFeatureGates) Set(featureName Feature, featureValue bool) error {
- feature, ok := f.known[featureName]
- if !ok {
- return fmt.Errorf("feature %q is not registered in FeatureGates %q", featureName, f.callSiteName)
- }
- if feature.LockToDefault && feature.Default != featureValue {
- return fmt.Errorf("cannot set feature gate %q to %v, feature is locked to %v", featureName, featureValue, feature.Default)
- }
- f.lockEnabledViaSetMethod.Lock()
- defer f.lockEnabledViaSetMethod.Unlock()
- f.enabledViaSetMethod[featureName] = featureValue
- return nil
- }
- // getEnabledMapFromEnvVar will fill the enabled map on the first call.
- // This is the only time a known feature can be set to a value
- // read from the corresponding environmental variable.
- func (f *envVarFeatureGates) getEnabledMapFromEnvVar() map[Feature]bool {
- f.readEnvVarsOnce.Do(func() {
- featureGatesState := map[Feature]bool{}
- for feature, featureSpec := range f.known {
- featureState, featureStateSet := os.LookupEnv(fmt.Sprintf("KUBE_FEATURE_%s", feature))
- if !featureStateSet {
- continue
- }
- boolVal, boolErr := strconv.ParseBool(featureState)
- switch {
- case boolErr != nil:
- utilruntime.HandleError(fmt.Errorf("cannot set feature gate %q to %q, due to %v", feature, featureState, boolErr))
- case featureSpec.LockToDefault:
- if boolVal != featureSpec.Default {
- utilruntime.HandleError(fmt.Errorf("cannot set feature gate %q to %q, feature is locked to %v", feature, featureState, featureSpec.Default))
- break
- }
- featureGatesState[feature] = featureSpec.Default
- default:
- featureGatesState[feature] = boolVal
- }
- }
- f.enabledViaEnvVar.Store(featureGatesState)
- f.readEnvVars.Store(true)
- for feature, featureSpec := range f.known {
- if featureState, ok := featureGatesState[feature]; ok {
- klog.V(1).InfoS("Feature gate updated state", "feature", feature, "enabled", featureState)
- continue
- }
- klog.V(1).InfoS("Feature gate default state", "feature", feature, "enabled", featureSpec.Default)
- }
- })
- return f.enabledViaEnvVar.Load().(map[Feature]bool)
- }
- func (f *envVarFeatureGates) wasFeatureEnabledViaSetMethod(key Feature) (bool, bool) {
- f.lockEnabledViaSetMethod.RLock()
- defer f.lockEnabledViaSetMethod.RUnlock()
- value, found := f.enabledViaSetMethod[key]
- return value, found
- }
- func (f *envVarFeatureGates) hasAlreadyReadEnvVar() bool {
- return f.readEnvVars.Load()
- }
|