@@ -26,6 +26,7 @@ import (
26
26
"net/http"
27
27
"path"
28
28
"reflect"
29
+ "slices"
29
30
"strconv"
30
31
"strings"
31
32
"sync"
@@ -48,22 +49,29 @@ import (
48
49
"k8s.io/apimachinery/pkg/fields"
49
50
"k8s.io/apimachinery/pkg/runtime"
50
51
"k8s.io/apimachinery/pkg/runtime/schema"
52
+ jsonserializer "k8s.io/apimachinery/pkg/runtime/serializer/json"
51
53
"k8s.io/apimachinery/pkg/runtime/serializer/protobuf"
54
+ "k8s.io/apimachinery/pkg/runtime/serializer/recognizer"
52
55
"k8s.io/apimachinery/pkg/runtime/serializer/streaming"
53
56
"k8s.io/apimachinery/pkg/types"
57
+ utiljson "k8s.io/apimachinery/pkg/util/json"
54
58
"k8s.io/apimachinery/pkg/util/uuid"
55
59
"k8s.io/apimachinery/pkg/util/wait"
56
60
"k8s.io/apimachinery/pkg/watch"
57
61
"k8s.io/apiserver/pkg/endpoints/handlers"
58
62
"k8s.io/apiserver/pkg/storage/storagebackend"
63
+ utilfeature "k8s.io/apiserver/pkg/util/feature"
59
64
"k8s.io/client-go/discovery/cached/memory"
60
65
"k8s.io/client-go/dynamic"
66
+ clientfeatures "k8s.io/client-go/features"
67
+ clientfeaturestesting "k8s.io/client-go/features/testing"
61
68
clientset "k8s.io/client-go/kubernetes"
62
69
appsv1 "k8s.io/client-go/kubernetes/typed/apps/v1"
63
70
"k8s.io/client-go/metadata"
64
71
restclient "k8s.io/client-go/rest"
65
72
"k8s.io/client-go/restmapper"
66
73
"k8s.io/client-go/tools/pager"
74
+ featuregatetesting "k8s.io/component-base/featuregate/testing"
67
75
utilversion "k8s.io/component-base/version"
68
76
"k8s.io/klog/v2"
69
77
"k8s.io/kubernetes/cmd/kube-apiserver/app/options"
@@ -3352,3 +3360,139 @@ func assertManagedFields(t *testing.T, obj *unstructured.Unstructured) {
3352
3360
func int32Ptr (i int32 ) * int32 {
3353
3361
return & i
3354
3362
}
3363
+
3364
+ // TestDefaultStorageEncoding verifies that the storage encoding for all built-in resources is
3365
+ // Protobuf.
3366
+ func TestDefaultStorageEncoding (t * testing.T ) {
3367
+ featuregatetesting .SetFeatureGateDuringTest (t , utilfeature .DefaultFeatureGate , "AllAlpha" , true )
3368
+ featuregatetesting .SetFeatureGateDuringTest (t , utilfeature .DefaultFeatureGate , "AllBeta" , true )
3369
+
3370
+ // TODO: Remove this override when the codecs used for serving all built-in APIs are wired to the apiserver feature gate.
3371
+ clientfeaturestesting .SetFeatureDuringTest (t , clientfeatures .ClientsPreferCBOR , false )
3372
+
3373
+ protobufRecognizer := protobuf .NewSerializer (runtime .NewScheme (), runtime .NewScheme ())
3374
+ var recognizersByGroup map [string ]recognizer.RecognizingDecoder
3375
+ {
3376
+ jsonRecognizer := jsonserializer .NewSerializerWithOptions (jsonserializer .DefaultMetaFactory , runtime .NewScheme (), runtime .NewScheme (), jsonserializer.SerializerOptions {})
3377
+ recognizersByGroup = map [string ]recognizer.RecognizingDecoder {
3378
+ // No new exceptions should be added. Once these groups begin using Protobuf
3379
+ // for storage, the exception list should be removed.
3380
+ "apiextensions.k8s.io" : jsonRecognizer ,
3381
+ "apiregistration.k8s.io" : jsonRecognizer ,
3382
+ }
3383
+ }
3384
+
3385
+ storageConfig := framework .SharedEtcd ()
3386
+ etcdPrefix := string (uuid .NewUUID ())
3387
+ storageConfig .Prefix = path .Join (etcdPrefix , "registry" )
3388
+ server := kubeapiservertesting .StartTestServerOrDie (
3389
+ t ,
3390
+ kubeapiservertesting .NewDefaultTestServerOptions (),
3391
+ []string {
3392
+ "--runtime-config=api/all=true" ,
3393
+ "--disable-admission-plugins=ServiceAccount" ,
3394
+ },
3395
+ storageConfig ,
3396
+ )
3397
+ t .Cleanup (server .TearDownFn )
3398
+
3399
+ client , err := clientset .NewForConfig (server .ClientConfig )
3400
+ if err != nil {
3401
+ t .Fatal (err )
3402
+ }
3403
+
3404
+ dynamicClient , err := dynamic .NewForConfig (server .ClientConfig )
3405
+ if err != nil {
3406
+ t .Fatal (err )
3407
+ }
3408
+
3409
+ etcdClient , kvClient , err := integration .GetEtcdClients (storageConfig .Transport )
3410
+ if err != nil {
3411
+ t .Fatal (err )
3412
+ }
3413
+ t .Cleanup (func () {
3414
+ if err := etcdClient .Close (); err != nil {
3415
+ t .Error (err )
3416
+ }
3417
+ })
3418
+
3419
+ const NamespaceName = "test-protobuf-default-storage-encoding"
3420
+ if _ , err := client .CoreV1 ().Namespaces ().Create (context .TODO (), & v1.Namespace {ObjectMeta : metav1.ObjectMeta {Name : NamespaceName }}, metav1.CreateOptions {}); err != nil {
3421
+ t .Fatal (err )
3422
+ }
3423
+
3424
+ storageDataByResource := etcd .GetEtcdStorageDataForNamespace (NamespaceName )
3425
+
3426
+ _ , lists , err := client .Discovery ().ServerGroupsAndResources ()
3427
+ if err != nil {
3428
+ t .Fatal (err )
3429
+ }
3430
+ for _ , list := range lists {
3431
+ for _ , resource := range list .APIResources {
3432
+ gv , err := schema .ParseGroupVersion (list .GroupVersion )
3433
+ if err != nil {
3434
+ t .Fatal (err )
3435
+ }
3436
+ if resource .Group != "" {
3437
+ gv .Group = resource .Group
3438
+ }
3439
+ if resource .Version != "" {
3440
+ gv .Version = resource .Version
3441
+ }
3442
+
3443
+ if strings .Contains (resource .Name , "/" ) {
3444
+ continue
3445
+ }
3446
+
3447
+ if ! slices .Contains (resource .Verbs , "create" ) {
3448
+ continue
3449
+ }
3450
+
3451
+ gvr := gv .WithResource (resource .Name )
3452
+
3453
+ storageData := storageDataByResource [gvr ]
3454
+ if storageData .Stub == "" {
3455
+ continue
3456
+ }
3457
+
3458
+ name := fmt .Sprintf ("%s.%s.%s" , gvr .Resource , gvr .Version , gvr .Group )
3459
+ if gvr .Group == "" {
3460
+ name = fmt .Sprintf ("%s.%s" , gvr .Resource , gvr .Version )
3461
+ }
3462
+ t .Run (name , func (t * testing.T ) {
3463
+ var o unstructured.Unstructured
3464
+ if err := utiljson .Unmarshal ([]byte (storageData .Stub ), & o .Object ); err != nil {
3465
+ t .Fatal (err )
3466
+ }
3467
+
3468
+ resourceClient := dynamicClient .Resource (gvr ).Namespace (NamespaceName )
3469
+ if ! resource .Namespaced {
3470
+ resourceClient = dynamicClient .Resource (gvr )
3471
+ }
3472
+ if _ , err := resourceClient .Create (context .TODO (), & o , metav1.CreateOptions {}); err != nil {
3473
+ t .Fatal (err )
3474
+ }
3475
+
3476
+ response , err := kvClient .Get (context .TODO (), path .Join ("/" , etcdPrefix , storageData .ExpectedEtcdPath ))
3477
+ if err != nil {
3478
+ t .Fatal (err )
3479
+ }
3480
+
3481
+ if n := len (response .Kvs ); n != 1 {
3482
+ t .Fatalf ("expected 1 kv, got %d" , n )
3483
+ }
3484
+ recognizer , ok := recognizersByGroup [gvr .Group ]
3485
+ if ! ok {
3486
+ recognizer = protobufRecognizer
3487
+ }
3488
+ recognized , _ , err := recognizer .RecognizesData (response .Kvs [0 ].Value )
3489
+ if err != nil {
3490
+ t .Fatal (err )
3491
+ }
3492
+ if ! recognized {
3493
+ t .Fatal ("stored object encoding not recognized as protobuf" )
3494
+ }
3495
+ })
3496
+ }
3497
+ }
3498
+ }
0 commit comments