2024-08-19 08:01:33 +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 .
* /
2025-01-16 04:11:46 +00:00
package featuregate
2024-08-19 08:01:33 +00:00
import (
"fmt"
"sort"
"strings"
"sync"
"github.com/spf13/pflag"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/version"
cliflag "k8s.io/component-base/cli/flag"
2025-01-16 04:11:46 +00:00
baseversion "k8s.io/component-base/version"
2024-08-19 08:01:33 +00:00
"k8s.io/klog/v2"
)
// DefaultComponentGlobalsRegistry is the global var to store the effective versions and feature gates for all components for easy access.
// Example usage:
// // register the component effective version and feature gate first
// _, _ = utilversion.DefaultComponentGlobalsRegistry.ComponentGlobalsOrRegister(utilversion.DefaultKubeComponent, utilversion.DefaultKubeEffectiveVersion(), utilfeature.DefaultMutableFeatureGate)
// wardleEffectiveVersion := utilversion.NewEffectiveVersion("1.2")
// wardleFeatureGate := featuregate.NewFeatureGate()
// utilruntime.Must(utilversion.DefaultComponentGlobalsRegistry.Register(apiserver.WardleComponentName, wardleEffectiveVersion, wardleFeatureGate, false))
//
// cmd := &cobra.Command{
// ...
// // call DefaultComponentGlobalsRegistry.Set() in PersistentPreRunE
// PersistentPreRunE: func(*cobra.Command, []string) error {
// if err := utilversion.DefaultComponentGlobalsRegistry.Set(); err != nil {
// return err
// }
// ...
// },
// RunE: func(c *cobra.Command, args []string) error {
// // call utilversion.DefaultComponentGlobalsRegistry.Validate() somewhere
// },
// }
//
// flags := cmd.Flags()
// // add flags
// utilversion.DefaultComponentGlobalsRegistry.AddFlags(flags)
var DefaultComponentGlobalsRegistry ComponentGlobalsRegistry = NewComponentGlobalsRegistry ( )
const (
DefaultKubeComponent = "kube"
klogLevel = 2
)
type VersionMapping func ( from * version . Version ) * version . Version
// ComponentGlobals stores the global variables for a component for easy access.
type ComponentGlobals struct {
2025-01-16 04:11:46 +00:00
effectiveVersion baseversion . MutableEffectiveVersion
featureGate MutableVersionedFeatureGate
2024-08-19 08:01:33 +00:00
// emulationVersionMapping contains the mapping from the emulation version of this component
// to the emulation version of another component.
emulationVersionMapping map [ string ] VersionMapping
// dependentEmulationVersion stores whether or not this component's EmulationVersion is dependent through mapping on another component.
// If true, the emulation version cannot be set from the flag, or version mapping from another component.
dependentEmulationVersion bool
// minCompatibilityVersionMapping contains the mapping from the min compatibility version of this component
// to the min compatibility version of another component.
minCompatibilityVersionMapping map [ string ] VersionMapping
// dependentMinCompatibilityVersion stores whether or not this component's MinCompatibilityVersion is dependent through mapping on another component
// If true, the min compatibility version cannot be set from the flag, or version mapping from another component.
dependentMinCompatibilityVersion bool
}
type ComponentGlobalsRegistry interface {
// EffectiveVersionFor returns the EffectiveVersion registered under the component.
// Returns nil if the component is not registered.
2025-01-16 04:11:46 +00:00
EffectiveVersionFor ( component string ) baseversion . EffectiveVersion
2024-08-19 08:01:33 +00:00
// FeatureGateFor returns the FeatureGate registered under the component.
// Returns nil if the component is not registered.
2025-01-16 04:11:46 +00:00
FeatureGateFor ( component string ) FeatureGate
2024-08-19 08:01:33 +00:00
// Register registers the EffectiveVersion and FeatureGate for a component.
// returns error if the component is already registered.
2025-01-16 04:11:46 +00:00
Register ( component string , effectiveVersion baseversion . MutableEffectiveVersion , featureGate MutableVersionedFeatureGate ) error
2024-08-19 08:01:33 +00:00
// ComponentGlobalsOrRegister would return the registered global variables for the component if it already exists in the registry.
// Otherwise, the provided variables would be registered under the component, and the same variables would be returned.
2025-01-16 04:11:46 +00:00
ComponentGlobalsOrRegister ( component string , effectiveVersion baseversion . MutableEffectiveVersion , featureGate MutableVersionedFeatureGate ) ( baseversion . MutableEffectiveVersion , MutableVersionedFeatureGate )
2024-08-19 08:01:33 +00:00
// AddFlags adds flags of "--emulated-version" and "--feature-gates"
AddFlags ( fs * pflag . FlagSet )
// Set sets the flags for all global variables for all components registered.
Set ( ) error
// SetFallback calls Set() if it has never been called.
SetFallback ( ) error
// Validate calls the Validate() function for all the global variables for all components registered.
Validate ( ) [ ] error
// Reset removes all stored ComponentGlobals, configurations, and version mappings.
Reset ( )
// SetEmulationVersionMapping sets the mapping from the emulation version of one component
// to the emulation version of another component.
// Once set, the emulation version of the toComponent will be determined by the emulation version of the fromComponent,
// and cannot be set from cmd flags anymore.
// For a given component, its emulation version can only depend on one other component, no multiple dependency is allowed.
SetEmulationVersionMapping ( fromComponent , toComponent string , f VersionMapping ) error
}
type componentGlobalsRegistry struct {
componentGlobals map [ string ] * ComponentGlobals
mutex sync . RWMutex
// list of component name to emulation version set from the flag.
emulationVersionConfig [ ] string
// map of component name to the list of feature gates set from the flag.
featureGatesConfig map [ string ] [ ] string
// set stores if the Set() function for the registry is already called.
set bool
}
func NewComponentGlobalsRegistry ( ) * componentGlobalsRegistry {
return & componentGlobalsRegistry {
componentGlobals : make ( map [ string ] * ComponentGlobals ) ,
emulationVersionConfig : nil ,
featureGatesConfig : nil ,
}
}
func ( r * componentGlobalsRegistry ) Reset ( ) {
r . mutex . Lock ( )
defer r . mutex . Unlock ( )
r . componentGlobals = make ( map [ string ] * ComponentGlobals )
r . emulationVersionConfig = nil
r . featureGatesConfig = nil
r . set = false
}
2025-01-16 04:11:46 +00:00
func ( r * componentGlobalsRegistry ) EffectiveVersionFor ( component string ) baseversion . EffectiveVersion {
2024-08-19 08:01:33 +00:00
r . mutex . RLock ( )
defer r . mutex . RUnlock ( )
globals , ok := r . componentGlobals [ component ]
if ! ok {
return nil
}
return globals . effectiveVersion
}
2025-01-16 04:11:46 +00:00
func ( r * componentGlobalsRegistry ) FeatureGateFor ( component string ) FeatureGate {
2024-08-19 08:01:33 +00:00
r . mutex . RLock ( )
defer r . mutex . RUnlock ( )
globals , ok := r . componentGlobals [ component ]
if ! ok {
return nil
}
return globals . featureGate
}
2025-01-16 04:11:46 +00:00
func ( r * componentGlobalsRegistry ) unsafeRegister ( component string , effectiveVersion baseversion . MutableEffectiveVersion , featureGate MutableVersionedFeatureGate ) error {
2024-08-19 08:01:33 +00:00
if _ , ok := r . componentGlobals [ component ] ; ok {
return fmt . Errorf ( "component globals of %s already registered" , component )
}
if featureGate != nil {
if err := featureGate . SetEmulationVersion ( effectiveVersion . EmulationVersion ( ) ) ; err != nil {
return err
}
}
c := ComponentGlobals {
effectiveVersion : effectiveVersion ,
featureGate : featureGate ,
emulationVersionMapping : make ( map [ string ] VersionMapping ) ,
minCompatibilityVersionMapping : make ( map [ string ] VersionMapping ) ,
}
r . componentGlobals [ component ] = & c
return nil
}
2025-01-16 04:11:46 +00:00
func ( r * componentGlobalsRegistry ) Register ( component string , effectiveVersion baseversion . MutableEffectiveVersion , featureGate MutableVersionedFeatureGate ) error {
2024-08-19 08:01:33 +00:00
if effectiveVersion == nil {
return fmt . Errorf ( "cannot register nil effectiveVersion" )
}
r . mutex . Lock ( )
defer r . mutex . Unlock ( )
return r . unsafeRegister ( component , effectiveVersion , featureGate )
}
2025-01-16 04:11:46 +00:00
func ( r * componentGlobalsRegistry ) ComponentGlobalsOrRegister ( component string , effectiveVersion baseversion . MutableEffectiveVersion , featureGate MutableVersionedFeatureGate ) ( baseversion . MutableEffectiveVersion , MutableVersionedFeatureGate ) {
2024-08-19 08:01:33 +00:00
r . mutex . Lock ( )
defer r . mutex . Unlock ( )
globals , ok := r . componentGlobals [ component ]
if ok {
return globals . effectiveVersion , globals . featureGate
}
utilruntime . Must ( r . unsafeRegister ( component , effectiveVersion , featureGate ) )
return effectiveVersion , featureGate
}
func ( r * componentGlobalsRegistry ) unsafeKnownFeatures ( ) [ ] string {
var known [ ] string
for component , globals := range r . componentGlobals {
if globals . featureGate == nil {
continue
}
for _ , f := range globals . featureGate . KnownFeatures ( ) {
known = append ( known , component + ":" + f )
}
}
sort . Strings ( known )
return known
}
func ( r * componentGlobalsRegistry ) unsafeVersionFlagOptions ( isEmulation bool ) [ ] string {
var vs [ ] string
for component , globals := range r . componentGlobals {
binaryVer := globals . effectiveVersion . BinaryVersion ( )
if isEmulation {
if globals . dependentEmulationVersion {
continue
}
// emulated version could be between binaryMajor.{binaryMinor} and binaryMajor.{binaryMinor}
// TODO: change to binaryMajor.{binaryMinor-1} and binaryMajor.{binaryMinor} in 1.32
vs = append ( vs , fmt . Sprintf ( "%s=%s..%s (default=%s)" , component ,
binaryVer . SubtractMinor ( 0 ) . String ( ) , binaryVer . String ( ) , globals . effectiveVersion . EmulationVersion ( ) . String ( ) ) )
} else {
if globals . dependentMinCompatibilityVersion {
continue
}
// min compatibility version could be between binaryMajor.{binaryMinor-1} and binaryMajor.{binaryMinor}
vs = append ( vs , fmt . Sprintf ( "%s=%s..%s (default=%s)" , component ,
binaryVer . SubtractMinor ( 1 ) . String ( ) , binaryVer . String ( ) , globals . effectiveVersion . MinCompatibilityVersion ( ) . String ( ) ) )
}
}
sort . Strings ( vs )
return vs
}
func ( r * componentGlobalsRegistry ) AddFlags ( fs * pflag . FlagSet ) {
if r == nil {
return
}
r . mutex . Lock ( )
defer r . mutex . Unlock ( )
for _ , globals := range r . componentGlobals {
if globals . featureGate != nil {
globals . featureGate . Close ( )
}
}
if r . emulationVersionConfig != nil || r . featureGatesConfig != nil {
klog . Warning ( "calling componentGlobalsRegistry.AddFlags more than once, the registry will be set by the latest flags" )
}
r . emulationVersionConfig = [ ] string { }
r . featureGatesConfig = make ( map [ string ] [ ] string )
fs . StringSliceVar ( & r . emulationVersionConfig , "emulated-version" , r . emulationVersionConfig , "" +
"The versions different components emulate their capabilities (APIs, features, ...) of.\n" +
"If set, the component will emulate the behavior of this version instead of the underlying binary version.\n" +
"Version format could only be major.minor, for example: '--emulated-version=wardle=1.2,kube=1.31'. Options are:\n" + strings . Join ( r . unsafeVersionFlagOptions ( true ) , "\n" ) +
"If the component is not specified, defaults to \"kube\"" )
fs . Var ( cliflag . NewColonSeparatedMultimapStringStringAllowDefaultEmptyKey ( & r . featureGatesConfig ) , "feature-gates" , "Comma-separated list of component:key=value pairs that describe feature gates for alpha/experimental features of different components.\n" +
"If the component is not specified, defaults to \"kube\". This flag can be repeatedly invoked. For example: --feature-gates 'wardle:featureA=true,wardle:featureB=false' --feature-gates 'kube:featureC=true'" +
"Options are:\n" + strings . Join ( r . unsafeKnownFeatures ( ) , "\n" ) )
}
type componentVersion struct {
component string
ver * version . Version
}
// getFullEmulationVersionConfig expands the given version config with version registered version mapping,
// and returns the map of component to Version.
func ( r * componentGlobalsRegistry ) getFullEmulationVersionConfig (
versionConfigMap map [ string ] * version . Version ) ( map [ string ] * version . Version , error ) {
result := map [ string ] * version . Version { }
setQueue := [ ] componentVersion { }
for comp , ver := range versionConfigMap {
if _ , ok := r . componentGlobals [ comp ] ; ! ok {
return result , fmt . Errorf ( "component not registered: %s" , comp )
}
klog . V ( klogLevel ) . Infof ( "setting version %s=%s" , comp , ver . String ( ) )
setQueue = append ( setQueue , componentVersion { comp , ver } )
}
for len ( setQueue ) > 0 {
cv := setQueue [ 0 ]
if _ , visited := result [ cv . component ] ; visited {
return result , fmt . Errorf ( "setting version of %s more than once, probably version mapping loop" , cv . component )
}
setQueue = setQueue [ 1 : ]
result [ cv . component ] = cv . ver
for toComp , f := range r . componentGlobals [ cv . component ] . emulationVersionMapping {
toVer := f ( cv . ver )
if toVer == nil {
return result , fmt . Errorf ( "got nil version from mapping of %s=%s to component:%s" , cv . component , cv . ver . String ( ) , toComp )
}
klog . V ( klogLevel ) . Infof ( "setting version %s=%s from version mapping of %s=%s" , toComp , toVer . String ( ) , cv . component , cv . ver . String ( ) )
setQueue = append ( setQueue , componentVersion { toComp , toVer } )
}
}
return result , nil
}
func toVersionMap ( versionConfig [ ] string ) ( map [ string ] * version . Version , error ) {
m := map [ string ] * version . Version { }
for _ , compVer := range versionConfig {
// default to "kube" of component is not specified
k := "kube"
v := compVer
if strings . Contains ( compVer , "=" ) {
arr := strings . SplitN ( compVer , "=" , 2 )
if len ( arr ) != 2 {
return m , fmt . Errorf ( "malformed pair, expect string=string" )
}
k = strings . TrimSpace ( arr [ 0 ] )
v = strings . TrimSpace ( arr [ 1 ] )
}
ver , err := version . Parse ( v )
if err != nil {
return m , err
}
if ver . Patch ( ) != 0 {
return m , fmt . Errorf ( "patch version not allowed, got: %s=%s" , k , ver . String ( ) )
}
if existingVer , ok := m [ k ] ; ok {
return m , fmt . Errorf ( "duplicate version flag, %s=%s and %s=%s" , k , existingVer . String ( ) , k , ver . String ( ) )
}
m [ k ] = ver
}
return m , nil
}
func ( r * componentGlobalsRegistry ) SetFallback ( ) error {
r . mutex . Lock ( )
set := r . set
r . mutex . Unlock ( )
if set {
return nil
}
klog . Warning ( "setting componentGlobalsRegistry in SetFallback. We recommend calling componentGlobalsRegistry.Set()" +
" right after parsing flags to avoid using feature gates before their final values are set by the flags." )
return r . Set ( )
}
func ( r * componentGlobalsRegistry ) Set ( ) error {
r . mutex . Lock ( )
defer r . mutex . Unlock ( )
r . set = true
emulationVersionConfigMap , err := toVersionMap ( r . emulationVersionConfig )
if err != nil {
return err
}
for comp := range emulationVersionConfigMap {
if _ , ok := r . componentGlobals [ comp ] ; ! ok {
return fmt . Errorf ( "component not registered: %s" , comp )
}
// only components without any dependencies can be set from the flag.
if r . componentGlobals [ comp ] . dependentEmulationVersion {
return fmt . Errorf ( "EmulationVersion of %s is set by mapping, cannot set it by flag" , comp )
}
}
if emulationVersions , err := r . getFullEmulationVersionConfig ( emulationVersionConfigMap ) ; err != nil {
return err
} else {
for comp , ver := range emulationVersions {
r . componentGlobals [ comp ] . effectiveVersion . SetEmulationVersion ( ver )
}
}
// Set feature gate emulation version before setting feature gate flag values.
for comp , globals := range r . componentGlobals {
if globals . featureGate == nil {
continue
}
klog . V ( klogLevel ) . Infof ( "setting %s:feature gate emulation version to %s" , comp , globals . effectiveVersion . EmulationVersion ( ) . String ( ) )
if err := globals . featureGate . SetEmulationVersion ( globals . effectiveVersion . EmulationVersion ( ) ) ; err != nil {
return err
}
}
for comp , fg := range r . featureGatesConfig {
if comp == "" {
if _ , ok := r . featureGatesConfig [ DefaultKubeComponent ] ; ok {
return fmt . Errorf ( "set kube feature gates with default empty prefix or kube: prefix consistently, do not mix use" )
}
comp = DefaultKubeComponent
}
if _ , ok := r . componentGlobals [ comp ] ; ! ok {
return fmt . Errorf ( "component not registered: %s" , comp )
}
featureGate := r . componentGlobals [ comp ] . featureGate
if featureGate == nil {
return fmt . Errorf ( "component featureGate not registered: %s" , comp )
}
flagVal := strings . Join ( fg , "," )
klog . V ( klogLevel ) . Infof ( "setting %s:feature-gates=%s" , comp , flagVal )
if err := featureGate . Set ( flagVal ) ; err != nil {
return err
}
}
return nil
}
func ( r * componentGlobalsRegistry ) Validate ( ) [ ] error {
var errs [ ] error
r . mutex . Lock ( )
defer r . mutex . Unlock ( )
for _ , globals := range r . componentGlobals {
errs = append ( errs , globals . effectiveVersion . Validate ( ) ... )
if globals . featureGate != nil {
errs = append ( errs , globals . featureGate . Validate ( ) ... )
}
}
return errs
}
func ( r * componentGlobalsRegistry ) SetEmulationVersionMapping ( fromComponent , toComponent string , f VersionMapping ) error {
if f == nil {
return nil
}
klog . V ( klogLevel ) . Infof ( "setting EmulationVersion mapping from %s to %s" , fromComponent , toComponent )
r . mutex . Lock ( )
defer r . mutex . Unlock ( )
if _ , ok := r . componentGlobals [ fromComponent ] ; ! ok {
return fmt . Errorf ( "component not registered: %s" , fromComponent )
}
if _ , ok := r . componentGlobals [ toComponent ] ; ! ok {
return fmt . Errorf ( "component not registered: %s" , toComponent )
}
// check multiple dependency
if r . componentGlobals [ toComponent ] . dependentEmulationVersion {
return fmt . Errorf ( "mapping of %s already exists from another component" , toComponent )
}
r . componentGlobals [ toComponent ] . dependentEmulationVersion = true
versionMapping := r . componentGlobals [ fromComponent ] . emulationVersionMapping
if _ , ok := versionMapping [ toComponent ] ; ok {
return fmt . Errorf ( "EmulationVersion from %s to %s already exists" , fromComponent , toComponent )
}
versionMapping [ toComponent ] = f
klog . V ( klogLevel ) . Infof ( "setting the default EmulationVersion of %s based on mapping from the default EmulationVersion of %s" , fromComponent , toComponent )
defaultFromVersion := r . componentGlobals [ fromComponent ] . effectiveVersion . EmulationVersion ( )
emulationVersions , err := r . getFullEmulationVersionConfig ( map [ string ] * version . Version { fromComponent : defaultFromVersion } )
if err != nil {
return err
}
for comp , ver := range emulationVersions {
r . componentGlobals [ comp ] . effectiveVersion . SetEmulationVersion ( ver )
}
return nil
}