From 9bb4aa730abaf73aaef17dc9e9608cceb4f904ed Mon Sep 17 00:00:00 2001 From: Siyuan Zhang Date: Fri, 20 Dec 2024 07:03:03 +0000 Subject: [PATCH] Refactor compatibility version code Replace DefaultComponentGlobalsRegistry with new instance of componentGlobalsRegistry in test api server. Signed-off-by: Siyuan Zhang move kube effective version validation out of component base. Signed-off-by: Siyuan Zhang move DefaultComponentGlobalsRegistry out of component base. Signed-off-by: Siyuan Zhang move ComponentGlobalsRegistry out of featuregate pkg. Signed-off-by: Siyuan Zhang remove usage of DefaultComponentGlobalsRegistry in test files. Signed-off-by: Siyuan Zhang change non-test DefaultKubeEffectiveVersion to use DefaultBuildEffectiveVersion. Signed-off-by: Siyuan Zhang Restore useDefaultBuildBinaryVersion in effective version. Signed-off-by: Siyuan Zhang rename DefaultKubeEffectiveVersion to DefaultKubeEffectiveVersionForTest. Signed-off-by: Siyuan Zhang pass options.ComponentGlobalsRegistry into config for controller manager and scheduler. Signed-off-by: Siyuan Zhang Pass apiserver effective version to DefaultResourceEncodingConfig. Signed-off-by: Siyuan Zhang change statusz registry to take effective version from the components. Signed-off-by: Siyuan Zhang Address review comments Signed-off-by: Siyuan Zhang update vendor Signed-off-by: Siyuan Zhang Kubernetes-commit: 8fc3a33454ba38783bb63de41ecf5343e2ced67c --- pkg/authentication/request/x509/x509_test.go | 15 ---- pkg/cel/environment/base.go | 8 +- pkg/server/config.go | 4 +- pkg/server/config_test.go | 8 +- pkg/server/genericapiserver.go | 4 +- pkg/server/genericapiserver_test.go | 9 ++- pkg/server/options/server_run_options.go | 16 ++-- pkg/server/options/server_run_options_test.go | 28 +++---- pkg/server/options/serving_test.go | 4 +- .../storage/resource_encoding_config.go | 17 +++-- .../storage/resource_encoding_config_test.go | 8 +- pkg/server/storage/storage_factory_test.go | 5 +- pkg/util/compatibility/registry.go | 53 +++++++++++++ pkg/util/compatibility/version.go | 65 ++++++++++++++++ pkg/util/compatibility/version_test.go | 76 +++++++++++++++++++ 15 files changed, 252 insertions(+), 68 deletions(-) create mode 100644 pkg/util/compatibility/registry.go create mode 100644 pkg/util/compatibility/version.go create mode 100644 pkg/util/compatibility/version_test.go diff --git a/pkg/authentication/request/x509/x509_test.go b/pkg/authentication/request/x509/x509_test.go index 48fe6798e..01cf24aa6 100644 --- a/pkg/authentication/request/x509/x509_test.go +++ b/pkg/authentication/request/x509/x509_test.go @@ -34,7 +34,6 @@ import ( "github.com/stretchr/testify/assert" asn1util "k8s.io/apimachinery/pkg/apis/asn1" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apimachinery/pkg/util/version" "k8s.io/apiserver/pkg/authentication/authenticator" "k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/features" @@ -799,9 +798,6 @@ func TestX509(t *testing.T) { ExpectErr: false, setupFunc: func(t *testing.T) { t.Helper() - // This statement can be removed once utilversion.DefaultKubeEffectiveVersion() is >= 1.33 - featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, feature.DefaultFeatureGate, version.MustParse("1.33")) - }, }, "common name and empty UID with feature gate disabled": { @@ -822,8 +818,6 @@ func TestX509(t *testing.T) { ExpectErr: false, setupFunc: func(t *testing.T) { t.Helper() - // This statement can be removed once utilversion.DefaultKubeEffectiveVersion() is >= 1.33 - featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, feature.DefaultFeatureGate, version.MustParse("1.33")) featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.AllowParsingUserUIDFromCertAuth, false) }, }, @@ -836,9 +830,6 @@ func TestX509(t *testing.T) { ExpectErrMsg: regexp.MustCompile("UID cannot be an empty string"), setupFunc: func(t *testing.T) { t.Helper() - // This statement can be removed once utilversion.DefaultKubeEffectiveVersion() is >= 1.33 - featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, feature.DefaultFeatureGate, version.MustParse("1.33")) - }, }, "ca with non-string UID": { @@ -850,9 +841,6 @@ func TestX509(t *testing.T) { ExpectErrMsg: regexp.MustCompile("unable to parse UID into a string"), setupFunc: func(t *testing.T) { t.Helper() - // This statement can be removed once utilversion.DefaultKubeEffectiveVersion() is >= 1.33 - featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, feature.DefaultFeatureGate, version.MustParse("1.33")) - }, }, "ca with multiple UIDs": { @@ -866,9 +854,6 @@ func TestX509(t *testing.T) { ExpectErrMsg: regexp.MustCompile("expected 1 UID, but found multiple"), setupFunc: func(t *testing.T) { t.Helper() - // This statement can be removed once utilversion.DefaultKubeEffectiveVersion() is >= 1.33 - featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, feature.DefaultFeatureGate, version.MustParse("1.33")) - }, }, "ca with multiple organizations": { diff --git a/pkg/cel/environment/base.go b/pkg/cel/environment/base.go index 3f4197b40..aa053e52e 100644 --- a/pkg/cel/environment/base.go +++ b/pkg/cel/environment/base.go @@ -32,9 +32,9 @@ import ( celconfig "k8s.io/apiserver/pkg/apis/cel" "k8s.io/apiserver/pkg/cel/library" genericfeatures "k8s.io/apiserver/pkg/features" + "k8s.io/apiserver/pkg/util/compatibility" utilfeature "k8s.io/apiserver/pkg/util/feature" - "k8s.io/component-base/featuregate" - utilversion "k8s.io/component-base/version" + basecompatibility "k8s.io/component-base/compatibility" ) // DefaultCompatibilityVersion returns a default compatibility version for use with EnvSet @@ -50,9 +50,9 @@ import ( // A default version number equal to the current Kubernetes major.minor version // indicates fast forward CEL features that can be used when rollback is no longer needed. func DefaultCompatibilityVersion() *version.Version { - effectiveVer := featuregate.DefaultComponentGlobalsRegistry.EffectiveVersionFor(featuregate.DefaultKubeComponent) + effectiveVer := compatibility.DefaultComponentGlobalsRegistry.EffectiveVersionFor(basecompatibility.DefaultKubeComponent) if effectiveVer == nil { - effectiveVer = utilversion.DefaultKubeEffectiveVersion() + effectiveVer = compatibility.DefaultBuildEffectiveVersion() } return effectiveVer.MinCompatibilityVersion() } diff --git a/pkg/server/config.go b/pkg/server/config.go index ee037aefe..9e9bf4e3d 100644 --- a/pkg/server/config.go +++ b/pkg/server/config.go @@ -73,12 +73,12 @@ import ( flowcontrolrequest "k8s.io/apiserver/pkg/util/flowcontrol/request" "k8s.io/client-go/informers" restclient "k8s.io/client-go/rest" + basecompatibility "k8s.io/component-base/compatibility" "k8s.io/component-base/featuregate" "k8s.io/component-base/logs" "k8s.io/component-base/metrics/features" "k8s.io/component-base/metrics/prometheus/slis" "k8s.io/component-base/tracing" - utilversion "k8s.io/component-base/version" "k8s.io/component-base/zpages/flagz" "k8s.io/klog/v2" openapicommon "k8s.io/kube-openapi/pkg/common" @@ -153,7 +153,7 @@ type Config struct { // EffectiveVersion determines which apis and features are available // based on when the api/feature lifecyle. - EffectiveVersion utilversion.EffectiveVersion + EffectiveVersion basecompatibility.EffectiveVersion // FeatureGate is a way to plumb feature gate through if you have them. FeatureGate featuregate.FeatureGate // AuditBackend is where audit events are sent to. diff --git a/pkg/server/config_test.go b/pkg/server/config_test.go index 1f30143a9..a4c2b7359 100644 --- a/pkg/server/config_test.go +++ b/pkg/server/config_test.go @@ -47,9 +47,9 @@ import ( "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/rest" + basecompatibility "k8s.io/component-base/compatibility" featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/component-base/tracing" - utilversion "k8s.io/component-base/version" "k8s.io/klog/v2/ktesting" netutils "k8s.io/utils/net" ) @@ -124,7 +124,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("") + delegateConfig.EffectiveVersion = basecompatibility.NewEffectiveVersionFromString("", "", "") clientset := fake.NewSimpleClientset() if clientset == nil { t.Fatal("unable to create fake client set") @@ -157,7 +157,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.EffectiveVersion = basecompatibility.NewEffectiveVersionFromString("", "", "") wrappingConfig.HealthzChecks = append(wrappingConfig.HealthzChecks, healthz.NamedCheck("wrapping-health", func(r *http.Request) error { return fmt.Errorf("wrapping failed healthcheck") @@ -434,7 +434,7 @@ func TestNewFeatureGatedSerializer(t *testing.T) { } }))) config.ExternalAddress = "192.168.10.4:443" - config.EffectiveVersion = utilversion.NewEffectiveVersion("") + config.EffectiveVersion = basecompatibility.NewEffectiveVersionFromString("", "", "") config.LoopbackClientConfig = &rest.Config{} if _, err := config.Complete(nil).New("test", NewEmptyDelegate()); err != nil { diff --git a/pkg/server/genericapiserver.go b/pkg/server/genericapiserver.go index e810a4608..43456f2f1 100644 --- a/pkg/server/genericapiserver.go +++ b/pkg/server/genericapiserver.go @@ -54,8 +54,8 @@ import ( "k8s.io/apiserver/pkg/storageversion" utilfeature "k8s.io/apiserver/pkg/util/feature" restclient "k8s.io/client-go/rest" + basecompatibility "k8s.io/component-base/compatibility" "k8s.io/component-base/featuregate" - utilversion "k8s.io/component-base/version" "k8s.io/klog/v2" openapibuilder3 "k8s.io/kube-openapi/pkg/builder3" openapicommon "k8s.io/kube-openapi/pkg/common" @@ -244,7 +244,7 @@ type GenericAPIServer struct { // EffectiveVersion determines which apis and features are available // based on when the api/feature lifecyle. - EffectiveVersion utilversion.EffectiveVersion + EffectiveVersion basecompatibility.EffectiveVersion // FeatureGate is a way to plumb feature gate through if you have them. FeatureGate featuregate.FeatureGate diff --git a/pkg/server/genericapiserver_test.go b/pkg/server/genericapiserver_test.go index 25c66b8d1..d3457cf34 100644 --- a/pkg/server/genericapiserver_test.go +++ b/pkg/server/genericapiserver_test.go @@ -38,6 +38,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/serializer" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/sets" + utilversion "k8s.io/apimachinery/pkg/util/version" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/version" "k8s.io/apiserver/pkg/apis/example" @@ -52,7 +53,7 @@ import ( "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes/fake" restclient "k8s.io/client-go/rest" - utilversion "k8s.io/component-base/version" + basecompatibility "k8s.io/component-base/compatibility" "k8s.io/klog/v2/ktesting" kubeopenapi "k8s.io/kube-openapi/pkg/common" "k8s.io/kube-openapi/pkg/validation/spec" @@ -138,7 +139,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.EffectiveVersion = basecompatibility.NewEffectiveVersionFromString("", "", "") config.OpenAPIConfig = DefaultOpenAPIConfig(testGetOpenAPIDefinitions, openapinamer.NewDefinitionNamer(runtime.NewScheme())) config.OpenAPIConfig.Info.Version = "unversioned" config.OpenAPIV3Config = DefaultOpenAPIV3Config(testGetOpenAPIDefinitions, openapinamer.NewDefinitionNamer(runtime.NewScheme())) @@ -460,8 +461,8 @@ func TestNotRestRoutesHaveAuth(t *testing.T) { config.EnableProfiling = true kubeVersion := fakeVersion() - effectiveVersion := utilversion.NewEffectiveVersion(kubeVersion.String()) - effectiveVersion.Set(effectiveVersion.BinaryVersion().WithInfo(kubeVersion), effectiveVersion.EmulationVersion(), effectiveVersion.MinCompatibilityVersion()) + binaryVersion := utilversion.MustParse(kubeVersion.String()) + effectiveVersion := basecompatibility.NewEffectiveVersion(binaryVersion, false, binaryVersion, binaryVersion.SubtractMinor(1)) config.EffectiveVersion = effectiveVersion s, err := config.Complete(nil).New("test", NewEmptyDelegate()) diff --git a/pkg/server/options/server_run_options.go b/pkg/server/options/server_run_options.go index a4d31ef92..1e3bd0060 100644 --- a/pkg/server/options/server_run_options.go +++ b/pkg/server/options/server_run_options.go @@ -27,9 +27,9 @@ import ( "k8s.io/apimachinery/pkg/util/errors" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apiserver/pkg/server" + "k8s.io/apiserver/pkg/util/compatibility" utilfeature "k8s.io/apiserver/pkg/util/feature" - "k8s.io/component-base/featuregate" - utilversion "k8s.io/component-base/version" + basecompatibility "k8s.io/component-base/compatibility" "github.com/spf13/pflag" ) @@ -95,22 +95,22 @@ type ServerRunOptions struct { ShutdownWatchTerminationGracePeriod time.Duration // ComponentGlobalsRegistry is the registry where the effective versions and feature gates for all components are stored. - ComponentGlobalsRegistry featuregate.ComponentGlobalsRegistry + ComponentGlobalsRegistry basecompatibility.ComponentGlobalsRegistry // ComponentName is name under which the server's global variabled are registered in the ComponentGlobalsRegistry. ComponentName string } func NewServerRunOptions() *ServerRunOptions { - if featuregate.DefaultComponentGlobalsRegistry.EffectiveVersionFor(featuregate.DefaultKubeComponent) == nil { + if compatibility.DefaultComponentGlobalsRegistry.EffectiveVersionFor(basecompatibility.DefaultKubeComponent) == nil { featureGate := utilfeature.DefaultMutableFeatureGate - effectiveVersion := utilversion.DefaultKubeEffectiveVersion() - utilruntime.Must(featuregate.DefaultComponentGlobalsRegistry.Register(featuregate.DefaultKubeComponent, effectiveVersion, featureGate)) + effectiveVersion := compatibility.DefaultBuildEffectiveVersion() + utilruntime.Must(compatibility.DefaultComponentGlobalsRegistry.Register(basecompatibility.DefaultKubeComponent, effectiveVersion, featureGate)) } - return NewServerRunOptionsForComponent(featuregate.DefaultKubeComponent, featuregate.DefaultComponentGlobalsRegistry) + return NewServerRunOptionsForComponent(basecompatibility.DefaultKubeComponent, compatibility.DefaultComponentGlobalsRegistry) } -func NewServerRunOptionsForComponent(componentName string, componentGlobalsRegistry featuregate.ComponentGlobalsRegistry) *ServerRunOptions { +func NewServerRunOptionsForComponent(componentName string, componentGlobalsRegistry basecompatibility.ComponentGlobalsRegistry) *ServerRunOptions { defaults := server.NewConfig(serializer.CodecFactory{}) return &ServerRunOptions{ MaxRequestsInFlight: defaults.MaxRequestsInFlight, diff --git a/pkg/server/options/server_run_options_test.go b/pkg/server/options/server_run_options_test.go index 69c09ccc4..462a73336 100644 --- a/pkg/server/options/server_run_options_test.go +++ b/pkg/server/options/server_run_options_test.go @@ -26,15 +26,15 @@ import ( utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/version" utilfeature "k8s.io/apiserver/pkg/util/feature" - "k8s.io/component-base/featuregate" - utilversion "k8s.io/component-base/version" + basecompatibility "k8s.io/component-base/compatibility" netutils "k8s.io/utils/net" ) func TestServerRunOptionsValidate(t *testing.T) { - testRegistry := featuregate.NewComponentGlobalsRegistry() + defaultComponentGlobalsRegistry := basecompatibility.NewComponentGlobalsRegistry() + testRegistry := basecompatibility.NewComponentGlobalsRegistry() featureGate := utilfeature.DefaultFeatureGate.DeepCopy() - effectiveVersion := utilversion.NewEffectiveVersion("1.35") + effectiveVersion := basecompatibility.NewEffectiveVersionFromString("1.35", "1.32", "1.32") effectiveVersion.SetEmulationVersion(version.MajorMinor(1, 31)) testComponent := "test" utilruntime.Must(testRegistry.Register(testComponent, effectiveVersion, featureGate)) @@ -55,7 +55,7 @@ func TestServerRunOptionsValidate(t *testing.T) { MinRequestTimeout: 1800, JSONPatchMaxCopyBytes: 10 * 1024 * 1024, MaxRequestBodyBytes: 10 * 1024 * 1024, - ComponentGlobalsRegistry: featuregate.DefaultComponentGlobalsRegistry, + ComponentGlobalsRegistry: defaultComponentGlobalsRegistry, }, expectErr: "--max-requests-inflight can not be negative value", }, @@ -70,7 +70,7 @@ func TestServerRunOptionsValidate(t *testing.T) { MinRequestTimeout: 1800, JSONPatchMaxCopyBytes: 10 * 1024 * 1024, MaxRequestBodyBytes: 10 * 1024 * 1024, - ComponentGlobalsRegistry: featuregate.DefaultComponentGlobalsRegistry, + ComponentGlobalsRegistry: defaultComponentGlobalsRegistry, }, expectErr: "--max-mutating-requests-inflight can not be negative value", }, @@ -85,7 +85,7 @@ func TestServerRunOptionsValidate(t *testing.T) { MinRequestTimeout: 1800, JSONPatchMaxCopyBytes: 10 * 1024 * 1024, MaxRequestBodyBytes: 10 * 1024 * 1024, - ComponentGlobalsRegistry: featuregate.DefaultComponentGlobalsRegistry, + ComponentGlobalsRegistry: defaultComponentGlobalsRegistry, }, expectErr: "--request-timeout can not be negative value", }, @@ -100,7 +100,7 @@ func TestServerRunOptionsValidate(t *testing.T) { MinRequestTimeout: -1800, JSONPatchMaxCopyBytes: 10 * 1024 * 1024, MaxRequestBodyBytes: 10 * 1024 * 1024, - ComponentGlobalsRegistry: featuregate.DefaultComponentGlobalsRegistry, + ComponentGlobalsRegistry: defaultComponentGlobalsRegistry, }, expectErr: "--min-request-timeout can not be negative value", }, @@ -115,7 +115,7 @@ func TestServerRunOptionsValidate(t *testing.T) { MinRequestTimeout: 1800, JSONPatchMaxCopyBytes: -10 * 1024 * 1024, MaxRequestBodyBytes: 10 * 1024 * 1024, - ComponentGlobalsRegistry: featuregate.DefaultComponentGlobalsRegistry, + ComponentGlobalsRegistry: defaultComponentGlobalsRegistry, }, expectErr: "ServerRunOptions.JSONPatchMaxCopyBytes can not be negative value", }, @@ -130,7 +130,7 @@ func TestServerRunOptionsValidate(t *testing.T) { MinRequestTimeout: 1800, JSONPatchMaxCopyBytes: 10 * 1024 * 1024, MaxRequestBodyBytes: -10 * 1024 * 1024, - ComponentGlobalsRegistry: featuregate.DefaultComponentGlobalsRegistry, + ComponentGlobalsRegistry: defaultComponentGlobalsRegistry, }, expectErr: "ServerRunOptions.MaxRequestBodyBytes can not be negative value", }, @@ -146,7 +146,7 @@ func TestServerRunOptionsValidate(t *testing.T) { JSONPatchMaxCopyBytes: 10 * 1024 * 1024, MaxRequestBodyBytes: 10 * 1024 * 1024, LivezGracePeriod: -time.Second, - ComponentGlobalsRegistry: featuregate.DefaultComponentGlobalsRegistry, + ComponentGlobalsRegistry: defaultComponentGlobalsRegistry, }, expectErr: "--livez-grace-period can not be a negative value", }, @@ -162,7 +162,7 @@ func TestServerRunOptionsValidate(t *testing.T) { JSONPatchMaxCopyBytes: 10 * 1024 * 1024, MaxRequestBodyBytes: 10 * 1024 * 1024, ShutdownDelayDuration: -time.Second, - ComponentGlobalsRegistry: featuregate.DefaultComponentGlobalsRegistry, + ComponentGlobalsRegistry: defaultComponentGlobalsRegistry, }, expectErr: "--shutdown-delay-duration can not be negative value", }, @@ -178,7 +178,7 @@ func TestServerRunOptionsValidate(t *testing.T) { MinRequestTimeout: 1800, JSONPatchMaxCopyBytes: 10 * 1024 * 1024, MaxRequestBodyBytes: 10 * 1024 * 1024, - ComponentGlobalsRegistry: featuregate.DefaultComponentGlobalsRegistry, + ComponentGlobalsRegistry: defaultComponentGlobalsRegistry, }, expectErr: "--strict-transport-security-directives invalid, allowed values: max-age=expireTime, includeSubDomains, preload. see https://tools.ietf.org/html/rfc6797#section-6.1 for more information", }, @@ -211,7 +211,7 @@ func TestServerRunOptionsValidate(t *testing.T) { MinRequestTimeout: 1800, JSONPatchMaxCopyBytes: 10 * 1024 * 1024, MaxRequestBodyBytes: 10 * 1024 * 1024, - ComponentGlobalsRegistry: featuregate.DefaultComponentGlobalsRegistry, + ComponentGlobalsRegistry: defaultComponentGlobalsRegistry, }, }, } diff --git a/pkg/server/options/serving_test.go b/pkg/server/options/serving_test.go index 3d9c4c3fc..898996837 100644 --- a/pkg/server/options/serving_test.go +++ b/pkg/server/options/serving_test.go @@ -46,7 +46,7 @@ import ( "k8s.io/client-go/discovery" restclient "k8s.io/client-go/rest" cliflag "k8s.io/component-base/cli/flag" - utilversion "k8s.io/component-base/version" + basecompatibility "k8s.io/component-base/compatibility" "k8s.io/klog/v2/ktesting" netutils "k8s.io/utils/net" ) @@ -278,7 +278,7 @@ func TestServerRunWithSNI(t *testing.T) { // launch server config := setUp(t) v := fakeVersion() - config.EffectiveVersion = utilversion.NewEffectiveVersion(v.String()) + config.EffectiveVersion = basecompatibility.NewEffectiveVersionFromString(v.String(), "", "") config.EnableIndex = true secureOptions := (&SecureServingOptions{ diff --git a/pkg/server/storage/resource_encoding_config.go b/pkg/server/storage/resource_encoding_config.go index ce1bec676..612983cca 100644 --- a/pkg/server/storage/resource_encoding_config.go +++ b/pkg/server/storage/resource_encoding_config.go @@ -22,7 +22,8 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" apimachineryversion "k8s.io/apimachinery/pkg/util/version" - version "k8s.io/component-base/version" + "k8s.io/apiserver/pkg/util/compatibility" + basecompatibility "k8s.io/component-base/compatibility" ) type ResourceEncodingConfig interface { @@ -43,7 +44,7 @@ type DefaultResourceEncodingConfig struct { // resources records the overriding encoding configs for individual resources. resources map[schema.GroupResource]*OverridingResourceEncoding scheme *runtime.Scheme - effectiveVersion version.EffectiveVersion + effectiveVersion basecompatibility.EffectiveVersion } type OverridingResourceEncoding struct { @@ -54,7 +55,11 @@ type OverridingResourceEncoding struct { var _ ResourceEncodingConfig = &DefaultResourceEncodingConfig{} func NewDefaultResourceEncodingConfig(scheme *runtime.Scheme) *DefaultResourceEncodingConfig { - return &DefaultResourceEncodingConfig{resources: map[schema.GroupResource]*OverridingResourceEncoding{}, scheme: scheme, effectiveVersion: version.DefaultKubeEffectiveVersion()} + return NewDefaultResourceEncodingConfigForEffectiveVersion(scheme, compatibility.DefaultComponentGlobalsRegistry.EffectiveVersionFor(basecompatibility.DefaultKubeComponent)) +} + +func NewDefaultResourceEncodingConfigForEffectiveVersion(scheme *runtime.Scheme, effectiveVersion basecompatibility.EffectiveVersion) *DefaultResourceEncodingConfig { + return &DefaultResourceEncodingConfig{resources: map[schema.GroupResource]*OverridingResourceEncoding{}, scheme: scheme, effectiveVersion: effectiveVersion} } func (o *DefaultResourceEncodingConfig) SetResourceEncoding(resourceBeingStored schema.GroupResource, externalEncodingVersion, internalVersion schema.GroupVersion) { @@ -64,7 +69,7 @@ func (o *DefaultResourceEncodingConfig) SetResourceEncoding(resourceBeingStored } } -func (o *DefaultResourceEncodingConfig) SetEffectiveVersion(effectiveVersion version.EffectiveVersion) { +func (o *DefaultResourceEncodingConfig) SetEffectiveVersion(effectiveVersion basecompatibility.EffectiveVersion) { o.effectiveVersion = effectiveVersion } @@ -121,7 +126,7 @@ type replacementInterface interface { APILifecycleReplacement() schema.GroupVersionKind } -func emulatedStorageVersion(binaryVersionOfResource schema.GroupVersion, example runtime.Object, effectiveVersion version.EffectiveVersion, scheme *runtime.Scheme) (schema.GroupVersion, error) { +func emulatedStorageVersion(binaryVersionOfResource schema.GroupVersion, example runtime.Object, effectiveVersion basecompatibility.EffectiveVersion, scheme *runtime.Scheme) (schema.GroupVersion, error) { if example == nil || effectiveVersion == nil { return binaryVersionOfResource, nil } @@ -172,7 +177,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 + // skip the introduced check for test when current compatibility version is 0.0 to test all apis if introduced, hasIntroduced := exampleOfGVK.(introducedInterface); hasIntroduced && (compatibilityVersion.Major() > 0 || compatibilityVersion.Minor() > 0) { // Skip versions that have a replacement. diff --git a/pkg/server/storage/resource_encoding_config_test.go b/pkg/server/storage/resource_encoding_config_test.go index a96e7e321..a6984deed 100644 --- a/pkg/server/storage/resource_encoding_config_test.go +++ b/pkg/server/storage/resource_encoding_config_test.go @@ -25,7 +25,7 @@ import ( "k8s.io/apimachinery/pkg/test" utilruntime "k8s.io/apimachinery/pkg/util/runtime" utilversion "k8s.io/apimachinery/pkg/util/version" - "k8s.io/component-base/version" + basecompatibility "k8s.io/component-base/compatibility" ) func TestEmulatedStorageVersion(t *testing.T) { @@ -33,21 +33,21 @@ func TestEmulatedStorageVersion(t *testing.T) { name string scheme *runtime.Scheme binaryVersion schema.GroupVersion - effectiveVersion version.EffectiveVersion + effectiveVersion basecompatibility.EffectiveVersion want schema.GroupVersion }{ { name: "pick compatible", scheme: AlphaBetaScheme(utilversion.MustParse("1.31"), utilversion.MustParse("1.32")), binaryVersion: v1beta1, - effectiveVersion: version.NewEffectiveVersion("1.32"), + effectiveVersion: basecompatibility.NewEffectiveVersionFromString("1.32", "", ""), want: v1alpha1, }, { name: "alpha has been replaced, pick binary version", scheme: AlphaReplacedBetaScheme(utilversion.MustParse("1.31"), utilversion.MustParse("1.32")), binaryVersion: v1beta1, - effectiveVersion: version.NewEffectiveVersion("1.32"), + effectiveVersion: basecompatibility.NewEffectiveVersionFromString("1.32", "", ""), want: v1beta1, }, } diff --git a/pkg/server/storage/storage_factory_test.go b/pkg/server/storage/storage_factory_test.go index fb79da75b..72d69a4b3 100644 --- a/pkg/server/storage/storage_factory_test.go +++ b/pkg/server/storage/storage_factory_test.go @@ -33,7 +33,7 @@ import ( "k8s.io/apiserver/pkg/apis/example2" example2install "k8s.io/apiserver/pkg/apis/example2/install" "k8s.io/apiserver/pkg/storage/storagebackend" - version "k8s.io/component-base/version" + basecompatibility "k8s.io/component-base/compatibility" ) var ( @@ -569,8 +569,7 @@ func TestStorageFactoryCompatibilityVersion(t *testing.T) { gvk := gvks[0] t.Run(gvk.GroupKind().String()+"@"+tc.effectiveVersion, func(t *testing.T) { - config := NewDefaultResourceEncodingConfig(sch) - config.SetEffectiveVersion(version.NewEffectiveVersion(tc.effectiveVersion)) + config := NewDefaultResourceEncodingConfigForEffectiveVersion(sch, basecompatibility.NewEffectiveVersionFromString(tc.effectiveVersion, "", "")) f := NewDefaultStorageFactory( storagebackend.Config{}, "", diff --git a/pkg/util/compatibility/registry.go b/pkg/util/compatibility/registry.go new file mode 100644 index 000000000..4110db43d --- /dev/null +++ b/pkg/util/compatibility/registry.go @@ -0,0 +1,53 @@ +/* +Copyright 2025 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 compatibility + +import ( + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + utilfeature "k8s.io/apiserver/pkg/util/feature" + basecompatibility "k8s.io/component-base/compatibility" +) + +// 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 +// wardleEffectiveVersion := basecompatibility.NewEffectiveVersion("1.2") +// wardleFeatureGate := featuregate.NewFeatureGate() +// utilruntime.Must(compatibility.DefaultComponentGlobalsRegistry.Register(apiserver.WardleComponentName, wardleEffectiveVersion, wardleFeatureGate, false)) +// +// cmd := &cobra.Command{ +// ... +// // call DefaultComponentGlobalsRegistry.Set() in PersistentPreRunE to ensure the feature gates are set based on emulation version right after parsing the flags. +// PersistentPreRunE: func(*cobra.Command, []string) error { +// if err := compatibility.DefaultComponentGlobalsRegistry.Set(); err != nil { +// return err +// } +// ... +// }, +// RunE: func(c *cobra.Command, args []string) error { +// // call compatibility.DefaultComponentGlobalsRegistry.Validate() somewhere +// }, +// } +// +// flags := cmd.Flags() +// // add flags +// compatibility.DefaultComponentGlobalsRegistry.AddFlags(flags) +var DefaultComponentGlobalsRegistry basecompatibility.ComponentGlobalsRegistry = basecompatibility.NewComponentGlobalsRegistry() + +func init() { + utilruntime.Must(DefaultComponentGlobalsRegistry.Register(basecompatibility.DefaultKubeComponent, DefaultBuildEffectiveVersion(), utilfeature.DefaultMutableFeatureGate)) +} diff --git a/pkg/util/compatibility/version.go b/pkg/util/compatibility/version.go new file mode 100644 index 000000000..9ceaf3de6 --- /dev/null +++ b/pkg/util/compatibility/version.go @@ -0,0 +1,65 @@ +/* +Copyright 2025 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 compatibility + +import ( + "k8s.io/apimachinery/pkg/util/version" + basecompatibility "k8s.io/component-base/compatibility" + baseversion "k8s.io/component-base/version" +) + +// minimumKubeEmulationVersion is the first release emulation version is introduced, +// so the emulation version cannot go lower than that. +var minimumKubeEmulationVersion *version.Version = version.MajorMinor(1, 31) + +// DefaultBuildEffectiveVersion returns the MutableEffectiveVersion based on the +// current build information. +func DefaultBuildEffectiveVersion() basecompatibility.MutableEffectiveVersion { + binaryVersion := defaultBuildBinaryVersion() + useDefaultBuildBinaryVersion := true + // fall back to the hard coded kube version only when the git tag is not available for local unit tests. + if binaryVersion.Major() == 0 && binaryVersion.Minor() == 0 { + useDefaultBuildBinaryVersion = false + binaryVersion = version.MustParse(baseversion.DefaultKubeBinaryVersion) + } + versionFloor := kubeEffectiveVersionFloors(binaryVersion) + return basecompatibility.NewEffectiveVersion(binaryVersion, useDefaultBuildBinaryVersion, versionFloor, versionFloor) +} + +func kubeEffectiveVersionFloors(binaryVersion *version.Version) *version.Version { + // both emulationVersion and minCompatibilityVersion can be set to binaryVersion - 3 + versionFloor := binaryVersion.WithPatch(0).SubtractMinor(3) + if versionFloor.LessThan(minimumKubeEmulationVersion) { + versionFloor = minimumKubeEmulationVersion + } + return versionFloor +} + +// DefaultKubeEffectiveVersionForTest returns the MutableEffectiveVersion based on the +// latest K8s release hardcoded in DefaultKubeBinaryVersion. +// DefaultKubeBinaryVersion is hard coded because defaultBuildBinaryVersion would return 0.0 when test is run without a git tag. +// We do not enforce the N-3..N emulation version range in tests so that the tests would not automatically fail when there is a version bump. +// Only used in tests. +func DefaultKubeEffectiveVersionForTest() basecompatibility.MutableEffectiveVersion { + binaryVersion := version.MustParse(baseversion.DefaultKubeBinaryVersion).WithInfo(baseversion.Get()) + return basecompatibility.NewEffectiveVersion(binaryVersion, false, version.MustParse("0.0"), version.MustParse("0.0")) +} + +func defaultBuildBinaryVersion() *version.Version { + verInfo := baseversion.Get() + return version.MustParse(verInfo.String()).WithInfo(verInfo) +} diff --git a/pkg/util/compatibility/version_test.go b/pkg/util/compatibility/version_test.go new file mode 100644 index 000000000..ed10f9371 --- /dev/null +++ b/pkg/util/compatibility/version_test.go @@ -0,0 +1,76 @@ +/* +Copyright 2025 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 compatibility + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/version" + basecompatibility "k8s.io/component-base/compatibility" +) + +func TestValidateKubeEffectiveVersion(t *testing.T) { + tests := []struct { + name string + emulationVersion string + minCompatibilityVersion string + expectErr bool + }{ + { + name: "valid versions", + emulationVersion: "v1.32.0", + minCompatibilityVersion: "v1.31.0", + expectErr: false, + }, + { + name: "emulationVersion too low", + emulationVersion: "v1.30.0", + minCompatibilityVersion: "v1.31.0", + expectErr: true, + }, + { + name: "minCompatibilityVersion too low", + emulationVersion: "v1.31.0", + minCompatibilityVersion: "v1.30.0", + expectErr: true, + }, + { + name: "both versions too low", + emulationVersion: "v1.30.0", + minCompatibilityVersion: "v1.29.0", + expectErr: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + binaryVersion := version.MustParseGeneric("1.33") + versionFloor := kubeEffectiveVersionFloors(binaryVersion) + effective := basecompatibility.NewEffectiveVersion(binaryVersion, false, versionFloor, versionFloor) + effective.SetEmulationVersion(version.MustParseGeneric(test.emulationVersion)) + effective.SetMinCompatibilityVersion(version.MustParseGeneric(test.minCompatibilityVersion)) + + err := effective.Validate() + if test.expectErr && err == nil { + t.Error("expected error, but got nil") + } + if !test.expectErr && err != nil { + t.Errorf("unexpected error: %v", err) + } + }) + } +}