Wire CBOR CR storage behind test-only feature gate.

This commit is contained in:
Ben Luddy 2024-10-24 16:09:32 -04:00
parent 5147eebf22
commit 950ed807c3
No known key found for this signature in database
GPG Key ID: A6551E73A5974C30
2 changed files with 159 additions and 1 deletions

View File

@ -30,12 +30,18 @@ import (
"k8s.io/apiextensions-apiserver/pkg/apiserver"
generatedopenapi "k8s.io/apiextensions-apiserver/pkg/generated/openapi"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructuredscheme"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer/cbor"
"k8s.io/apimachinery/pkg/runtime/serializer/recognizer"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
openapinamer "k8s.io/apiserver/pkg/endpoints/openapi"
"k8s.io/apiserver/pkg/features"
genericregistry "k8s.io/apiserver/pkg/registry/generic"
genericapiserver "k8s.io/apiserver/pkg/server"
genericoptions "k8s.io/apiserver/pkg/server/options"
storagevalue "k8s.io/apiserver/pkg/storage/value"
utilfeature "k8s.io/apiserver/pkg/util/feature"
flowcontrolrequest "k8s.io/apiserver/pkg/util/flowcontrol/request"
"k8s.io/apiserver/pkg/util/openapi"
"k8s.io/apiserver/pkg/util/proxy"
@ -130,8 +136,23 @@ func (o CustomResourceDefinitionsServerOptions) Config() (*apiserver.Config, err
// Avoid messing with anything outside of changes to StorageConfig as that
// may lead to unexpected behavior when the options are applied.
func NewCRDRESTOptionsGetter(etcdOptions genericoptions.EtcdOptions, resourceTransformers storagevalue.ResourceTransformers, tracker flowcontrolrequest.StorageObjectCountTracker) genericregistry.RESTOptionsGetter {
ucbor := cbor.NewSerializer(unstructuredscheme.NewUnstructuredCreator(), unstructuredscheme.NewUnstructuredObjectTyper())
encoder := unstructured.UnstructuredJSONScheme
if utilfeature.TestOnlyFeatureGate.Enabled(features.TestOnlyCBORServingAndStorage) {
encoder = ucbor
}
etcdOptionsCopy := etcdOptions
etcdOptionsCopy.StorageConfig.Codec = unstructured.UnstructuredJSONScheme
etcdOptionsCopy.StorageConfig.Codec = runtime.NewCodec(
encoder,
// Whether the feature gate is enabled or disabled, the decoder must be able to
// recognize any resources stored using the CBOR encoder.
recognizer.NewDecoder(
ucbor,
unstructured.UnstructuredJSONScheme,
),
)
etcdOptionsCopy.StorageConfig.StorageObjectCountTracker = tracker
etcdOptionsCopy.WatchCacheSizes = nil // this control is not provided for custom resources

View File

@ -17,10 +17,14 @@ limitations under the License.
package integration
import (
"bytes"
"context"
"fmt"
"path"
"testing"
"github.com/google/uuid"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
"k8s.io/apiextensions-apiserver/test/integration/fixtures"
@ -31,6 +35,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
cbor "k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/apiserver/pkg/features"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/dynamic"
@ -39,6 +44,138 @@ import (
featuregatetesting "k8s.io/component-base/featuregate/testing"
)
func TestCBORStorageEnablement(t *testing.T) {
crd := &apiextensionsv1.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{Name: "bars.mygroup.example.com"},
Spec: apiextensionsv1.CustomResourceDefinitionSpec{
Group: "mygroup.example.com",
Versions: []apiextensionsv1.CustomResourceDefinitionVersion{{
Name: "v1beta1",
Served: true,
Storage: true,
Schema: fixtures.AllowAllSchema(),
}},
Names: apiextensionsv1.CustomResourceDefinitionNames{
Plural: "bars",
Singular: "bar",
Kind: "Bar",
ListKind: "BarList",
},
Scope: apiextensionsv1.ClusterScoped,
},
}
etcdPrefix := uuid.New().String()
func() {
t.Log("starting server with feature gate disabled")
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.TestOnlyFeatureGate, features.TestOnlyCBORServingAndStorage, false)
tearDown, apiExtensionsClientset, dynamicClient, etcdClient, _, err := fixtures.StartDefaultServerWithClientsAndEtcd(t, "--etcd-prefix", etcdPrefix)
if err != nil {
t.Fatal(err)
}
defer tearDown()
if _, err := fixtures.CreateNewV1CustomResourceDefinition(crd, apiExtensionsClientset, dynamicClient); err != nil {
t.Fatal(err)
}
if _, err := dynamicClient.Resource(schema.GroupVersionResource{Group: "mygroup.example.com", Version: "v1beta1", Resource: "bars"}).Create(
context.TODO(),
&unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "mygroup.example.com/v1beta1",
"kind": "Bar",
"metadata": map[string]interface{}{
"name": "test-storage-json",
},
}},
metav1.CreateOptions{},
); err != nil {
t.Fatal(err)
}
response, err := etcdClient.KV.Get(context.TODO(), path.Join("/", etcdPrefix, crd.Spec.Group, crd.Spec.Names.Plural, "test-storage-json"))
if err != nil {
t.Fatal(err)
}
if n := len(response.Kvs); n != 1 {
t.Fatalf("expected 1 kv, got %d", n)
}
if err := json.Unmarshal(response.Kvs[0].Value, new(interface{})); err != nil {
t.Fatalf("failed to decode stored custom resource as json: %v", err)
}
}()
func() {
t.Log("starting server with feature gate enabled")
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.TestOnlyFeatureGate, features.TestOnlyCBORServingAndStorage, true)
tearDown, _, dynamicClient, etcdClient, _, err := fixtures.StartDefaultServerWithClientsAndEtcd(t, "--etcd-prefix", etcdPrefix)
if err != nil {
t.Fatal(err)
}
defer tearDown()
if _, err := dynamicClient.Resource(schema.GroupVersionResource{Group: "mygroup.example.com", Version: "v1beta1", Resource: "bars"}).Create(
context.TODO(),
&unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "mygroup.example.com/v1beta1",
"kind": "Bar",
"metadata": map[string]interface{}{
"name": "test-storage-cbor",
},
}},
metav1.CreateOptions{},
); err != nil {
t.Fatal(err)
}
response, err := etcdClient.KV.Get(context.TODO(), path.Join("/", etcdPrefix, crd.Spec.Group, crd.Spec.Names.Plural, "test-storage-cbor"))
if err != nil {
t.Fatal(err)
}
if n := len(response.Kvs); n != 1 {
t.Fatalf("expected 1 kv, got %d", n)
}
if !bytes.HasPrefix(response.Kvs[0].Value, []byte{0xd9, 0xd9, 0xf7}) {
// Check for the encoding of the "self-described CBOR" tag which acts as a
// "magic number" for distinguishing CBOR from JSON. Valid CBOR data items
// do not require this prefix, but the Kubernetes CBOR serializer guarantees
// it.
t.Fatalf(`stored custom resource lacks required "self-described CBOR" tag (prefix 0x%x)`, response.Kvs[0].Value[:3])
}
if err := cbor.Unmarshal(response.Kvs[0].Value, new(interface{})); err != nil {
t.Fatalf("failed to decode stored custom resource as cbor: %v", err)
}
for _, name := range []string{"test-storage-json", "test-storage-cbor"} {
_, err := dynamicClient.Resource(schema.GroupVersionResource{Group: "mygroup.example.com", Version: "v1beta1", Resource: "bars"}).Get(context.TODO(), name, metav1.GetOptions{})
if err != nil {
t.Errorf("failed to get cr %q: %v", name, err)
}
}
}()
func() {
t.Log("starting server with feature gate disabled")
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.TestOnlyFeatureGate, features.TestOnlyCBORServingAndStorage, false)
tearDown, _, dynamicClient, _, _, err := fixtures.StartDefaultServerWithClientsAndEtcd(t, "--etcd-prefix", etcdPrefix)
if err != nil {
t.Fatal(err)
}
defer tearDown()
for _, name := range []string{"test-storage-json", "test-storage-cbor"} {
_, err := dynamicClient.Resource(schema.GroupVersionResource{Group: "mygroup.example.com", Version: "v1beta1", Resource: "bars"}).Get(context.TODO(), name, metav1.GetOptions{})
if err != nil {
t.Errorf("failed to get cr %q: %v", name, err)
}
}
}()
}
func TestCBORServingEnablement(t *testing.T) {
for _, tc := range []struct {
name string