diff --git a/staging/src/k8s.io/apiserver/pkg/server/storage/resource_encoding_config.go b/staging/src/k8s.io/apiserver/pkg/server/storage/resource_encoding_config.go index 271948c147f..ce1bec6763a 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/storage/resource_encoding_config.go +++ b/staging/src/k8s.io/apiserver/pkg/server/storage/resource_encoding_config.go @@ -117,6 +117,10 @@ type introducedInterface interface { APILifecycleIntroduced() (major, minor int) } +type replacementInterface interface { + APILifecycleReplacement() schema.GroupVersionKind +} + func emulatedStorageVersion(binaryVersionOfResource schema.GroupVersion, example runtime.Object, effectiveVersion version.EffectiveVersion, scheme *runtime.Scheme) (schema.GroupVersion, error) { if example == nil || effectiveVersion == nil { return binaryVersionOfResource, nil @@ -170,6 +174,14 @@ 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) { + + // Skip versions that have a replacement. + // This can be used to override this storage version selection by + // marking a storage version has having a replacement and preventing a + // that storage version from being selected. + if _, hasReplacement := exampleOfGVK.(replacementInterface); hasReplacement { + continue + } // API resource lifecycles should be relative to k8s api version majorIntroduced, minorIntroduced := introduced.APILifecycleIntroduced() introducedVer := apimachineryversion.MajorMinor(uint(majorIntroduced), uint(minorIntroduced)) diff --git a/staging/src/k8s.io/apiserver/pkg/server/storage/resource_encoding_config_test.go b/staging/src/k8s.io/apiserver/pkg/server/storage/resource_encoding_config_test.go new file mode 100644 index 00000000000..a96e7e321d8 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/server/storage/resource_encoding_config_test.go @@ -0,0 +1,135 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package storage + +import ( + "testing" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + runtimetesting "k8s.io/apimachinery/pkg/runtime/testing" + "k8s.io/apimachinery/pkg/test" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + utilversion "k8s.io/apimachinery/pkg/util/version" + "k8s.io/component-base/version" +) + +func TestEmulatedStorageVersion(t *testing.T) { + cases := []struct { + name string + scheme *runtime.Scheme + binaryVersion schema.GroupVersion + effectiveVersion version.EffectiveVersion + want schema.GroupVersion + }{ + { + name: "pick compatible", + scheme: AlphaBetaScheme(utilversion.MustParse("1.31"), utilversion.MustParse("1.32")), + binaryVersion: v1beta1, + effectiveVersion: version.NewEffectiveVersion("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"), + want: v1beta1, + }, + } + + for _, tc := range cases { + test.TestScheme() + t.Run(tc.name, func(t *testing.T) { + found, err := emulatedStorageVersion(tc.binaryVersion, &CronJob{}, tc.effectiveVersion, tc.scheme) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if found != tc.want { + t.Errorf("got %v; want %v", found, tc.want) + } + }) + } +} + +var internalGV = schema.GroupVersion{Group: "workload.example.com", Version: runtime.APIVersionInternal} +var v1alpha1 = schema.GroupVersion{Group: "workload.example.com", Version: "v1alpha1"} +var v1beta1 = schema.GroupVersion{Group: "workload.example.com", Version: "v1beta1"} + +type CronJobWithReplacement struct { + introduced *utilversion.Version + A string `json:"A,omitempty"` + B int `json:"B,omitempty"` +} + +func (*CronJobWithReplacement) GetObjectKind() schema.ObjectKind { panic("not implemented") } +func (*CronJobWithReplacement) DeepCopyObject() runtime.Object { + panic("not implemented") +} + +func (in *CronJobWithReplacement) APILifecycleIntroduced() (major, minor int) { + if in.introduced == nil { + return 0, 0 + } + return int(in.introduced.Major()), int(in.introduced.Minor()) +} + +func (in *CronJobWithReplacement) APILifecycleReplacement() schema.GroupVersionKind { + return schema.GroupVersionKind{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRole"} +} + +type CronJob struct { + introduced *utilversion.Version + A string `json:"A,omitempty"` + B int `json:"B,omitempty"` +} + +func (*CronJob) GetObjectKind() schema.ObjectKind { panic("not implemented") } +func (*CronJob) DeepCopyObject() runtime.Object { + panic("not implemented") +} + +func (in *CronJob) APILifecycleIntroduced() (major, minor int) { + if in.introduced == nil { + return 0, 0 + } + return int(in.introduced.Major()), int(in.introduced.Minor()) +} + +func AlphaBetaScheme(alphaVersion, betaVersion *utilversion.Version) *runtime.Scheme { + s := runtime.NewScheme() + s.AddKnownTypes(internalGV, &CronJob{}) + s.AddKnownTypes(v1alpha1, &CronJob{introduced: alphaVersion}) + s.AddKnownTypes(v1beta1, &CronJob{introduced: betaVersion}) + s.AddKnownTypeWithName(internalGV.WithKind("CronJob"), &CronJob{}) + s.AddKnownTypeWithName(v1alpha1.WithKind("CronJob"), &CronJob{introduced: alphaVersion}) + s.AddKnownTypeWithName(v1beta1.WithKind("CronJob"), &CronJob{introduced: betaVersion}) + utilruntime.Must(runtimetesting.RegisterConversions(s)) + return s +} + +func AlphaReplacedBetaScheme(alphaVersion, betaVersion *utilversion.Version) *runtime.Scheme { + s := runtime.NewScheme() + s.AddKnownTypes(internalGV, &CronJob{}) + s.AddKnownTypes(v1alpha1, &CronJobWithReplacement{introduced: alphaVersion}) + s.AddKnownTypes(v1beta1, &CronJob{introduced: betaVersion}) + s.AddKnownTypeWithName(internalGV.WithKind("CronJob"), &CronJob{}) + s.AddKnownTypeWithName(v1alpha1.WithKind("CronJob"), &CronJobWithReplacement{introduced: alphaVersion}) + s.AddKnownTypeWithName(v1beta1.WithKind("CronJob"), &CronJob{introduced: betaVersion}) + utilruntime.Must(runtimetesting.RegisterConversions(s)) + return s +}