Add test-only client feature gates for CBOR.

As with the apiserver feature gate for CBOR as a serving and storage encoding, the client feature
gates for CBOR are being initially added through a test-only feature gate instance that is not wired
to environment variables or to command-line flags and is intended only to be enabled
programmatically from integration tests. The test-only instance will be removed as part of alpha
graduation and replaced by conventional client feature gating.

Kubernetes-commit: ea13190d8bd3a4bb3e82055b529aa7599ae5c6e1
This commit is contained in:
Ben Luddy 2024-10-23 16:36:37 -04:00 committed by Kubernetes Publisher
parent abe0e99c21
commit 1cca19dc02
4 changed files with 100 additions and 8 deletions

View File

@ -21,7 +21,9 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer/cbor"
"k8s.io/apimachinery/pkg/runtime/serializer/json"
"k8s.io/client-go/features"
)
var basicScheme = runtime.NewScheme()
@ -35,11 +37,8 @@ func init() {
metav1.AddToGroupVersion(parameterScheme, versionV1)
}
// basicNegotiatedSerializer is used to handle discovery and error handling serialization
type basicNegotiatedSerializer struct{}
func (s basicNegotiatedSerializer) SupportedMediaTypes() []runtime.SerializerInfo {
return []runtime.SerializerInfo{
func newBasicNegotiatedSerializer() basicNegotiatedSerializer {
supportedMediaTypes := []runtime.SerializerInfo{
{
MediaType: "application/json",
MediaTypeType: "application",
@ -54,6 +53,27 @@ func (s basicNegotiatedSerializer) SupportedMediaTypes() []runtime.SerializerInf
},
},
}
if features.TestOnlyFeatureGates.Enabled(features.TestOnlyClientAllowsCBOR) {
supportedMediaTypes = append(supportedMediaTypes, runtime.SerializerInfo{
MediaType: "application/cbor",
MediaTypeType: "application",
MediaTypeSubType: "cbor",
Serializer: cbor.NewSerializer(unstructuredCreater{basicScheme}, unstructuredTyper{basicScheme}),
StreamSerializer: &runtime.StreamSerializerInfo{
Serializer: cbor.NewSerializer(basicScheme, basicScheme, cbor.Transcode(false)),
Framer: cbor.NewFramer(),
},
})
}
return basicNegotiatedSerializer{supportedMediaTypes: supportedMediaTypes}
}
type basicNegotiatedSerializer struct {
supportedMediaTypes []runtime.SerializerInfo
}
func (s basicNegotiatedSerializer) SupportedMediaTypes() []runtime.SerializerInfo {
return s.supportedMediaTypes
}
func (s basicNegotiatedSerializer) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {

View File

@ -29,6 +29,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/features"
"k8s.io/client-go/rest"
"k8s.io/client-go/util/consistencydetector"
"k8s.io/client-go/util/watchlist"
@ -45,9 +46,17 @@ var _ Interface = &DynamicClient{}
// appropriate dynamic client defaults set.
func ConfigFor(inConfig *rest.Config) *rest.Config {
config := rest.CopyConfig(inConfig)
config.AcceptContentTypes = "application/json"
config.ContentType = "application/json"
config.NegotiatedSerializer = basicNegotiatedSerializer{} // this gets used for discovery and error handling types
config.AcceptContentTypes = "application/json"
if features.TestOnlyFeatureGates.Enabled(features.TestOnlyClientAllowsCBOR) {
config.AcceptContentTypes = "application/json;q=0.9,application/cbor;q=1"
if features.TestOnlyFeatureGates.Enabled(features.TestOnlyClientPrefersCBOR) {
config.ContentType = "application/cbor"
}
}
config.NegotiatedSerializer = newBasicNegotiatedSerializer()
if config.UserAgent == "" {
config.UserAgent = rest.DefaultKubernetesUserAgent()
}

View File

@ -18,9 +18,11 @@ package features
import (
"errors"
"fmt"
"sync"
"sync/atomic"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"sync/atomic"
)
// NOTE: types Feature, FeatureSpec, prerelease (and its values)
@ -141,3 +143,43 @@ var (
// should use AddFeaturesToExistingFeatureGates followed by ReplaceFeatureGates.
featureGates = &atomic.Value{}
)
// TestOnlyFeatureGates is a distinct registry of pre-alpha client features that must not be
// included in runtime wiring to command-line flags or environment variables. It exists as a risk
// mitigation to allow only programmatic enablement of CBOR serialization for integration testing
// purposes.
//
// TODO: Once all required integration test coverage is complete, this will be deleted and the
// test-only feature gates will be replaced by normal feature gates.
var TestOnlyFeatureGates = &testOnlyFeatureGates{
features: map[Feature]bool{
TestOnlyClientAllowsCBOR: false,
TestOnlyClientPrefersCBOR: false,
},
}
type testOnlyFeatureGates struct {
lock sync.RWMutex
features map[Feature]bool
}
func (t *testOnlyFeatureGates) Enabled(feature Feature) bool {
t.lock.RLock()
defer t.lock.RUnlock()
enabled, ok := t.features[feature]
if !ok {
panic(fmt.Sprintf("test-only feature %q not recognized", feature))
}
return enabled
}
func (t *testOnlyFeatureGates) Set(feature Feature, enabled bool) error {
t.lock.Lock()
defer t.lock.Unlock()
if _, ok := t.features[feature]; !ok {
return fmt.Errorf("test-only feature %q not recognized", feature)
}
t.features[feature] = enabled
return nil
}

View File

@ -41,6 +41,27 @@ const (
// owner: @nilekhc
// alpha: v1.30
InformerResourceVersion Feature = "InformerResourceVersion"
// owner: @benluddy
// kep: https://kep.k8s.io/4222
//
// If disabled, clients configured to accept "application/cbor" will instead accept
// "application/json" with the same relative preference, and clients configured to write
// "application/cbor" or "application/apply-patch+cbor" will instead write
// "application/json" or "application/apply-patch+yaml", respectively.
//
// This feature is currently PRE-ALPHA and MUST NOT be enabled outside of integration tests.
TestOnlyClientAllowsCBOR Feature = "TestOnlyClientAllowsCBOR"
// owner: @benluddy
// kep: https://kep.k8s.io/4222
//
// If enabled AND TestOnlyClientAllowsCBOR is also enabled, the default request content type
// (if not explicitly configured) and the dynamic client's request content type both become
// "application/cbor".
//
// This feature is currently PRE-ALPHA and MUST NOT be enabled outside of integration tests.
TestOnlyClientPrefersCBOR Feature = "TestOnlyClientPrefersCBOR"
)
// defaultKubernetesFeatureGates consists of all known Kubernetes-specific feature keys.