2024-05-15 06:54:18 +00:00
|
|
|
/*
|
|
|
|
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.
|
2024-08-19 08:01:33 +00:00
|
|
|
//
|
|
|
|
// Features can also be set directly via the Set method.
|
|
|
|
// In that case, these features take precedence over
|
|
|
|
// features set via environmental variables.
|
2024-05-15 06:54:18 +00:00
|
|
|
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,
|
|
|
|
}
|
2024-08-19 08:01:33 +00:00
|
|
|
fg.enabledViaEnvVar.Store(map[Feature]bool{})
|
|
|
|
fg.enabledViaSetMethod = map[Feature]bool{}
|
2024-05-15 06:54:18 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2024-08-19 08:01:33 +00:00
|
|
|
// enabledViaEnvVar holds a map[Feature]bool
|
2024-05-15 06:54:18 +00:00
|
|
|
// with values explicitly set via env var
|
2024-08-19 08:01:33 +00:00
|
|
|
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
|
2024-05-15 06:54:18 +00:00
|
|
|
|
|
|
|
// readEnvVars holds the boolean value which
|
|
|
|
// indicates whether readEnvVarsOnce has been called.
|
|
|
|
readEnvVars atomic.Bool
|
|
|
|
}
|
|
|
|
|
2024-08-19 08:01:33 +00:00
|
|
|
// Enabled returns true if the key is enabled. If the key is not known, this call will panic.
|
2024-05-15 06:54:18 +00:00
|
|
|
func (f *envVarFeatureGates) Enabled(key Feature) bool {
|
2024-08-19 08:01:33 +00:00
|
|
|
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
|
|
|
|
}
|
2024-05-15 06:54:18 +00:00
|
|
|
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))
|
|
|
|
}
|
|
|
|
|
2024-08-19 08:01:33 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2024-05-15 06:54:18 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
}
|
2024-08-19 08:01:33 +00:00
|
|
|
f.enabledViaEnvVar.Store(featureGatesState)
|
2024-05-15 06:54:18 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
})
|
2024-08-19 08:01:33 +00:00
|
|
|
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
|
2024-05-15 06:54:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (f *envVarFeatureGates) hasAlreadyReadEnvVar() bool {
|
|
|
|
return f.readEnvVars.Load()
|
|
|
|
}
|