Skip to content

Commit 5147eeb

Browse files
authored
Merge pull request kubernetes#128243 from benluddy/cbor-dynamic-integration
KEP-4222: Add CBOR variant of admission webhook integration test.
2 parents 66da447 + 77401d7 commit 5147eeb

File tree

20 files changed

+900
-137
lines changed

20 files changed

+900
-137
lines changed

staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import (
5454
"k8s.io/apimachinery/pkg/runtime"
5555
"k8s.io/apimachinery/pkg/runtime/schema"
5656
"k8s.io/apimachinery/pkg/runtime/serializer"
57+
"k8s.io/apimachinery/pkg/runtime/serializer/cbor"
5758
"k8s.io/apimachinery/pkg/runtime/serializer/json"
5859
"k8s.io/apimachinery/pkg/runtime/serializer/protobuf"
5960
"k8s.io/apimachinery/pkg/runtime/serializer/versioning"
@@ -69,8 +70,10 @@ import (
6970
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
7071
"k8s.io/apiserver/pkg/endpoints/metrics"
7172
apirequest "k8s.io/apiserver/pkg/endpoints/request"
73+
"k8s.io/apiserver/pkg/features"
7274
"k8s.io/apiserver/pkg/registry/generic"
7375
genericfilters "k8s.io/apiserver/pkg/server/filters"
76+
utilfeature "k8s.io/apiserver/pkg/util/feature"
7477
"k8s.io/apiserver/pkg/util/webhook"
7578
"k8s.io/apiserver/pkg/warning"
7679
"k8s.io/client-go/scale"
@@ -600,6 +603,20 @@ func (r *crdHandler) GetCustomResourceListerCollectionDeleter(crd *apiextensions
600603
return info.storages[info.storageVersion].CustomResource, nil
601604
}
602605

606+
func newCBORSerializerInfo(creater runtime.ObjectCreater, typer runtime.ObjectTyper) runtime.SerializerInfo {
607+
return runtime.SerializerInfo{
608+
MediaType: "application/cbor",
609+
MediaTypeType: "application",
610+
MediaTypeSubType: "cbor",
611+
Serializer: cbor.NewSerializer(creater, typer),
612+
StrictSerializer: cbor.NewSerializer(creater, typer, cbor.Strict(true)),
613+
StreamSerializer: &runtime.StreamSerializerInfo{
614+
Framer: cbor.NewFramer(),
615+
Serializer: cbor.NewSerializer(creater, typer, cbor.Transcode(false)),
616+
},
617+
}
618+
}
619+
603620
// getOrCreateServingInfoFor gets the CRD serving info for the given CRD UID if the key exists in the storage map.
604621
// Otherwise the function fetches the up-to-date CRD using the given CRD name and creates CRD serving info.
605622
func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crdInfo, error) {
@@ -892,6 +909,11 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd
892909
},
893910
},
894911
}
912+
913+
if utilfeature.TestOnlyFeatureGate.Enabled(features.TestOnlyCBORServingAndStorage) {
914+
negotiatedSerializer.supportedMediaTypes = append(negotiatedSerializer.supportedMediaTypes, newCBORSerializerInfo(creator, typer))
915+
}
916+
895917
var standardSerializers []runtime.SerializerInfo
896918
for _, s := range negotiatedSerializer.SupportedMediaTypes() {
897919
if s.MediaType == runtime.ContentTypeProtobuf {
@@ -955,7 +977,11 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd
955977
scaleScope := *requestScopes[v.Name]
956978
scaleConverter := scale.NewScaleConverter()
957979
scaleScope.Subresource = "scale"
958-
scaleScope.Serializer = serializer.NewCodecFactory(scaleConverter.Scheme())
980+
var opts []serializer.CodecFactoryOptionsMutator
981+
if utilfeature.TestOnlyFeatureGate.Enabled(features.TestOnlyCBORServingAndStorage) {
982+
opts = append(opts, serializer.WithSerializer(newCBORSerializerInfo))
983+
}
984+
scaleScope.Serializer = serializer.NewCodecFactory(scaleConverter.Scheme(), opts...)
959985
scaleScope.Kind = autoscalingv1.SchemeGroupVersion.WithKind("Scale")
960986
scaleScope.Namer = handlers.ContextBasedNaming{
961987
Namer: meta.NewAccessor(),
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
/*
2+
Copyright 2024 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package integration
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"testing"
23+
24+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
25+
apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
26+
"k8s.io/apiextensions-apiserver/test/integration/fixtures"
27+
"k8s.io/apimachinery/pkg/api/errors"
28+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
30+
"k8s.io/apimachinery/pkg/runtime"
31+
"k8s.io/apimachinery/pkg/runtime/schema"
32+
"k8s.io/apimachinery/pkg/runtime/serializer"
33+
cbor "k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct"
34+
"k8s.io/apiserver/pkg/features"
35+
utilfeature "k8s.io/apiserver/pkg/util/feature"
36+
"k8s.io/client-go/dynamic"
37+
"k8s.io/client-go/rest"
38+
"k8s.io/client-go/util/retry"
39+
featuregatetesting "k8s.io/component-base/featuregate/testing"
40+
)
41+
42+
func TestCBORServingEnablement(t *testing.T) {
43+
for _, tc := range []struct {
44+
name string
45+
enabled bool
46+
}{
47+
{name: "enabled", enabled: true},
48+
{name: "disabled", enabled: false},
49+
} {
50+
t.Run(tc.name, func(t *testing.T) {
51+
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.TestOnlyFeatureGate, features.TestOnlyCBORServingAndStorage, tc.enabled)
52+
53+
tearDown, config, _, err := fixtures.StartDefaultServer(t)
54+
if err != nil {
55+
t.Fatal(err)
56+
}
57+
defer tearDown()
58+
59+
apiExtensionsClientset, err := apiextensionsclientset.NewForConfig(config)
60+
if err != nil {
61+
t.Fatal(err)
62+
}
63+
dynamicClient, err := dynamic.NewForConfig(config)
64+
if err != nil {
65+
t.Fatal(err)
66+
}
67+
68+
crd := &apiextensionsv1.CustomResourceDefinition{
69+
ObjectMeta: metav1.ObjectMeta{Name: "foos.mygroup.example.com"},
70+
Spec: apiextensionsv1.CustomResourceDefinitionSpec{
71+
Group: "mygroup.example.com",
72+
Versions: []apiextensionsv1.CustomResourceDefinitionVersion{{
73+
Name: "v1beta1",
74+
Served: true,
75+
Storage: true,
76+
Schema: fixtures.AllowAllSchema(),
77+
Subresources: &apiextensionsv1.CustomResourceSubresources{
78+
Status: &apiextensionsv1.CustomResourceSubresourceStatus{},
79+
Scale: &apiextensionsv1.CustomResourceSubresourceScale{
80+
SpecReplicasPath: ".spec.replicas",
81+
StatusReplicasPath: ".status.replicas",
82+
},
83+
},
84+
}},
85+
Names: apiextensionsv1.CustomResourceDefinitionNames{
86+
Plural: "foos",
87+
Singular: "foo",
88+
Kind: "Foo",
89+
ListKind: "FooList",
90+
},
91+
Scope: apiextensionsv1.ClusterScoped,
92+
},
93+
}
94+
if _, err = fixtures.CreateNewV1CustomResourceDefinition(crd, apiExtensionsClientset, dynamicClient); err != nil {
95+
t.Fatal(err)
96+
}
97+
cr, err := dynamicClient.Resource(schema.GroupVersionResource{Group: "mygroup.example.com", Version: "v1beta1", Resource: "foos"}).Create(
98+
context.TODO(),
99+
&unstructured.Unstructured{
100+
Object: map[string]interface{}{
101+
"apiVersion": "mygroup.example.com/v1beta1",
102+
"kind": "Foo",
103+
"metadata": map[string]interface{}{
104+
"name": fmt.Sprintf("test-cbor-%s", tc.name),
105+
},
106+
"spec": map[string]interface{}{
107+
"replicas": int64(0),
108+
},
109+
"status": map[string]interface{}{
110+
"replicas": int64(0),
111+
},
112+
}},
113+
metav1.CreateOptions{},
114+
)
115+
if err != nil {
116+
t.Fatal(err)
117+
}
118+
119+
config = rest.CopyConfig(config)
120+
config.NegotiatedSerializer = serializer.NewCodecFactory(runtime.NewScheme()).WithoutConversion()
121+
config.APIPath = "/apis"
122+
config.GroupVersion = &schema.GroupVersion{Group: "mygroup.example.com", Version: "v1beta1"}
123+
restClient, err := rest.RESTClientFor(config)
124+
if err != nil {
125+
t.Fatal(err)
126+
}
127+
128+
for _, subresource := range []string{"", "status", "scale"} {
129+
err = restClient.Get().
130+
Resource(crd.Spec.Names.Plural).
131+
SubResource(subresource).
132+
Name(cr.GetName()).
133+
SetHeader("Accept", "application/cbor").
134+
Do(context.TODO()).Error()
135+
switch {
136+
case tc.enabled && err == nil:
137+
// ok
138+
case !tc.enabled && errors.IsNotAcceptable(err):
139+
// ok
140+
default:
141+
t.Errorf("unexpected error on read (subresource %q): %v", subresource, err)
142+
}
143+
}
144+
145+
createBody, err := cbor.Marshal(map[string]interface{}{
146+
"apiVersion": "mygroup.example.com/v1beta1",
147+
"kind": "Foo",
148+
"metadata": map[string]interface{}{
149+
"name": fmt.Sprintf("test-cbor-%s-2", tc.name),
150+
},
151+
"spec": map[string]interface{}{
152+
"replicas": int64(0),
153+
},
154+
"status": map[string]interface{}{
155+
"replicas": int64(0),
156+
},
157+
})
158+
if err != nil {
159+
t.Fatal(err)
160+
}
161+
err = restClient.Post().
162+
Resource(crd.Spec.Names.Plural).
163+
SetHeader("Content-Type", "application/cbor").
164+
Body(createBody).
165+
Do(context.TODO()).Error()
166+
switch {
167+
case tc.enabled && err == nil:
168+
// ok
169+
case !tc.enabled && errors.IsUnsupportedMediaType(err):
170+
// ok
171+
default:
172+
t.Errorf("unexpected error on write: %v", err)
173+
}
174+
175+
scaleBody, err := cbor.Marshal(map[string]interface{}{
176+
"apiVersion": "autoscaling/v1",
177+
"kind": "Scale",
178+
"metadata": map[string]interface{}{
179+
"name": cr.GetName(),
180+
},
181+
"spec": map[string]interface{}{
182+
"replicas": int64(0),
183+
},
184+
"status": map[string]interface{}{
185+
"replicas": int64(0),
186+
},
187+
})
188+
if err != nil {
189+
t.Fatal(err)
190+
}
191+
err = restClient.Put().
192+
Resource(crd.Spec.Names.Plural).
193+
SubResource("scale").
194+
Name(cr.GetName()).
195+
SetHeader("Content-Type", "application/cbor").
196+
Body(scaleBody).
197+
Do(context.TODO()).Error()
198+
switch {
199+
case tc.enabled && err == nil:
200+
// ok
201+
case !tc.enabled && errors.IsUnsupportedMediaType(err):
202+
// ok
203+
default:
204+
t.Errorf("unexpected error on scale write: %v", err)
205+
}
206+
207+
err = retry.RetryOnConflict(retry.DefaultBackoff, func() error {
208+
latest, err := dynamicClient.Resource(schema.GroupVersionResource{Group: "mygroup.example.com", Version: "v1beta1", Resource: "foos"}).Get(context.TODO(), cr.GetName(), metav1.GetOptions{})
209+
if err != nil {
210+
t.Fatal(err)
211+
}
212+
213+
statusBody, err := cbor.Marshal(latest.Object)
214+
if err != nil {
215+
t.Fatal(err)
216+
}
217+
218+
return restClient.Put().
219+
Resource(crd.Spec.Names.Plural).
220+
SubResource("status").
221+
Name(cr.GetName()).
222+
SetHeader("Content-Type", "application/cbor").
223+
Body(statusBody).
224+
Do(context.TODO()).Error()
225+
})
226+
switch {
227+
case tc.enabled && err == nil:
228+
// ok
229+
case !tc.enabled && errors.IsUnsupportedMediaType(err):
230+
// ok
231+
default:
232+
t.Fatalf("unexpected error on status write: %v", err)
233+
}
234+
})
235+
}
236+
}

staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme/register.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,16 @@ import (
2424
)
2525

2626
// Scheme is the registry for any type that adheres to the meta API spec.
27-
var scheme = runtime.NewScheme()
27+
var Scheme = runtime.NewScheme()
2828

2929
// Codecs provides access to encoding and decoding for the scheme.
30-
var Codecs = serializer.NewCodecFactory(scheme)
30+
var Codecs = serializer.NewCodecFactory(Scheme)
3131

3232
// ParameterCodec handles versioning of objects that are converted to query parameters.
33-
var ParameterCodec = runtime.NewParameterCodec(scheme)
33+
var ParameterCodec = runtime.NewParameterCodec(Scheme)
3434

3535
// Unlike other API groups, meta internal knows about all meta external versions, but keeps
3636
// the logic for conversion private.
3737
func init() {
38-
utilruntime.Must(internalversion.AddToScheme(scheme))
38+
utilruntime.Must(internalversion.AddToScheme(Scheme))
3939
}

staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme/register_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ func TestListOptions(t *testing.T) {
3737
Watch: true,
3838
}
3939
out := &metainternalversion.ListOptions{}
40-
if err := scheme.Convert(in, out, nil); err != nil {
40+
if err := Scheme.Convert(in, out, nil); err != nil {
4141
t.Fatal(err)
4242
}
4343
actual := &metav1.ListOptions{}
44-
if err := scheme.Convert(out, actual, nil); err != nil {
44+
if err := Scheme.Convert(out, actual, nil); err != nil {
4545
t.Fatal(err)
4646
}
4747
if !reflect.DeepEqual(in, actual) {
@@ -54,16 +54,16 @@ func TestListOptions(t *testing.T) {
5454
{FieldSelector: "a!!!"},
5555
} {
5656
out = &metainternalversion.ListOptions{}
57-
if err := scheme.Convert(failingObject, out, nil); err == nil {
57+
if err := Scheme.Convert(failingObject, out, nil); err == nil {
5858
t.Errorf("%d: unexpected conversion: %#v", i, out)
5959
}
6060
}
6161

6262
// verify kind registration
63-
if gvks, unversioned, err := scheme.ObjectKinds(in); err != nil || unversioned || gvks[0] != metav1.SchemeGroupVersion.WithKind("ListOptions") {
63+
if gvks, unversioned, err := Scheme.ObjectKinds(in); err != nil || unversioned || gvks[0] != metav1.SchemeGroupVersion.WithKind("ListOptions") {
6464
t.Errorf("unexpected: %v %v %v", gvks[0], unversioned, err)
6565
}
66-
if gvks, unversioned, err := scheme.ObjectKinds(out); err != nil || unversioned || gvks[0] != metainternalversion.SchemeGroupVersion.WithKind("ListOptions") {
66+
if gvks, unversioned, err := Scheme.ObjectKinds(out); err != nil || unversioned || gvks[0] != metainternalversion.SchemeGroupVersion.WithKind("ListOptions") {
6767
t.Errorf("unexpected: %v %v %v", gvks[0], unversioned, err)
6868
}
6969

staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme/roundtrip_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,5 @@ import (
2424
)
2525

2626
func TestRoundTrip(t *testing.T) {
27-
roundtrip.RoundTripTestForScheme(t, scheme, fuzzer.Funcs)
27+
roundtrip.RoundTripTestForScheme(t, Scheme, fuzzer.Funcs)
2828
}

0 commit comments

Comments
 (0)