Add version mapping in ComponentGlobalsRegistry.

Signed-off-by: Siyuan Zhang <sizhang@google.com>

Kubernetes-commit: 4352c4ad2762ce49ce30e62381f8ceb24723fbcc
This commit is contained in:
Siyuan Zhang 2024-05-31 20:29:48 -07:00 committed by Kubernetes Publisher
parent c80af88d21
commit 00857ca9ec
11 changed files with 471 additions and 257 deletions

View File

@ -44,7 +44,6 @@ import (
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/version"
utilwaitgroup "k8s.io/apimachinery/pkg/util/waitgroup"
apimachineryversion "k8s.io/apimachinery/pkg/version"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/audit"
"k8s.io/apiserver/pkg/authentication/authenticator"
@ -150,8 +149,6 @@ type Config struct {
// done values in this values for this map are ignored.
PostStartHooks map[string]PostStartHookConfigEntry
// Version will enable the /version endpoint if non-nil
Version *apimachineryversion.Info
// EffectiveVersion determines which apis and features are available
// based on when the api/feature lifecyle.
EffectiveVersion utilversion.EffectiveVersion
@ -702,12 +699,8 @@ func (c *Config) Complete(informers informers.SharedInformerFactory) CompletedCo
}
c.ExternalAddress = net.JoinHostPort(c.ExternalAddress, strconv.Itoa(port))
}
var ver *version.Version
if c.EffectiveVersion != nil {
ver = c.EffectiveVersion.EmulationVersion()
}
completeOpenAPI(c.OpenAPIConfig, ver)
completeOpenAPIV3(c.OpenAPIV3Config, ver)
completeOpenAPI(c.OpenAPIConfig, c.EffectiveVersion.EmulationVersion())
completeOpenAPIV3(c.OpenAPIV3Config, c.EffectiveVersion.EmulationVersion())
if c.DiscoveryAddresses == nil {
c.DiscoveryAddresses = discovery.DefaultAddresses{DefaultAddress: c.ExternalAddress}
@ -834,7 +827,6 @@ func (c completedConfig) New(name string, delegationTarget DelegationTarget) (*G
StorageVersionManager: c.StorageVersionManager,
EffectiveVersion: c.EffectiveVersion,
Version: c.Version,
FeatureGate: c.FeatureGate,
muxAndDiscoveryCompleteSignals: map[string]<-chan struct{}{},
@ -1103,7 +1095,7 @@ func installAPI(s *GenericAPIServer, c *Config) {
}
}
routes.Version{Version: c.Version}.Install(s.Handler.GoRestfulContainer)
routes.Version{Version: c.EffectiveVersion.BinaryVersion().Info()}.Install(s.Handler.GoRestfulContainer)
if c.EnableDiscovery {
if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.AggregatedDiscoveryEndpoint) {

View File

@ -40,6 +40,7 @@ import (
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/server/healthz"
utilversion "k8s.io/apiserver/pkg/util/version"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/rest"
@ -90,6 +91,7 @@ func TestNewWithDelegate(t *testing.T) {
delegateConfig.PublicAddress = netutils.ParseIPSloppy("192.168.10.4")
delegateConfig.LegacyAPIGroupPrefixes = sets.NewString("/api")
delegateConfig.LoopbackClientConfig = &rest.Config{}
delegateConfig.EffectiveVersion = utilversion.NewEffectiveVersion("")
clientset := fake.NewSimpleClientset()
if clientset == nil {
t.Fatal("unable to create fake client set")
@ -122,6 +124,7 @@ func TestNewWithDelegate(t *testing.T) {
wrappingConfig.PublicAddress = netutils.ParseIPSloppy("192.168.10.4")
wrappingConfig.LegacyAPIGroupPrefixes = sets.NewString("/api")
wrappingConfig.LoopbackClientConfig = &rest.Config{}
wrappingConfig.EffectiveVersion = utilversion.NewEffectiveVersion("")
wrappingConfig.HealthzChecks = append(wrappingConfig.HealthzChecks, healthz.NamedCheck("wrapping-health", func(r *http.Request) error {
return fmt.Errorf("wrapping failed healthcheck")

View File

@ -100,7 +100,7 @@ func (e *resourceExpirationEvaluator) shouldServe(gv schema.GroupVersion, versio
}
introduced, ok := versionedPtr.(introducedInterface)
// skip the introduced check for test where currentVersion is 0.0
// skip the introduced check for test when currentVersion is 0.0 to test all apis
if ok && (e.currentVersion.Major() > 0 || e.currentVersion.Minor() > 0) {
majorIntroduced, minorIntroduced := introduced.APILifecycleIntroduced()
verIntroduced := apimachineryversion.MajorMinor(uint(majorIntroduced), uint(minorIntroduced))

View File

@ -40,7 +40,6 @@ import (
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/wait"
utilwaitgroup "k8s.io/apimachinery/pkg/util/waitgroup"
"k8s.io/apimachinery/pkg/version"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/audit"
"k8s.io/apiserver/pkg/authorization/authorizer"
@ -238,8 +237,6 @@ type GenericAPIServer struct {
// StorageVersionManager holds the storage versions of the API resources installed by this server.
StorageVersionManager storageversion.Manager
// Version will enable the /version endpoint if non-nil
Version *version.Info
// EffectiveVersion determines which apis and features are available
// based on when the api/feature lifecyle.
EffectiveVersion utilversion.EffectiveVersion

View File

@ -138,7 +138,7 @@ func setUp(t *testing.T) (Config, *assert.Assertions) {
if clientset == nil {
t.Fatal("unable to create fake client set")
}
config.EffectiveVersion = utilversion.NewEffectiveVersion("")
config.OpenAPIConfig = DefaultOpenAPIConfig(testGetOpenAPIDefinitions, openapinamer.NewDefinitionNamer(runtime.NewScheme()))
config.OpenAPIConfig.Info.Version = "unversioned"
config.OpenAPIV3Config = DefaultOpenAPIV3Config(testGetOpenAPIDefinitions, openapinamer.NewDefinitionNamer(runtime.NewScheme()))
@ -460,8 +460,9 @@ func TestNotRestRoutesHaveAuth(t *testing.T) {
config.EnableProfiling = true
kubeVersion := fakeVersion()
config.Version = &kubeVersion
config.EffectiveVersion = utilversion.NewEffectiveVersion(kubeVersion.String())
effectiveVersion := utilversion.NewEffectiveVersion(kubeVersion.String())
effectiveVersion.Set(effectiveVersion.BinaryVersion().WithInfo(kubeVersion), effectiveVersion.EmulationVersion(), effectiveVersion.MinCompatibilityVersion())
config.EffectiveVersion = effectiveVersion
s, err := config.Complete(nil).New("test", NewEmptyDelegate())
if err != nil {

View File

@ -278,7 +278,6 @@ func TestServerRunWithSNI(t *testing.T) {
// launch server
config := setUp(t)
v := fakeVersion()
config.Version = &v
config.EffectiveVersion = utilversion.NewEffectiveVersion(v.String())
config.EnableIndex = true

View File

@ -157,6 +157,7 @@ func emulatedStorageVersion(binaryVersionOfResource schema.GroupVersion, example
}
// If it was introduced after current compatibility version, don't use it
// skip the introduced check for test when currentVersion is 0.0 to test all apis
if introduced, hasIntroduced := exampleOfGVK.(introducedInterface); hasIntroduced && (compatibilityVersion.Major() > 0 || compatibilityVersion.Minor() > 0) {
// API resource lifecycles should be relative to k8s api version
majorIntroduced, minorIntroduced := introduced.APILifecycleIntroduced()

View File

@ -59,12 +59,29 @@ var DefaultComponentGlobalsRegistry ComponentGlobalsRegistry = NewComponentGloba
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 {
effectiveVersion MutableEffectiveVersion
featureGate featuregate.MutableVersionedFeatureGate
// 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 {
@ -75,9 +92,8 @@ type ComponentGlobalsRegistry interface {
// Returns nil if the component is not registered.
FeatureGateFor(component string) featuregate.FeatureGate
// Register registers the EffectiveVersion and FeatureGate for a component.
// Overrides existing ComponentGlobals if it is already in the registry if override is true,
// otherwise returns error if the component is already registered.
Register(component string, effectiveVersion MutableEffectiveVersion, featureGate featuregate.MutableVersionedFeatureGate, override bool) error
// returns error if the component is already registered.
Register(component string, effectiveVersion MutableEffectiveVersion, featureGate featuregate.MutableVersionedFeatureGate) error
// 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.
ComponentGlobalsOrRegister(component string, effectiveVersion MutableEffectiveVersion, featureGate featuregate.MutableVersionedFeatureGate) (MutableEffectiveVersion, featuregate.MutableVersionedFeatureGate)
@ -85,25 +101,43 @@ type ComponentGlobalsRegistry interface {
AddFlags(fs *pflag.FlagSet)
// Set sets the flags for all global variables for all components registered.
Set() error
// SetAllComponents calls the Validate() function for all the global variables for all components registered.
// 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
componentGlobals map[string]*ComponentGlobals
mutex sync.RWMutex
// map of component name to emulation version set from the flag.
emulationVersionConfig cliflag.ConfigurationMap
// 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
}
func NewComponentGlobalsRegistry() ComponentGlobalsRegistry {
func NewComponentGlobalsRegistry() *componentGlobalsRegistry {
return &componentGlobalsRegistry{
componentGlobals: make(map[string]ComponentGlobals),
componentGlobals: make(map[string]*ComponentGlobals),
emulationVersionConfig: nil,
featureGatesConfig: nil,
}
}
func (r *componentGlobalsRegistry) Reset() {
r.mutex.RLock()
defer r.mutex.RUnlock()
r.componentGlobals = make(map[string]*ComponentGlobals)
r.emulationVersionConfig = nil
r.featureGatesConfig = nil
}
func (r *componentGlobalsRegistry) EffectiveVersionFor(component string) EffectiveVersion {
r.mutex.RLock()
defer r.mutex.RUnlock()
@ -124,8 +158,8 @@ func (r *componentGlobalsRegistry) FeatureGateFor(component string) featuregate.
return globals.featureGate
}
func (r *componentGlobalsRegistry) unsafeRegister(component string, effectiveVersion MutableEffectiveVersion, featureGate featuregate.MutableVersionedFeatureGate, override bool) error {
if _, ok := r.componentGlobals[component]; ok && !override {
func (r *componentGlobalsRegistry) unsafeRegister(component string, effectiveVersion MutableEffectiveVersion, featureGate featuregate.MutableVersionedFeatureGate) error {
if _, ok := r.componentGlobals[component]; ok {
return fmt.Errorf("component globals of %s already registered", component)
}
if featureGate != nil {
@ -133,18 +167,23 @@ func (r *componentGlobalsRegistry) unsafeRegister(component string, effectiveVer
return err
}
}
c := ComponentGlobals{effectiveVersion: effectiveVersion, featureGate: featureGate}
r.componentGlobals[component] = c
c := ComponentGlobals{
effectiveVersion: effectiveVersion,
featureGate: featureGate,
emulationVersionMapping: make(map[string]VersionMapping),
minCompatibilityVersionMapping: make(map[string]VersionMapping),
}
r.componentGlobals[component] = &c
return nil
}
func (r *componentGlobalsRegistry) Register(component string, effectiveVersion MutableEffectiveVersion, featureGate featuregate.MutableVersionedFeatureGate, override bool) error {
func (r *componentGlobalsRegistry) Register(component string, effectiveVersion MutableEffectiveVersion, featureGate featuregate.MutableVersionedFeatureGate) error {
if effectiveVersion == nil {
return fmt.Errorf("cannot register nil effectiveVersion")
}
r.mutex.Lock()
defer r.mutex.Unlock()
return r.unsafeRegister(component, effectiveVersion, featureGate, override)
return r.unsafeRegister(component, effectiveVersion, featureGate)
}
func (r *componentGlobalsRegistry) ComponentGlobalsOrRegister(component string, effectiveVersion MutableEffectiveVersion, featureGate featuregate.MutableVersionedFeatureGate) (MutableEffectiveVersion, featuregate.MutableVersionedFeatureGate) {
@ -154,13 +193,11 @@ func (r *componentGlobalsRegistry) ComponentGlobalsOrRegister(component string,
if ok {
return globals.effectiveVersion, globals.featureGate
}
utilruntime.Must(r.unsafeRegister(component, effectiveVersion, featureGate, false))
utilruntime.Must(r.unsafeRegister(component, effectiveVersion, featureGate))
return effectiveVersion, featureGate
}
func (r *componentGlobalsRegistry) knownFeatures() []string {
r.mutex.Lock()
defer r.mutex.Unlock()
func (r *componentGlobalsRegistry) unsafeKnownFeatures() []string {
var known []string
for component, globals := range r.componentGlobals {
if globals.featureGate == nil {
@ -174,18 +211,22 @@ func (r *componentGlobalsRegistry) knownFeatures() []string {
return known
}
func (r *componentGlobalsRegistry) versionFlagOptions(isEmulation bool) []string {
r.mutex.Lock()
defer r.mutex.Unlock()
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()))
@ -200,51 +241,133 @@ func (r *componentGlobalsRegistry) AddFlags(fs *pflag.FlagSet) {
return
}
r.mutex.Lock()
defer r.mutex.Unlock()
for _, globals := range r.componentGlobals {
if globals.featureGate != nil {
globals.featureGate.Close()
}
}
r.emulationVersionConfig = make(cliflag.ConfigurationMap)
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)
r.mutex.Unlock()
fs.Var(&r.emulationVersionConfig, "emulated-version", ""+
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.versionFlagOptions(true), "\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.knownFeatures(), "\n"))
"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) Set() error {
r.mutex.Lock()
defer r.mutex.Unlock()
for comp, emuVer := range r.emulationVersionConfig {
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)
}
klog.V(2).Infof("setting %s:emulation version to %s\n", comp, emuVer)
v, err := version.Parse(emuVer)
if err != nil {
return err
// 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)
}
r.componentGlobals[comp].effectiveVersion.SetEmulationVersion(v)
}
// Set feature gate emulation version before setting feature gate flag values.
for comp, globals := range r.componentGlobals {
if globals.featureGate == nil {
continue
}
klog.V(2).Infof("setting %s:feature gate emulation version to %s\n", comp, globals.effectiveVersion.EmulationVersion().String())
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 {
@ -255,7 +378,7 @@ func (r *componentGlobalsRegistry) Set() error {
return fmt.Errorf("component featureGate not registered: %s", comp)
}
flagVal := strings.Join(fg, ",")
klog.V(2).Infof("setting %s:feature-gates=%s\n", comp, flagVal)
klog.V(klogLevel).Infof("setting %s:feature-gates=%s", comp, flagVal)
if err := featureGate.Set(flagVal); err != nil {
return err
}
@ -275,3 +398,39 @@ func (r *componentGlobalsRegistry) Validate() []error {
}
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
}

View File

@ -22,8 +22,8 @@ import (
"testing"
"github.com/spf13/pflag"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/version"
cliflag "k8s.io/component-base/cli/flag"
"k8s.io/component-base/featuregate"
)
@ -39,30 +39,23 @@ func TestEffectiveVersionRegistry(t *testing.T) {
if r.EffectiveVersionFor(testComponent) != nil {
t.Fatalf("expected nil EffectiveVersion initially")
}
if err := r.Register(testComponent, ver1, nil, false); err != nil {
if err := r.Register(testComponent, ver1, nil); err != nil {
t.Fatalf("expected no error to register new component, but got err: %v", err)
}
if !r.EffectiveVersionFor(testComponent).EqualTo(ver1) {
t.Fatalf("expected EffectiveVersionFor to return the version registered")
}
// overwrite
if err := r.Register(testComponent, ver2, nil, false); err == nil {
if err := r.Register(testComponent, ver2, nil); err == nil {
t.Fatalf("expected error to register existing component when override is false")
}
if err := r.Register(testComponent, ver2, nil, true); err != nil {
t.Fatalf("expected no error to overriding existing component, but got err: %v", err)
}
if !r.EffectiveVersionFor(testComponent).EqualTo(ver2) {
if !r.EffectiveVersionFor(testComponent).EqualTo(ver1) {
t.Fatalf("expected EffectiveVersionFor to return the version overridden")
}
}
func testRegistry(t *testing.T) *componentGlobalsRegistry {
r := componentGlobalsRegistry{
componentGlobals: map[string]ComponentGlobals{},
emulationVersionConfig: make(cliflag.ConfigurationMap),
featureGatesConfig: make(map[string][]string),
}
r := NewComponentGlobalsRegistry()
verKube := NewEffectiveVersion("1.31")
fgKube := featuregate.NewVersionedFeatureGate(version.MustParse("0.0"))
err := fgKube.AddVersioned(map[featuregate.Feature]featuregate.VersionedSpecs{
@ -102,19 +95,35 @@ func testRegistry(t *testing.T) *componentGlobalsRegistry {
if err != nil {
t.Fatal(err)
}
_ = r.Register(DefaultKubeComponent, verKube, fgKube, true)
_ = r.Register(testComponent, verTest, fgTest, true)
return &r
utilruntime.Must(r.Register(DefaultKubeComponent, verKube, fgKube))
utilruntime.Must(r.Register(testComponent, verTest, fgTest))
return r
}
func TestVersionFlagOptions(t *testing.T) {
r := testRegistry(t)
emuVers := strings.Join(r.versionFlagOptions(true), "\n")
emuVers := strings.Join(r.unsafeVersionFlagOptions(true), "\n")
expectedEmuVers := "kube=1.31..1.31 (default=1.31)\ntest=2.8..2.8 (default=2.8)"
if emuVers != expectedEmuVers {
t.Errorf("wanted emulation version flag options to be: %s, got %s", expectedEmuVers, emuVers)
}
minCompVers := strings.Join(r.versionFlagOptions(false), "\n")
minCompVers := strings.Join(r.unsafeVersionFlagOptions(false), "\n")
expectedMinCompVers := "kube=1.30..1.31 (default=1.30)\ntest=2.7..2.8 (default=2.7)"
if minCompVers != expectedMinCompVers {
t.Errorf("wanted min compatibility version flag options to be: %s, got %s", expectedMinCompVers, minCompVers)
}
}
func TestVersionFlagOptionsWithMapping(t *testing.T) {
r := testRegistry(t)
utilruntime.Must(r.SetEmulationVersionMapping(testComponent, DefaultKubeComponent,
func(from *version.Version) *version.Version { return from.OffsetMinor(3) }))
emuVers := strings.Join(r.unsafeVersionFlagOptions(true), "\n")
expectedEmuVers := "test=2.8..2.8 (default=2.8)"
if emuVers != expectedEmuVers {
t.Errorf("wanted emulation version flag options to be: %s, got %s", expectedEmuVers, emuVers)
}
minCompVers := strings.Join(r.unsafeVersionFlagOptions(false), "\n")
expectedMinCompVers := "kube=1.30..1.31 (default=1.30)\ntest=2.7..2.8 (default=2.7)"
if minCompVers != expectedMinCompVers {
t.Errorf("wanted min compatibility version flag options to be: %s, got %s", expectedMinCompVers, minCompVers)
@ -123,7 +132,7 @@ func TestVersionFlagOptions(t *testing.T) {
func TestVersionedFeatureGateFlag(t *testing.T) {
r := testRegistry(t)
known := strings.Join(r.knownFeatures(), "\n")
known := strings.Join(r.unsafeKnownFeatures(), "\n")
expectedKnown := "kube:AllAlpha=true|false (ALPHA - default=false)\n" +
"kube:AllBeta=true|false (BETA - default=false)\n" +
"kube:commonC=true|false (BETA - default=true)\n" +
@ -140,86 +149,127 @@ func TestVersionedFeatureGateFlag(t *testing.T) {
func TestFlags(t *testing.T) {
tests := []struct {
name string
emulationVersionFlag string
featureGatesFlag string
flags []string
parseError string
expectedKubeEmulationVersion *version.Version
expectedTestEmulationVersion *version.Version
expectedKubeEmulationVersion string
expectedTestEmulationVersion string
expectedKubeFeatureValues map[featuregate.Feature]bool
expectedTestFeatureValues map[featuregate.Feature]bool
}{
{
name: "setting kube emulation version",
emulationVersionFlag: "kube=1.30",
expectedKubeEmulationVersion: version.MajorMinor(1, 30),
flags: []string{"--emulated-version=kube=1.30"},
expectedKubeEmulationVersion: "1.30",
},
{
name: "setting kube emulation version, prefix v ok",
emulationVersionFlag: "kube=v1.30",
expectedKubeEmulationVersion: version.MajorMinor(1, 30),
name: "setting kube emulation version twice",
flags: []string{
"--emulated-version=kube=1.30",
"--emulated-version=kube=1.32",
},
parseError: "duplicate version flag, kube=1.30 and kube=1.32",
},
{
name: "prefix v ok",
flags: []string{"--emulated-version=kube=v1.30"},
expectedKubeEmulationVersion: "1.30",
},
{
name: "patch version not ok",
flags: []string{"--emulated-version=kube=1.30.2"},
parseError: "patch version not allowed, got: kube=1.30.2",
},
{
name: "setting test emulation version",
emulationVersionFlag: "test=2.7",
expectedKubeEmulationVersion: version.MajorMinor(1, 31),
expectedTestEmulationVersion: version.MajorMinor(2, 7),
flags: []string{"--emulated-version=test=2.7"},
expectedKubeEmulationVersion: "1.31",
expectedTestEmulationVersion: "2.7",
},
{
name: "version missing component",
emulationVersionFlag: "1.31",
parseError: "component not registered: 1.31",
name: "version missing component default to kube",
flags: []string{"--emulated-version=1.30"},
expectedKubeEmulationVersion: "1.30",
},
{
name: "version unregistered component",
emulationVersionFlag: "test3=1.31",
parseError: "component not registered: test3",
name: "version missing component default to kube with duplicate",
flags: []string{"--emulated-version=1.30", "--emulated-version=kube=1.30"},
parseError: "duplicate version flag, kube=1.30 and kube=1.30",
},
{
name: "invalid version",
emulationVersionFlag: "test=1.foo",
parseError: "illegal version string \"1.foo\"",
name: "version unregistered component",
flags: []string{"--emulated-version=test3=1.31"},
parseError: "component not registered: test3",
},
{
name: "setting test feature flag",
emulationVersionFlag: "test=2.7",
featureGatesFlag: "test:testA=true",
expectedKubeEmulationVersion: version.MajorMinor(1, 31),
expectedTestEmulationVersion: version.MajorMinor(2, 7),
name: "invalid version",
flags: []string{"--emulated-version=test=1.foo"},
parseError: "illegal version string \"1.foo\"",
},
{
name: "setting test feature flag",
flags: []string{
"--emulated-version=test=2.7",
"--feature-gates=test:testA=true",
},
expectedKubeEmulationVersion: "1.31",
expectedTestEmulationVersion: "2.7",
expectedKubeFeatureValues: map[featuregate.Feature]bool{"kubeA": true, "kubeB": false, "commonC": true},
expectedTestFeatureValues: map[featuregate.Feature]bool{"testA": true, "testB": false, "commonC": false},
},
{
name: "setting future test feature flag",
emulationVersionFlag: "test=2.7",
featureGatesFlag: "test:testA=true,test:testB=true",
parseError: "cannot set feature gate testB to true, feature is PreAlpha at emulated version 2.7",
name: "setting future test feature flag",
flags: []string{
"--emulated-version=test=2.7",
"--feature-gates=test:testA=true,test:testB=true",
},
parseError: "cannot set feature gate testB to true, feature is PreAlpha at emulated version 2.7",
},
{
name: "setting kube feature flag",
emulationVersionFlag: "test=2.7,kube=1.30",
featureGatesFlag: "test:commonC=true,commonC=false,kube:kubeB=true",
expectedKubeEmulationVersion: version.MajorMinor(1, 30),
expectedTestEmulationVersion: version.MajorMinor(2, 7),
name: "setting kube feature flag",
flags: []string{
"--emulated-version=test=2.7",
"--emulated-version=kube=1.30",
"--feature-gates=kubeB=false,test:commonC=true",
"--feature-gates=commonC=false,kubeB=true",
},
expectedKubeEmulationVersion: "1.30",
expectedTestEmulationVersion: "2.7",
expectedKubeFeatureValues: map[featuregate.Feature]bool{"kubeA": false, "kubeB": true, "commonC": false},
expectedTestFeatureValues: map[featuregate.Feature]bool{"testA": false, "testB": false, "commonC": true},
},
{
name: "setting locked kube feature flag",
emulationVersionFlag: "test=2.7",
featureGatesFlag: "kubeA=false",
parseError: "cannot set feature gate kubeA to false, feature is locked to true",
name: "setting kube feature flag with different prefix",
flags: []string{
"--emulated-version=test=2.7",
"--emulated-version=kube=1.30",
"--feature-gates=kube:kubeB=false,test:commonC=true",
"--feature-gates=commonC=false,kubeB=true",
},
parseError: "set kube feature gates with default empty prefix or kube: prefix consistently, do not mix use",
},
{
name: "setting unknown test feature flag",
emulationVersionFlag: "test=2.7",
featureGatesFlag: "test:testD=true",
parseError: "unrecognized feature gate: testD",
name: "setting locked kube feature flag",
flags: []string{
"--emulated-version=test=2.7",
"--feature-gates=kubeA=false",
},
parseError: "cannot set feature gate kubeA to false, feature is locked to true",
},
{
name: "setting unknown component feature flag",
emulationVersionFlag: "test=2.7",
featureGatesFlag: "test3:commonC=true",
parseError: "component not registered: test3",
name: "setting unknown test feature flag",
flags: []string{
"--emulated-version=test=2.7",
"--feature-gates=test:testD=true",
},
parseError: "unrecognized feature gate: testD",
},
{
name: "setting unknown component feature flag",
flags: []string{
"--emulated-version=test=2.7",
"--feature-gates=test3:commonC=true",
},
parseError: "component not registered: test3",
},
}
for i, test := range tests {
@ -227,9 +277,7 @@ func TestFlags(t *testing.T) {
fs := pflag.NewFlagSet("testflag", pflag.ContinueOnError)
r := testRegistry(t)
r.AddFlags(fs)
err := fs.Parse([]string{fmt.Sprintf("--emulated-version=%s", test.emulationVersionFlag),
fmt.Sprintf("--feature-gates=%s", test.featureGatesFlag)})
err := fs.Parse(test.flags)
if err == nil {
err = r.Set()
}
@ -242,19 +290,11 @@ func TestFlags(t *testing.T) {
if err != nil {
t.Fatalf("%d: Parse() expected: nil, got: %v", i, err)
}
if test.expectedKubeEmulationVersion != nil {
v := r.EffectiveVersionFor("kube").EmulationVersion()
if !v.EqualTo(test.expectedKubeEmulationVersion) {
t.Fatalf("%d: EmulationVersion expected: %s, got: %s", i, test.expectedKubeEmulationVersion.String(), v.String())
return
}
if len(test.expectedKubeEmulationVersion) > 0 {
assertVersionEqualTo(t, r.EffectiveVersionFor(DefaultKubeComponent).EmulationVersion(), test.expectedKubeEmulationVersion)
}
if test.expectedTestEmulationVersion != nil {
v := r.EffectiveVersionFor("test").EmulationVersion()
if !v.EqualTo(test.expectedTestEmulationVersion) {
t.Fatalf("%d: EmulationVersion expected: %s, got: %s", i, test.expectedTestEmulationVersion.String(), v.String())
return
}
if len(test.expectedTestEmulationVersion) > 0 {
assertVersionEqualTo(t, r.EffectiveVersionFor(testComponent).EmulationVersion(), test.expectedTestEmulationVersion)
}
for f, v := range test.expectedKubeFeatureValues {
if r.FeatureGateFor(DefaultKubeComponent).Enabled(f) != v {
@ -269,3 +309,110 @@ func TestFlags(t *testing.T) {
})
}
}
func TestVersionMapping(t *testing.T) {
r := NewComponentGlobalsRegistry()
ver1 := NewEffectiveVersion("0.58")
ver2 := NewEffectiveVersion("1.28")
ver3 := NewEffectiveVersion("2.10")
utilruntime.Must(r.Register("test1", ver1, nil))
utilruntime.Must(r.Register("test2", ver2, nil))
utilruntime.Must(r.Register("test3", ver3, nil))
assertVersionEqualTo(t, r.EffectiveVersionFor("test1").EmulationVersion(), "0.58")
assertVersionEqualTo(t, r.EffectiveVersionFor("test2").EmulationVersion(), "1.28")
assertVersionEqualTo(t, r.EffectiveVersionFor("test3").EmulationVersion(), "2.10")
utilruntime.Must(r.SetEmulationVersionMapping("test2", "test3",
func(from *version.Version) *version.Version {
return version.MajorMinor(from.Major()+1, from.Minor()-19)
}))
utilruntime.Must(r.SetEmulationVersionMapping("test1", "test2",
func(from *version.Version) *version.Version {
return version.MajorMinor(from.Major()+1, from.Minor()-28)
}))
assertVersionEqualTo(t, r.EffectiveVersionFor("test1").EmulationVersion(), "0.58")
assertVersionEqualTo(t, r.EffectiveVersionFor("test2").EmulationVersion(), "1.30")
assertVersionEqualTo(t, r.EffectiveVersionFor("test3").EmulationVersion(), "2.11")
fs := pflag.NewFlagSet("testflag", pflag.ContinueOnError)
r.AddFlags(fs)
if err := fs.Parse([]string{fmt.Sprintf("--emulated-version=%s", "test1=0.56")}); err != nil {
t.Fatal(err)
return
}
if err := r.Set(); err != nil {
t.Fatal(err)
return
}
assertVersionEqualTo(t, r.EffectiveVersionFor("test1").EmulationVersion(), "0.56")
assertVersionEqualTo(t, r.EffectiveVersionFor("test2").EmulationVersion(), "1.28")
assertVersionEqualTo(t, r.EffectiveVersionFor("test3").EmulationVersion(), "2.09")
}
func TestVersionMappingWithMultipleDependency(t *testing.T) {
r := NewComponentGlobalsRegistry()
ver1 := NewEffectiveVersion("0.58")
ver2 := NewEffectiveVersion("1.28")
ver3 := NewEffectiveVersion("2.10")
utilruntime.Must(r.Register("test1", ver1, nil))
utilruntime.Must(r.Register("test2", ver2, nil))
utilruntime.Must(r.Register("test3", ver3, nil))
assertVersionEqualTo(t, r.EffectiveVersionFor("test1").EmulationVersion(), "0.58")
assertVersionEqualTo(t, r.EffectiveVersionFor("test2").EmulationVersion(), "1.28")
assertVersionEqualTo(t, r.EffectiveVersionFor("test3").EmulationVersion(), "2.10")
utilruntime.Must(r.SetEmulationVersionMapping("test1", "test2",
func(from *version.Version) *version.Version {
return version.MajorMinor(from.Major()+1, from.Minor()-28)
}))
err := r.SetEmulationVersionMapping("test3", "test2",
func(from *version.Version) *version.Version {
return version.MajorMinor(from.Major()-1, from.Minor()+19)
})
if err == nil {
t.Errorf("expect error when setting 2nd mapping to test2")
}
}
func TestVersionMappingWithCyclicDependency(t *testing.T) {
r := NewComponentGlobalsRegistry()
ver1 := NewEffectiveVersion("0.58")
ver2 := NewEffectiveVersion("1.28")
ver3 := NewEffectiveVersion("2.10")
utilruntime.Must(r.Register("test1", ver1, nil))
utilruntime.Must(r.Register("test2", ver2, nil))
utilruntime.Must(r.Register("test3", ver3, nil))
assertVersionEqualTo(t, r.EffectiveVersionFor("test1").EmulationVersion(), "0.58")
assertVersionEqualTo(t, r.EffectiveVersionFor("test2").EmulationVersion(), "1.28")
assertVersionEqualTo(t, r.EffectiveVersionFor("test3").EmulationVersion(), "2.10")
utilruntime.Must(r.SetEmulationVersionMapping("test1", "test2",
func(from *version.Version) *version.Version {
return version.MajorMinor(from.Major()+1, from.Minor()-28)
}))
utilruntime.Must(r.SetEmulationVersionMapping("test2", "test3",
func(from *version.Version) *version.Version {
return version.MajorMinor(from.Major()+1, from.Minor()-19)
}))
err := r.SetEmulationVersionMapping("test3", "test1",
func(from *version.Version) *version.Version {
return version.MajorMinor(from.Major()-2, from.Minor()+48)
})
if err == nil {
t.Errorf("expect cyclic version mapping error")
}
}
func assertVersionEqualTo(t *testing.T, ver *version.Version, expectedVer string) {
if ver.EqualTo(version.MustParse(expectedVer)) {
return
}
t.Errorf("expected: %s, got %s", expectedVer, ver.String())
}

View File

@ -18,10 +18,8 @@ package version
import (
"fmt"
"strings"
"sync/atomic"
"github.com/spf13/pflag"
"k8s.io/apimachinery/pkg/util/version"
baseversion "k8s.io/component-base/version"
)
@ -40,37 +38,6 @@ type MutableEffectiveVersion interface {
Set(binaryVersion, emulationVersion, minCompatibilityVersion *version.Version)
SetEmulationVersion(emulationVersion *version.Version)
SetMinCompatibilityVersion(minCompatibilityVersion *version.Version)
// AddFlags adds the "{prefix}-emulated-version" to the flagset.
AddFlags(fs *pflag.FlagSet, prefix string)
}
type VersionVar struct {
val atomic.Pointer[version.Version]
}
// Set sets the flag value
func (v *VersionVar) Set(s string) error {
components := strings.Split(s, ".")
if len(components) != 2 {
return fmt.Errorf("version %s is not in the format of major.minor", s)
}
ver, err := version.ParseGeneric(s)
if err != nil {
return err
}
v.val.Store(ver)
return nil
}
// String returns the flag value
func (v *VersionVar) String() string {
ver := v.val.Load()
return ver.String()
}
// Type gets the flag type
func (v *VersionVar) Type() string {
return "version"
}
type effectiveVersion struct {
@ -78,9 +45,9 @@ type effectiveVersion struct {
// If the emulationVersion is set by the users, it could only contain major and minor versions.
// In tests, emulationVersion could be the same as the binary version, or set directly,
// which can have "alpha" as pre-release to continue serving expired apis while we clean up the test.
emulationVersion VersionVar
emulationVersion atomic.Pointer[version.Version]
// minCompatibilityVersion could only contain major and minor versions.
minCompatibilityVersion VersionVar
minCompatibilityVersion atomic.Pointer[version.Version]
}
func (m *effectiveVersion) BinaryVersion() *version.Version {
@ -88,13 +55,17 @@ func (m *effectiveVersion) BinaryVersion() *version.Version {
}
func (m *effectiveVersion) EmulationVersion() *version.Version {
// Emulation version can have "alpha" as pre-release to continue serving expired apis while we clean up the test.
// The pre-release should not be accessible to the users.
return m.emulationVersion.val.Load().WithPreRelease(m.BinaryVersion().PreRelease())
ver := m.emulationVersion.Load()
if ver != nil {
// Emulation version can have "alpha" as pre-release to continue serving expired apis while we clean up the test.
// The pre-release should not be accessible to the users.
return ver.WithPreRelease(m.BinaryVersion().PreRelease())
}
return ver
}
func (m *effectiveVersion) MinCompatibilityVersion() *version.Version {
return m.minCompatibilityVersion.val.Load()
return m.minCompatibilityVersion.Load()
}
func (m *effectiveVersion) EqualTo(other EffectiveVersion) bool {
@ -109,26 +80,33 @@ func (m *effectiveVersion) String() string {
m.BinaryVersion().String(), m.EmulationVersion().String(), m.MinCompatibilityVersion().String())
}
func majorMinor(ver *version.Version) *version.Version {
if ver == nil {
return ver
}
return version.MajorMinor(ver.Major(), ver.Minor())
}
func (m *effectiveVersion) Set(binaryVersion, emulationVersion, minCompatibilityVersion *version.Version) {
m.binaryVersion.Store(binaryVersion)
m.emulationVersion.val.Store(version.MajorMinor(emulationVersion.Major(), emulationVersion.Minor()))
m.minCompatibilityVersion.val.Store(version.MajorMinor(minCompatibilityVersion.Major(), minCompatibilityVersion.Minor()))
m.emulationVersion.Store(majorMinor(emulationVersion))
m.minCompatibilityVersion.Store(majorMinor(minCompatibilityVersion))
}
func (m *effectiveVersion) SetEmulationVersion(emulationVersion *version.Version) {
m.emulationVersion.val.Store(version.MajorMinor(emulationVersion.Major(), emulationVersion.Minor()))
m.emulationVersion.Store(majorMinor(emulationVersion))
}
func (m *effectiveVersion) SetMinCompatibilityVersion(minCompatibilityVersion *version.Version) {
m.minCompatibilityVersion.val.Store(version.MajorMinor(minCompatibilityVersion.Major(), minCompatibilityVersion.Minor()))
m.minCompatibilityVersion.Store(majorMinor(minCompatibilityVersion))
}
func (m *effectiveVersion) Validate() []error {
var errs []error
// Validate only checks the major and minor versions.
binaryVersion := m.binaryVersion.Load().WithPatch(0)
emulationVersion := m.emulationVersion.val.Load()
minCompatibilityVersion := m.minCompatibilityVersion.val.Load()
emulationVersion := m.emulationVersion.Load()
minCompatibilityVersion := m.minCompatibilityVersion.Load()
// emulationVersion can only be 1.{binaryMinor-1}...1.{binaryMinor}.
maxEmuVer := binaryVersion
@ -151,45 +129,36 @@ func (m *effectiveVersion) Validate() []error {
return errs
}
// AddFlags adds the "{prefix}-emulated-version" to the flagset.
func (m *effectiveVersion) AddFlags(fs *pflag.FlagSet, prefix string) {
if m == nil {
return
}
if len(prefix) > 0 && !strings.HasSuffix(prefix, "-") {
prefix += "-"
}
fs.Var(&m.emulationVersion, prefix+"emulated-version", ""+
"The version the K8s component emulates its capabilities (APIs, features, ...) of.\n"+
"If set, the component will emulate the behavior of this version instead of the underlying binary version.\n"+
"Any capabilities present in the binary version that were introduced after the emulated version will be unavailable and any capabilities removed after the emulated version will be available.\n"+
"This flag applies only to component capabilities, and does not disable bug fixes and performance improvements present in the binary version.\n"+
"Defaults to the binary version. The value should be between 1.{binaryMinorVersion-1} and 1.{binaryMinorVersion}.\n"+
"Format could only be major.minor")
}
func NewEffectiveVersion(binaryVer string) MutableEffectiveVersion {
func newEffectiveVersion(binaryVersion *version.Version) MutableEffectiveVersion {
effective := &effectiveVersion{}
binaryVersion := version.MustParse(binaryVer)
compatVersion := binaryVersion.SubtractMinor(1)
effective.Set(binaryVersion, binaryVersion, compatVersion)
return effective
}
func NewEffectiveVersion(binaryVer string) MutableEffectiveVersion {
if binaryVer == "" {
return &effectiveVersion{}
}
binaryVersion := version.MustParse(binaryVer)
return newEffectiveVersion(binaryVersion)
}
// DefaultBuildEffectiveVersion returns the MutableEffectiveVersion based on the
// current build information.
func DefaultBuildEffectiveVersion() MutableEffectiveVersion {
verInfo := baseversion.Get()
ver := NewEffectiveVersion(verInfo.String())
if ver.BinaryVersion().Major() == 0 && ver.BinaryVersion().Minor() == 0 {
ver = DefaultKubeEffectiveVersion()
binaryVersion := version.MustParse(verInfo.String()).WithInfo(verInfo)
if binaryVersion.Major() == 0 && binaryVersion.Minor() == 0 {
return DefaultKubeEffectiveVersion()
}
return ver
return newEffectiveVersion(binaryVersion)
}
// DefaultKubeEffectiveVersion returns the MutableEffectiveVersion based on the
// latest K8s release.
// Should update for each minor release!
func DefaultKubeEffectiveVersion() MutableEffectiveVersion {
return NewEffectiveVersion("1.31")
binaryVersion := version.MustParse("1.31").WithInfo(baseversion.Get())
return newEffectiveVersion(binaryVersion)
}

View File

@ -17,11 +17,8 @@ limitations under the License.
package version
import (
"fmt"
"strings"
"testing"
"github.com/spf13/pflag"
"k8s.io/apimachinery/pkg/util/version"
)
@ -127,54 +124,3 @@ func TestValidate(t *testing.T) {
})
}
}
func TestEffectiveVersionsFlag(t *testing.T) {
tests := []struct {
name string
emulationVersion string
expectedEmulationVersion *version.Version
parseError string
}{
{
name: "major.minor ok",
emulationVersion: "1.30",
expectedEmulationVersion: version.MajorMinor(1, 30),
},
{
name: "v prefix ok",
emulationVersion: "v1.30",
expectedEmulationVersion: version.MajorMinor(1, 30),
},
{
name: "semantic version not ok",
emulationVersion: "1.30.1",
parseError: "version 1.30.1 is not in the format of major.minor",
},
{
name: "invalid version",
emulationVersion: "1.foo",
parseError: "illegal version string",
},
}
for i, test := range tests {
t.Run(test.name, func(t *testing.T) {
fs := pflag.NewFlagSet("testflag", pflag.ContinueOnError)
effective := NewEffectiveVersion("1.30")
effective.AddFlags(fs, "test")
err := fs.Parse([]string{fmt.Sprintf("--test-emulated-version=%s", test.emulationVersion)})
if test.parseError != "" {
if !strings.Contains(err.Error(), test.parseError) {
t.Fatalf("%d: Parse() Expected %v, Got %v", i, test.parseError, err)
}
return
}
if err != nil {
t.Fatalf("%d: Parse() Expected nil, Got %v", i, err)
}
if !effective.EmulationVersion().EqualTo(test.expectedEmulationVersion) {
t.Errorf("%d: EmulationVersion Expected %s, Got %s", i, test.expectedEmulationVersion.String(), effective.EmulationVersion().String())
}
})
}
}