From ca9c3d965e3c731883419aa09ecc4a5f8f476678 Mon Sep 17 00:00:00 2001 From: Ben Luddy Date: Mon, 4 Nov 2024 10:40:19 -0500 Subject: [PATCH] Add CBOR feature gates. For alpha, there is one apiserver feature gate and two client-go feature gates controlling CBOR. They were initially wired to separate test-only feature gate instances in order to prevent them from being configurable at runtime via command-line flags or environment variables (for client-go feature gates outside of Kubernetes components). All of the integration tests required by the KEP as alpha criteria have been implemented. This adds the feature gates to the usual feature gate instances and removes the temporary code to support separate test-only feature gate instances. Kubernetes-commit: 072dfcb416fd4e1ddab0a89ac4faf519e268bc96 --- pkg/endpoints/handlers/patch.go | 4 ++-- .../handlers/responsewriters/writers.go | 2 +- pkg/endpoints/handlers/watch.go | 6 +++--- pkg/endpoints/installer.go | 2 +- pkg/features/kube_features.go | 18 +++++------------- pkg/server/config.go | 2 +- pkg/server/config_test.go | 2 +- pkg/util/feature/feature_gate.go | 12 ------------ 8 files changed, 14 insertions(+), 34 deletions(-) diff --git a/pkg/endpoints/handlers/patch.go b/pkg/endpoints/handlers/patch.go index 097107842..acfff1961 100644 --- a/pkg/endpoints/handlers/patch.go +++ b/pkg/endpoints/handlers/patch.go @@ -138,7 +138,7 @@ func PatchResource(r rest.Patcher, scope *RequestScope, admit admission.Interfac case types.ApplyYAMLPatchType: baseContentType = runtime.ContentTypeYAML case types.ApplyCBORPatchType: - if !utilfeature.TestOnlyFeatureGate.Enabled(features.TestOnlyCBORServingAndStorage) { + if !utilfeature.DefaultFeatureGate.Enabled(features.CBORServingAndStorage) { // This request should have already been rejected by the // Content-Type allowlist check. Return 500 because assumptions are // already broken and the feature is not GA. @@ -673,7 +673,7 @@ func (p *patcher) patchResource(ctx context.Context, scope *RequestScope) (runti p.mechanism = newApplyPatcher(p, scope.FieldManager, yaml.Unmarshal, yaml.UnmarshalStrict) p.forceAllowCreate = true case types.ApplyCBORPatchType: - if !utilfeature.TestOnlyFeatureGate.Enabled(features.TestOnlyCBORServingAndStorage) { + if !utilfeature.DefaultFeatureGate.Enabled(features.CBORServingAndStorage) { utilruntime.HandleErrorWithContext(context.TODO(), nil, "CBOR apply requests should be rejected before reaching this point unless the feature gate is enabled.") return nil, false, fmt.Errorf("%v: unimplemented patch type", p.patchType) } diff --git a/pkg/endpoints/handlers/responsewriters/writers.go b/pkg/endpoints/handlers/responsewriters/writers.go index 9b9e10ad1..3ca5cba8c 100644 --- a/pkg/endpoints/handlers/responsewriters/writers.go +++ b/pkg/endpoints/handlers/responsewriters/writers.go @@ -286,7 +286,7 @@ func WriteObjectNegotiated(s runtime.NegotiatedSerializer, restrictions negotiat audit.LogResponseObject(req.Context(), object, gv, s) var encoder runtime.Encoder - if utilfeature.TestOnlyFeatureGate.Enabled(features.TestOnlyCBORServingAndStorage) { + if utilfeature.DefaultFeatureGate.Enabled(features.CBORServingAndStorage) { encoder = s.EncoderForVersion(runtime.UseNondeterministicEncoding(serializer.Serializer), gv) } else { encoder = s.EncoderForVersion(serializer.Serializer, gv) diff --git a/pkg/endpoints/handlers/watch.go b/pkg/endpoints/handlers/watch.go index 938e8ef5b..8876a19cd 100644 --- a/pkg/endpoints/handlers/watch.go +++ b/pkg/endpoints/handlers/watch.go @@ -77,7 +77,7 @@ func serveWatchHandler(watcher watch.Interface, scope *RequestScope, mediaTypeOp } framer := serializer.StreamSerializer.Framer var encoder runtime.Encoder - if utilfeature.TestOnlyFeatureGate.Enabled(features.TestOnlyCBORServingAndStorage) { + if utilfeature.DefaultFeatureGate.Enabled(features.CBORServingAndStorage) { encoder = scope.Serializer.EncoderForVersion(runtime.UseNondeterministicEncoding(serializer.StreamSerializer.Serializer), scope.Kind.GroupVersion()) } else { encoder = scope.Serializer.EncoderForVersion(serializer.StreamSerializer.Serializer, scope.Kind.GroupVersion()) @@ -102,13 +102,13 @@ func serveWatchHandler(watcher watch.Interface, scope *RequestScope, mediaTypeOp if !ok { return nil, fmt.Errorf("no encoder for %q exists in the requested target %#v", serializer.MediaType, contentSerializer) } - if utilfeature.TestOnlyFeatureGate.Enabled(features.TestOnlyCBORServingAndStorage) { + if utilfeature.DefaultFeatureGate.Enabled(features.CBORServingAndStorage) { negotiatedEncoder = contentSerializer.EncoderForVersion(runtime.UseNondeterministicEncoding(info.Serializer), contentKind.GroupVersion()) } else { negotiatedEncoder = contentSerializer.EncoderForVersion(info.Serializer, contentKind.GroupVersion()) } } else { - if utilfeature.TestOnlyFeatureGate.Enabled(features.TestOnlyCBORServingAndStorage) { + if utilfeature.DefaultFeatureGate.Enabled(features.CBORServingAndStorage) { negotiatedEncoder = scope.Serializer.EncoderForVersion(runtime.UseNondeterministicEncoding(serializer.Serializer), contentKind.GroupVersion()) } else { negotiatedEncoder = scope.Serializer.EncoderForVersion(serializer.Serializer, contentKind.GroupVersion()) diff --git a/pkg/endpoints/installer.go b/pkg/endpoints/installer.go index 6a1826b4e..78e67109f 100644 --- a/pkg/endpoints/installer.go +++ b/pkg/endpoints/installer.go @@ -895,7 +895,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag string(types.StrategicMergePatchType), string(types.ApplyYAMLPatchType), } - if utilfeature.TestOnlyFeatureGate.Enabled(features.TestOnlyCBORServingAndStorage) { + if utilfeature.DefaultFeatureGate.Enabled(features.CBORServingAndStorage) { supportedTypes = append(supportedTypes, string(types.ApplyCBORPatchType)) } handler := metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, deprecated, removedRelease, restfulPatchResource(patcher, reqScope, admit, supportedTypes)) diff --git a/pkg/features/kube_features.go b/pkg/features/kube_features.go index 336e29d99..02debfb76 100644 --- a/pkg/features/kube_features.go +++ b/pkg/features/kube_features.go @@ -92,9 +92,7 @@ const ( // // Enables CBOR as a supported encoding for requests and responses, and as the // preferred storage encoding for custom resources. - // - // This feature is currently PRE-ALPHA and MUST NOT be enabled outside of integration tests. - TestOnlyCBORServingAndStorage featuregate.Feature = "TestOnlyCBORServingAndStorage" + CBORServingAndStorage featuregate.Feature = "CBORServingAndStorage" // owner: @serathius // @@ -245,7 +243,6 @@ const ( func init() { runtime.Must(utilfeature.DefaultMutableFeatureGate.Add(defaultKubernetesFeatureGates)) runtime.Must(utilfeature.DefaultMutableFeatureGate.AddVersioned(defaultVersionedKubernetesFeatureGates)) - runtime.Must(utilfeature.TestOnlyMutableFeatureGate.AddVersioned(testOnlyVersionedKubernetesFeatureGates)) } // defaultVersionedKubernetesFeatureGates consists of all known Kubernetes-specific feature keys with VersionedSpecs. @@ -306,6 +303,10 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate {Version: version.MustParse("1.32"), Default: true, PreRelease: featuregate.Beta}, }, + CBORServingAndStorage: { + {Version: version.MustParse("1.32"), Default: false, PreRelease: featuregate.Alpha}, + }, + ConcurrentWatchObjectDecode: { {Version: version.MustParse("1.31"), Default: false, PreRelease: featuregate.Beta}, }, @@ -417,12 +418,3 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate // defaultKubernetesFeatureGates consists of legacy unversioned Kubernetes-specific feature keys. // Please do not add to this struct and use defaultVersionedKubernetesFeatureGates instead. var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{} - -// testOnlyVersionedKubernetesFeatureGates consists of features that require programmatic enablement -// for integration testing, but have not yet graduated to alpha in a release and must not be enabled -// by a runtime option. -var testOnlyVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate.VersionedSpecs{ - TestOnlyCBORServingAndStorage: { - {Version: version.MustParse("1.32"), Default: false, PreRelease: featuregate.Alpha}, - }, -} diff --git a/pkg/server/config.go b/pkg/server/config.go index fe9dca9dc..b37728556 100644 --- a/pkg/server/config.go +++ b/pkg/server/config.go @@ -756,7 +756,7 @@ func (c completedConfig) New(name string, delegationTarget DelegationTarget) (*G return nil, fmt.Errorf("Genericapiserver.New() called with config.Serializer == nil") } allowedMediaTypes := defaultAllowedMediaTypes - if utilfeature.TestOnlyFeatureGate.Enabled(genericfeatures.TestOnlyCBORServingAndStorage) { + if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.CBORServingAndStorage) { allowedMediaTypes = append(allowedMediaTypes, runtime.ContentTypeCBOR) } for _, info := range c.Serializer.SupportedMediaTypes() { diff --git a/pkg/server/config_test.go b/pkg/server/config_test.go index f6447eef7..1f30143a9 100644 --- a/pkg/server/config_test.go +++ b/pkg/server/config_test.go @@ -424,7 +424,7 @@ func TestNewErrorForbiddenSerializer(t *testing.T) { } func TestNewFeatureGatedSerializer(t *testing.T) { - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.TestOnlyFeatureGate, features.TestOnlyCBORServingAndStorage, true) + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CBORServingAndStorage, true) config := NewConfig(serializer.NewCodecFactory(scheme, serializer.WithSerializer(func(creater runtime.ObjectCreater, typer runtime.ObjectTyper) runtime.SerializerInfo { return runtime.SerializerInfo{ diff --git a/pkg/util/feature/feature_gate.go b/pkg/util/feature/feature_gate.go index 7c061042a..00a9e099b 100644 --- a/pkg/util/feature/feature_gate.go +++ b/pkg/util/feature/feature_gate.go @@ -31,15 +31,3 @@ var ( // Top-level commands/options setup that needs to modify this feature gate should use DefaultMutableFeatureGate. DefaultFeatureGate featuregate.FeatureGate = DefaultMutableFeatureGate ) - -var ( - // TestOnlyMutableFeatureGate is a mutable version of TestOnlyFeatureGate. Only top-level - // commands/options setup and the k8s.io/component-base/featuregate/testing package should - // make use of this. - TestOnlyMutableFeatureGate featuregate.MutableVersionedFeatureGate = featuregate.NewFeatureGate() - - // TestOnlyFeatureGate is a shared global FeatureGate for features that have not yet - // graduated to alpha and require programmatic feature enablement for pre-alpha integration - // testing without exposing the feature as a runtime option. - TestOnlyFeatureGate featuregate.FeatureGate = TestOnlyMutableFeatureGate -)