Skip to content

Commit 819cb8f

Browse files
Add emulation forward compatibility into api enablement and RemoveDeletedKinds.
Signed-off-by: Siyuan Zhang <[email protected]>
1 parent 9b57a96 commit 819cb8f

File tree

13 files changed

+828
-140
lines changed

13 files changed

+828
-140
lines changed

cmd/kube-apiserver/app/options/options_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ func TestAddFlags(t *testing.T) {
128128
"--service-cluster-ip-range=192.168.128.0/17",
129129
"--lease-reuse-duration-seconds=100",
130130
"--emulated-version=test=1.31",
131+
"--emulation-forward-compatible=true",
131132
}
132133
fs.Parse(args)
133134
utilruntime.Must(componentGlobalsRegistry.Set())
@@ -147,6 +148,7 @@ func TestAddFlags(t *testing.T) {
147148
MaxRequestBodyBytes: int64(3 * 1024 * 1024),
148149
ComponentGlobalsRegistry: componentGlobalsRegistry,
149150
ComponentName: basecompatibility.DefaultKubeComponent,
151+
EmulationForwardCompatible: true,
150152
},
151153
Admission: &kubeoptions.AdmissionOptions{
152154
GenericAdmission: &apiserveroptions.AdmissionOptions{

pkg/controlplane/apiserver/apis.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ func (s *Server) InstallAPIs(restStorageProviders ...RESTStorageProvider) error
8989
nonLegacy := []*genericapiserver.APIGroupInfo{}
9090

9191
// used later in the loop to filter the served resource by those that have expired.
92-
resourceExpirationEvaluator, err := genericapiserver.NewResourceExpirationEvaluator(s.GenericAPIServer.EffectiveVersion.EmulationVersion())
92+
resourceExpirationEvaluator, err := genericapiserver.NewResourceExpirationEvaluator(s.GenericAPIServer.EffectiveVersion.EmulationVersion(), s.GenericAPIServer.EmulationForwardCompatible)
9393
if err != nil {
9494
return err
9595
}
@@ -111,6 +111,7 @@ func (s *Server) InstallAPIs(restStorageProviders ...RESTStorageProvider) error
111111
// We do this here so that we don't accidentally serve versions without resources or openapi information that for kinds we don't serve.
112112
// This is a spot above the construction of individual storage handlers so that no sig accidentally forgets to check.
113113
resourceExpirationEvaluator.RemoveDeletedKinds(groupName, apiGroupInfo.Scheme, apiGroupInfo.VersionedResourcesStorageMap)
114+
resourceExpirationEvaluator.RemoveUnIntroducedKinds(groupName, apiGroupInfo.Scheme, apiGroupInfo.VersionedResourcesStorageMap)
114115
if len(apiGroupInfo.VersionedResourcesStorageMap) == 0 {
115116
klog.V(1).Infof("Removing API group %v because it is time to stop serving it because it has no versions per APILifecycle.", groupName)
116117
continue

staging/src/k8s.io/apimachinery/pkg/runtime/interfaces.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,7 @@ type ObjectDefaulter interface {
259259

260260
type ObjectVersioner interface {
261261
ConvertToVersion(in Object, gv GroupVersioner) (out Object, err error)
262+
PrioritizedVersionsForGroup(group string) []schema.GroupVersion
262263
}
263264

264265
// ObjectConvertor converts an object to a different version.

staging/src/k8s.io/apiserver/pkg/server/config.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,10 @@ type Config struct {
154154
// EffectiveVersion determines which apis and features are available
155155
// based on when the api/feature lifecyle.
156156
EffectiveVersion basecompatibility.EffectiveVersion
157+
// EmulationForwardCompatible indicates APIs introduced after the emulation version are installed.
158+
// If true, APIs that have higher priority than the APIs of the same group resource enabled at the emulation version will be installed.
159+
// This is useful if a controller has switched to use newer APIs in the binary version, and we want it still functional in an older emulation version.
160+
EmulationForwardCompatible bool
157161
// FeatureGate is a way to plumb feature gate through if you have them.
158162
FeatureGate featuregate.FeatureGate
159163
// AuditBackend is where audit events are sent to.
@@ -839,8 +843,9 @@ func (c completedConfig) New(name string, delegationTarget DelegationTarget) (*G
839843
StorageReadinessHook: NewStorageReadinessHook(c.StorageInitializationTimeout),
840844
StorageVersionManager: c.StorageVersionManager,
841845

842-
EffectiveVersion: c.EffectiveVersion,
843-
FeatureGate: c.FeatureGate,
846+
EffectiveVersion: c.EffectiveVersion,
847+
EmulationForwardCompatible: c.EmulationForwardCompatible,
848+
FeatureGate: c.FeatureGate,
844849

845850
muxAndDiscoveryCompleteSignals: map[string]<-chan struct{}{},
846851
}

staging/src/k8s.io/apiserver/pkg/server/deleted_kinds.go

Lines changed: 90 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package server
1919
import (
2020
"fmt"
2121
"os"
22+
"regexp"
2223
"strconv"
2324
"strings"
2425

@@ -31,10 +32,13 @@ import (
3132
"k8s.io/klog/v2"
3233
)
3334

35+
var alphaPattern = regexp.MustCompile(`^v\d+alpha\d+$`)
36+
3437
// resourceExpirationEvaluator holds info for deciding if a particular rest.Storage needs to excluded from the API
3538
type resourceExpirationEvaluator struct {
36-
currentVersion *apimachineryversion.Version
37-
isAlpha bool
39+
currentVersion *apimachineryversion.Version
40+
emulationForwardCompatible bool
41+
isAlpha bool
3842
// Special flag checking for the existence of alpha.0
3943
// alpha.0 is a special case where everything merged to master is auto propagated to the release-1.n branch
4044
isAlphaZero bool
@@ -53,17 +57,21 @@ type ResourceExpirationEvaluator interface {
5357
// RemoveDeletedKinds inspects the storage map and modifies it in place by removing storage for kinds that have been deleted.
5458
// versionedResourcesStorageMap mirrors the field on APIGroupInfo, it's a map from version to resource to the storage.
5559
RemoveDeletedKinds(groupName string, versioner runtime.ObjectVersioner, versionedResourcesStorageMap map[string]map[string]rest.Storage)
60+
// RemoveUnIntroducedKinds inspects the storage map and modifies it in place by removing storage for kinds that are introduced after the current version.
61+
// versionedResourcesStorageMap mirrors the field on APIGroupInfo, it's a map from version to resource to the storage.
62+
RemoveUnIntroducedKinds(groupName string, versioner runtime.ObjectVersioner, versionedResourcesStorageMap map[string]map[string]rest.Storage)
5663
// ShouldServeForVersion returns true if a particular version cut off is after the current version
5764
ShouldServeForVersion(majorRemoved, minorRemoved int) bool
5865
}
5966

60-
func NewResourceExpirationEvaluator(currentVersion *apimachineryversion.Version) (ResourceExpirationEvaluator, error) {
67+
func NewResourceExpirationEvaluator(currentVersion *apimachineryversion.Version, emulationForwardCompatible bool) (ResourceExpirationEvaluator, error) {
6168
if currentVersion == nil {
6269
return nil, fmt.Errorf("empty NewResourceExpirationEvaluator currentVersion")
6370
}
6471
klog.V(1).Infof("NewResourceExpirationEvaluator with currentVersion: %s.", currentVersion)
6572
ret := &resourceExpirationEvaluator{
6673
strictRemovedHandlingInAlpha: false,
74+
emulationForwardCompatible: emulationForwardCompatible,
6775
}
6876
// Only keeps the major and minor versions from input version.
6977
ret.currentVersion = apimachineryversion.MajorMinor(currentVersion.Major(), currentVersion.Minor())
@@ -89,7 +97,7 @@ func NewResourceExpirationEvaluator(currentVersion *apimachineryversion.Version)
8997
return ret, nil
9098
}
9199

92-
func (e *resourceExpirationEvaluator) shouldServe(gv schema.GroupVersion, versioner runtime.ObjectVersioner, resourceServingInfo rest.Storage) bool {
100+
func (e *resourceExpirationEvaluator) isNotRemoved(gv schema.GroupVersion, versioner runtime.ObjectVersioner, resourceServingInfo rest.Storage) bool {
93101
internalPtr := resourceServingInfo.New()
94102

95103
target := gv
@@ -104,15 +112,6 @@ func (e *resourceExpirationEvaluator) shouldServe(gv schema.GroupVersion, versio
104112
return false
105113
}
106114

107-
introduced, ok := versionedPtr.(introducedInterface)
108-
if ok {
109-
majorIntroduced, minorIntroduced := introduced.APILifecycleIntroduced()
110-
verIntroduced := apimachineryversion.MajorMinor(uint(majorIntroduced), uint(minorIntroduced))
111-
if e.currentVersion.LessThan(verIntroduced) {
112-
return false
113-
}
114-
}
115-
116115
removed, ok := versionedPtr.(removedInterface)
117116
if !ok {
118117
return true
@@ -153,15 +152,15 @@ type introducedInterface interface {
153152
APILifecycleIntroduced() (major, minor int)
154153
}
155154

156-
// removeDeletedKinds inspects the storage map and modifies it in place by removing storage for kinds that have been deleted.
155+
// RemoveDeletedKinds inspects the storage map and modifies it in place by removing storage for kinds that have been deleted.
157156
// versionedResourcesStorageMap mirrors the field on APIGroupInfo, it's a map from version to resource to the storage.
158157
func (e *resourceExpirationEvaluator) RemoveDeletedKinds(groupName string, versioner runtime.ObjectVersioner, versionedResourcesStorageMap map[string]map[string]rest.Storage) {
159158
versionsToRemove := sets.NewString()
160159
for apiVersion := range sets.StringKeySet(versionedResourcesStorageMap) {
161160
versionToResource := versionedResourcesStorageMap[apiVersion]
162161
resourcesToRemove := sets.NewString()
163162
for resourceName, resourceServingInfo := range versionToResource {
164-
if !e.shouldServe(schema.GroupVersion{Group: groupName, Version: apiVersion}, versioner, resourceServingInfo) {
163+
if !e.isNotRemoved(schema.GroupVersion{Group: groupName, Version: apiVersion}, versioner, resourceServingInfo) {
165164
resourcesToRemove.Insert(resourceName)
166165
}
167166
}
@@ -189,6 +188,82 @@ func (e *resourceExpirationEvaluator) RemoveDeletedKinds(groupName string, versi
189188
}
190189
}
191190

191+
// RemoveUnIntroducedKinds inspects the storage map and modifies it in place by removing storage for kinds that are introduced after the current version.
192+
// versionedResourcesStorageMap mirrors the field on APIGroupInfo, it's a map from version to resource to the storage.
193+
func (e *resourceExpirationEvaluator) RemoveUnIntroducedKinds(groupName string, versioner runtime.ObjectVersioner, versionedResourcesStorageMap map[string]map[string]rest.Storage) {
194+
versionsToRemove := sets.NewString()
195+
prioritizedVersions := versioner.PrioritizedVersionsForGroup(groupName)
196+
enabledResources := sets.NewString()
197+
198+
// iterate from the end to the front, so that we remove the older versions first.
199+
for i := len(prioritizedVersions) - 1; i >= 0; i-- {
200+
apiVersion := prioritizedVersions[i].Version
201+
versionToResource := versionedResourcesStorageMap[apiVersion]
202+
resourcesToRemove := sets.NewString()
203+
for resourceName, resourceServingInfo := range versionToResource {
204+
// if an earlier version of the resource has been enabled, the same resource with higher priority
205+
// should also be enabled if emulationForwardCompatible.
206+
if e.emulationForwardCompatible && enabledResources.Has(resourceName) {
207+
continue
208+
}
209+
verIntroduced := versionIntroduced(schema.GroupVersion{Group: groupName, Version: apiVersion}, versioner, resourceServingInfo)
210+
if e.currentVersion.LessThan(verIntroduced) {
211+
resourcesToRemove.Insert(resourceName)
212+
} else {
213+
// emulation forward compatibility is not applicable to alpha apis.
214+
if !alphaPattern.MatchString(apiVersion) {
215+
enabledResources.Insert(resourceName)
216+
}
217+
}
218+
}
219+
220+
for resourceName := range versionedResourcesStorageMap[apiVersion] {
221+
if !shouldRemoveResourceAndSubresources(resourcesToRemove, resourceName) {
222+
continue
223+
}
224+
225+
klog.V(1).Infof("Removing resource %v.%v.%v because it is introduced after the current version %s per APILifecycle.", resourceName, apiVersion, groupName, e.currentVersion.String())
226+
storage := versionToResource[resourceName]
227+
storage.Destroy()
228+
delete(versionToResource, resourceName)
229+
}
230+
versionedResourcesStorageMap[apiVersion] = versionToResource
231+
232+
if len(versionedResourcesStorageMap[apiVersion]) == 0 {
233+
versionsToRemove.Insert(apiVersion)
234+
}
235+
}
236+
237+
for _, apiVersion := range versionsToRemove.List() {
238+
klog.V(1).Infof("Removing version %v.%v because it is introduced after the current version %s and because it has no resources per APILifecycle.", apiVersion, groupName, e.currentVersion.String())
239+
delete(versionedResourcesStorageMap, apiVersion)
240+
}
241+
}
242+
243+
func versionIntroduced(gv schema.GroupVersion, versioner runtime.ObjectVersioner, resourceServingInfo rest.Storage) *apimachineryversion.Version {
244+
defaultVer := apimachineryversion.MajorMinor(0, 0)
245+
internalPtr := resourceServingInfo.New()
246+
247+
target := gv
248+
// honor storage that overrides group version (used for things like scale subresources)
249+
if versionProvider, ok := resourceServingInfo.(rest.GroupVersionKindProvider); ok {
250+
target = versionProvider.GroupVersionKind(target).GroupVersion()
251+
}
252+
253+
versionedPtr, err := versioner.ConvertToVersion(internalPtr, target)
254+
if err != nil {
255+
utilruntime.HandleError(err)
256+
return defaultVer
257+
}
258+
259+
introduced, ok := versionedPtr.(introducedInterface)
260+
if ok {
261+
majorIntroduced, minorIntroduced := introduced.APILifecycleIntroduced()
262+
return apimachineryversion.MajorMinor(uint(majorIntroduced), uint(minorIntroduced))
263+
}
264+
return defaultVer
265+
}
266+
192267
func shouldRemoveResourceAndSubresources(resourcesToRemove sets.String, resourceName string) bool {
193268
for _, resourceToRemove := range resourcesToRemove.List() {
194269
if resourceName == resourceToRemove {

0 commit comments

Comments
 (0)