1
16
17 package apiserver
18
19 import (
20 "context"
21 "encoding/json"
22 "flag"
23 "fmt"
24 "strings"
25 "testing"
26 "time"
27
28 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
29 apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
30 "k8s.io/apiextensions-apiserver/test/integration/fixtures"
31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
32 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
33 "k8s.io/apimachinery/pkg/runtime/schema"
34 "k8s.io/apimachinery/pkg/types"
35 "k8s.io/client-go/dynamic"
36 clientset "k8s.io/client-go/kubernetes"
37 "k8s.io/client-go/rest"
38 "k8s.io/klog/v2"
39 kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
40
41 "k8s.io/kubernetes/test/integration/framework"
42 )
43
44 var (
45 invalidBodyJSON = `
46 {
47 "apiVersion": "apps/v1",
48 "kind": "Deployment",
49 "metadata": {
50 "name": "dupename",
51 "name": "%s",
52 "labels": {"app": "nginx"},
53 "unknownMeta": "metaVal"
54 },
55 "spec": {
56 "unknown1": "val1",
57 "unknownDupe": "valDupe",
58 "unknownDupe": "valDupe2",
59 "paused": true,
60 "paused": false,
61 "selector": {
62 "matchLabels": {
63 "app": "nginx"
64 }
65 },
66 "template": {
67 "metadata": {
68 "labels": {
69 "app": "nginx"
70 }
71 },
72 "spec": {
73 "containers": [{
74 "name": "nginx",
75 "image": "nginx:latest",
76 "unknownNested": "val1",
77 "imagePullPolicy": "Always",
78 "imagePullPolicy": "Never"
79 }]
80 }
81 }
82 }
83 }
84 `
85 validBodyJSON = `
86 {
87 "apiVersion": "apps/v1",
88 "kind": "Deployment",
89 "metadata": {
90 "name": "%s",
91 "labels": {"app": "nginx"},
92 "annotations": {"a1": "foo", "a2": "bar"}
93 },
94 "spec": {
95 "selector": {
96 "matchLabels": {
97 "app": "nginx"
98 }
99 },
100 "template": {
101 "metadata": {
102 "labels": {
103 "app": "nginx"
104 }
105 },
106 "spec": {
107 "containers": [{
108 "name": "nginx",
109 "image": "nginx:latest",
110 "imagePullPolicy": "Always"
111 }]
112 }
113 },
114 "replicas": 2
115 }
116 }`
117
118 invalidBodyYAML = `apiVersion: apps/v1
119 kind: Deployment
120 metadata:
121 name: dupename
122 name: %s
123 unknownMeta: metaVal
124 labels:
125 app: nginx
126 spec:
127 unknown1: val1
128 unknownDupe: valDupe
129 unknownDupe: valDupe2
130 paused: true
131 paused: false
132 selector:
133 matchLabels:
134 app: nginx
135 template:
136 metadata:
137 labels:
138 app: nginx
139 spec:
140 containers:
141 - name: nginx
142 image: nginx:latest
143 unknownNested: val1
144 imagePullPolicy: Always
145 imagePullPolicy: Never`
146
147 validBodyYAML = `apiVersion: apps/v1
148 kind: Deployment
149 metadata:
150 name: %s
151 labels:
152 app: nginx
153 annotations:
154 a1: foo
155 a2: bar
156 spec:
157 replicas: 2
158 paused: true
159 selector:
160 matchLabels:
161 app: nginx
162 template:
163 metadata:
164 labels:
165 app: nginx
166 spec:
167 containers:
168 - name: nginx
169 image: nginx:latest
170 imagePullPolicy: Always`
171
172 applyInvalidBody = `{
173 "apiVersion": "apps/v1",
174 "kind": "Deployment",
175 "metadata": {
176 "name": "%s",
177 "labels": {"app": "nginx"}
178 },
179 "spec": {
180 "paused": false,
181 "paused": true,
182 "selector": {
183 "matchLabels": {
184 "app": "nginx"
185 }
186 },
187 "template": {
188 "metadata": {
189 "labels": {
190 "app": "nginx"
191 }
192 },
193 "spec": {
194 "containers": [{
195 "name": "nginx",
196 "image": "nginx:latest",
197 "imagePullPolicy": "Never",
198 "imagePullPolicy": "Always"
199 }]
200 }
201 }
202 }
203 }`
204 applyValidBody = `
205 {
206 "apiVersion": "apps/v1",
207 "kind": "Deployment",
208 "metadata": {
209 "name": "%s",
210 "labels": {"app": "nginx"},
211 "annotations": {"a1": "foo", "a2": "bar"}
212 },
213 "spec": {
214 "selector": {
215 "matchLabels": {
216 "app": "nginx"
217 }
218 },
219 "template": {
220 "metadata": {
221 "labels": {
222 "app": "nginx"
223 }
224 },
225 "spec": {
226 "containers": [{
227 "name": "nginx",
228 "image": "nginx:latest",
229 "imagePullPolicy": "Always"
230 }]
231 }
232 },
233 "replicas": 3
234 }
235 }`
236 crdInvalidBody = `
237 {
238 "apiVersion": "%s",
239 "kind": "%s",
240 "metadata": {
241 "name": "dupename",
242 "name": "%s",
243 "unknownMeta": "metaVal",
244 "resourceVersion": "%s"
245 },
246 "spec": {
247 "unknown1": "val1",
248 "unknownDupe": "valDupe",
249 "unknownDupe": "valDupe2",
250 "knownField1": "val1",
251 "knownField1": "val2",
252 "ports": [{
253 "name": "portName",
254 "containerPort": 8080,
255 "protocol": "TCP",
256 "hostPort": 8081,
257 "hostPort": 8082,
258 "unknownNested": "val"
259 }],
260 "embeddedObj": {
261 "apiVersion": "v1",
262 "kind": "ConfigMap",
263 "metadata": {
264 "name": "my-cm",
265 "namespace": "my-ns",
266 "unknownEmbeddedMeta": "foo"
267 }
268 }
269 }
270 }`
271
272 crdValidBody = `
273 {
274 "apiVersion": "%s",
275 "kind": "%s",
276 "metadata": {
277 "name": "%s",
278 "resourceVersion": "%s"
279 },
280 "spec": {
281 "knownField1": "val1",
282 "ports": [{
283 "name": "portName",
284 "containerPort": 8080,
285 "protocol": "TCP",
286 "hostPort": 8081
287 }],
288 "embeddedObj": {
289 "apiVersion": "v1",
290 "kind": "ConfigMap",
291 "metadata": {
292 "name": "my-cm"
293 }
294 }
295 }
296 }
297 `
298
299 crdInvalidBodyYAML = `
300 apiVersion: "%s"
301 kind: "%s"
302 metadata:
303 name: dupename
304 name: "%s"
305 resourceVersion: "%s"
306 unknownMeta: metaVal
307 spec:
308 unknown1: val1
309 unknownDupe: valDupe
310 unknownDupe: valDupe2
311 knownField1: val1
312 knownField1: val2
313 ports:
314 - name: portName
315 containerPort: 8080
316 protocol: TCP
317 hostPort: 8081
318 hostPort: 8082
319 unknownNested: val
320 embeddedObj:
321 apiVersion: v1
322 kind: ConfigMap
323 metadata:
324 name: my-cm
325 namespace: my-ns
326 unknownEmbeddedMeta: foo`
327
328 crdValidBodyYAML = `
329 apiVersion: "%s"
330 kind: "%s"
331 metadata:
332 name: "%s"
333 resourceVersion: "%s"
334 spec:
335 knownField1: val1
336 ports:
337 - name: portName
338 containerPort: 8080
339 protocol: TCP
340 hostPort: 8081
341 embeddedObj:
342 apiVersion: v1
343 kind: ConfigMap
344 metadata:
345 name: my-cm
346 namespace: my-ns`
347
348 crdApplyInvalidBody = `
349 {
350 "apiVersion": "%s",
351 "kind": "%s",
352 "metadata": {
353 "name": "%s"
354 },
355 "spec": {
356 "knownField1": "val1",
357 "knownField1": "val2",
358 "ports": [{
359 "name": "portName",
360 "containerPort": 8080,
361 "protocol": "TCP",
362 "hostPort": 8081,
363 "hostPort": 8082
364 }],
365 "embeddedObj": {
366 "apiVersion": "v1",
367 "kind": "ConfigMap",
368 "metadata": {
369 "name": "my-cm",
370 "namespace": "my-ns"
371 }
372 }
373 }
374 }`
375
376 crdApplyValidBody = `
377 {
378 "apiVersion": "%s",
379 "kind": "%s",
380 "metadata": {
381 "name": "%s"
382 },
383 "spec": {
384 "knownField1": "val1",
385 "ports": [{
386 "name": "portName",
387 "containerPort": 8080,
388 "protocol": "TCP",
389 "hostPort": 8082
390 }],
391 "embeddedObj": {
392 "apiVersion": "v1",
393 "kind": "ConfigMap",
394 "metadata": {
395 "name": "my-cm",
396 "namespace": "my-ns"
397 }
398 }
399 }
400 }`
401
402 crdApplyValidBody2 = `
403 {
404 "apiVersion": "%s",
405 "kind": "%s",
406 "metadata": {
407 "name": "%s"
408 },
409 "spec": {
410 "knownField1": "val2",
411 "ports": [{
412 "name": "portName",
413 "containerPort": 8080,
414 "protocol": "TCP",
415 "hostPort": 8083
416 }],
417 "embeddedObj": {
418 "apiVersion": "v1",
419 "kind": "ConfigMap",
420 "metadata": {
421 "name": "my-cm",
422 "namespace": "my-ns"
423 }
424 }
425 }
426 }`
427
428 crdApplyFinalizerBody = `
429 {
430 "apiVersion": "%s",
431 "kind": "%s",
432 "metadata": {
433 "name": "%s",
434 "finalizers": %s
435 },
436 "spec": {
437 "knownField1": "val1",
438 "ports": [{
439 "name": "portName",
440 "containerPort": 8080,
441 "protocol": "TCP",
442 "hostPort": 8082
443 }],
444 "embeddedObj": {
445 "apiVersion": "v1",
446 "kind": "ConfigMap",
447 "metadata": {
448 "name": "my-cm",
449 "namespace": "my-ns"
450 }
451 }
452 }
453 }`
454
455 patchYAMLBody = `
456 apiVersion: %s
457 kind: %s
458 metadata:
459 name: %s
460 finalizers:
461 - test/finalizer
462 spec:
463 cronSpec: "* * * * */5"
464 ports:
465 - name: x
466 containerPort: 80
467 protocol: TCP
468 `
469
470 crdSchemaBase = `
471 {
472 "openAPIV3Schema": {
473 "type": "object",
474 "properties": {
475 "spec": {
476 "type": "object",
477 %s
478 "properties": {
479 "cronSpec": {
480 "type": "string",
481 "pattern": "^(\\d+|\\*)(/\\d+)?(\\s+(\\d+|\\*)(/\\d+)?){4}$"
482 },
483 "knownField1": {
484 "type": "string"
485 },
486 "embeddedObj": {
487 "x-kubernetes-embedded-resource": true,
488 "type": "object",
489 "properties": {
490 "apiversion": {
491 "type": "string"
492 },
493 "kind": {
494 "type": "string"
495 },
496 "metadata": {
497 "type": "object"
498 }
499 }
500 },
501 "ports": {
502 "type": "array",
503 "x-kubernetes-list-map-keys": [
504 "containerPort",
505 "protocol"
506 ],
507 "x-kubernetes-list-type": "map",
508 "items": {
509 "properties": {
510 "containerPort": {
511 "format": "int32",
512 "type": "integer"
513 },
514 "hostIP": {
515 "type": "string"
516 },
517 "hostPort": {
518 "format": "int32",
519 "type": "integer"
520 },
521 "name": {
522 "type": "string"
523 },
524 "protocol": {
525 "type": "string"
526 }
527 },
528 "required": [
529 "containerPort",
530 "protocol"
531 ],
532 "type": "object"
533 }
534 }
535 }
536 }
537 }
538 }
539 }
540 `
541 )
542
543 func TestFieldValidation(t *testing.T) {
544 server, err := kubeapiservertesting.StartTestServer(t, kubeapiservertesting.NewDefaultTestServerOptions(), nil, framework.SharedEtcd())
545 if err != nil {
546 t.Fatal(err)
547 }
548 config := server.ClientConfig
549 defer server.TearDownFn()
550
551
552 config.WarningHandler = rest.NoWarnings{}
553
554 schemaCRD := setupCRD(t, config, "schema.example.com", false)
555 schemaGVR := schema.GroupVersionResource{
556 Group: schemaCRD.Spec.Group,
557 Version: schemaCRD.Spec.Versions[0].Name,
558 Resource: schemaCRD.Spec.Names.Plural,
559 }
560 schemaGVK := schema.GroupVersionKind{
561 Group: schemaCRD.Spec.Group,
562 Version: schemaCRD.Spec.Versions[0].Name,
563 Kind: schemaCRD.Spec.Names.Kind,
564 }
565
566 schemalessCRD := setupCRD(t, config, "schemaless.example.com", true)
567 schemalessGVR := schema.GroupVersionResource{
568 Group: schemalessCRD.Spec.Group,
569 Version: schemalessCRD.Spec.Versions[0].Name,
570 Resource: schemalessCRD.Spec.Names.Plural,
571 }
572 schemalessGVK := schema.GroupVersionKind{
573 Group: schemalessCRD.Spec.Group,
574 Version: schemalessCRD.Spec.Versions[0].Name,
575 Kind: schemalessCRD.Spec.Names.Kind,
576 }
577
578 client := clientset.NewForConfigOrDie(config)
579 rest := client.Discovery().RESTClient()
580
581 t.Run("Post", func(t *testing.T) { testFieldValidationPost(t, client) })
582 t.Run("Put", func(t *testing.T) { testFieldValidationPut(t, client) })
583 t.Run("PatchTyped", func(t *testing.T) { testFieldValidationPatchTyped(t, client) })
584 t.Run("SMP", func(t *testing.T) { testFieldValidationSMP(t, client) })
585 t.Run("ApplyCreate", func(t *testing.T) { testFieldValidationApplyCreate(t, client) })
586 t.Run("ApplyUpdate", func(t *testing.T) { testFieldValidationApplyUpdate(t, client) })
587
588 t.Run("PostCRD", func(t *testing.T) { testFieldValidationPostCRD(t, rest, schemaGVK, schemaGVR) })
589 t.Run("PutCRD", func(t *testing.T) { testFieldValidationPutCRD(t, rest, schemaGVK, schemaGVR) })
590 t.Run("PatchCRD", func(t *testing.T) { testFieldValidationPatchCRD(t, rest, schemaGVK, schemaGVR) })
591 t.Run("ApplyCreateCRD", func(t *testing.T) { testFieldValidationApplyCreateCRD(t, rest, schemaGVK, schemaGVR) })
592 t.Run("ApplyUpdateCRD", func(t *testing.T) { testFieldValidationApplyUpdateCRD(t, rest, schemaGVK, schemaGVR) })
593
594 t.Run("PostCRDSchemaless", func(t *testing.T) { testFieldValidationPostCRDSchemaless(t, rest, schemalessGVK, schemalessGVR) })
595 t.Run("PutCRDSchemaless", func(t *testing.T) { testFieldValidationPutCRDSchemaless(t, rest, schemalessGVK, schemalessGVR) })
596 t.Run("PatchCRDSchemaless", func(t *testing.T) { testFieldValidationPatchCRDSchemaless(t, rest, schemalessGVK, schemalessGVR) })
597 t.Run("ApplyCreateCRDSchemaless", func(t *testing.T) { testFieldValidationApplyCreateCRDSchemaless(t, rest, schemalessGVK, schemalessGVR) })
598 t.Run("ApplyUpdateCRDSchemaless", func(t *testing.T) { testFieldValidationApplyUpdateCRDSchemaless(t, rest, schemalessGVK, schemalessGVR) })
599 t.Run("testFinalizerValidationApplyCreateCRD", func(t *testing.T) {
600 testFinalizerValidationApplyCreateAndUpdateCRD(t, rest, schemalessGVK, schemalessGVR)
601 })
602 }
603
604
605
606 func testFieldValidationPost(t *testing.T, client clientset.Interface) {
607 var testcases = []struct {
608 name string
609 bodyBase string
610 opts metav1.CreateOptions
611 contentType string
612 strictDecodingError string
613 strictDecodingWarnings []string
614 }{
615 {
616 name: "post-strict-validation",
617 opts: metav1.CreateOptions{
618 FieldValidation: "Strict",
619 },
620 bodyBase: invalidBodyJSON,
621 strictDecodingError: `strict decoding error: duplicate field "metadata.name", unknown field "metadata.unknownMeta", unknown field "spec.unknown1", unknown field "spec.unknownDupe", duplicate field "spec.paused", unknown field "spec.template.spec.containers[0].unknownNested", duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
622 },
623 {
624 name: "post-warn-validation",
625 opts: metav1.CreateOptions{
626 FieldValidation: "Warn",
627 },
628 bodyBase: invalidBodyJSON,
629 strictDecodingWarnings: []string{
630 `duplicate field "metadata.name"`,
631 `unknown field "metadata.unknownMeta"`,
632 `unknown field "spec.unknown1"`,
633 `unknown field "spec.unknownDupe"`,
634 `duplicate field "spec.paused"`,
635
636
637
638 `unknown field "spec.template.spec.containers[0].unknownNested"`,
639 `duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
640 },
641 },
642 {
643 name: "post-ignore-validation",
644 opts: metav1.CreateOptions{
645 FieldValidation: "Ignore",
646 },
647 bodyBase: invalidBodyJSON,
648 },
649 {
650 name: "post-no-validation",
651 bodyBase: invalidBodyJSON,
652 strictDecodingWarnings: []string{
653 `duplicate field "metadata.name"`,
654 `unknown field "metadata.unknownMeta"`,
655 `unknown field "spec.unknown1"`,
656 `unknown field "spec.unknownDupe"`,
657 `duplicate field "spec.paused"`,
658
659
660
661 `unknown field "spec.template.spec.containers[0].unknownNested"`,
662 `duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
663 },
664 },
665 {
666 name: "post-strict-validation-yaml",
667 opts: metav1.CreateOptions{
668 FieldValidation: "Strict",
669 },
670 bodyBase: invalidBodyYAML,
671 contentType: "application/yaml",
672 strictDecodingError: `strict decoding error: yaml: unmarshal errors:
673 line 5: key "name" already set in map
674 line 12: key "unknownDupe" already set in map
675 line 14: key "paused" already set in map
676 line 28: key "imagePullPolicy" already set in map, unknown field "metadata.unknownMeta", unknown field "spec.template.spec.containers[0].unknownNested", unknown field "spec.unknown1", unknown field "spec.unknownDupe"`,
677 },
678 {
679 name: "post-warn-validation-yaml",
680 opts: metav1.CreateOptions{
681 FieldValidation: "Warn",
682 },
683 bodyBase: invalidBodyYAML,
684 contentType: "application/yaml",
685 strictDecodingWarnings: []string{
686 `line 5: key "name" already set in map`,
687 `line 12: key "unknownDupe" already set in map`,
688 `line 14: key "paused" already set in map`,
689 `line 28: key "imagePullPolicy" already set in map`,
690 `unknown field "metadata.unknownMeta"`,
691 `unknown field "spec.template.spec.containers[0].unknownNested"`,
692 `unknown field "spec.unknown1"`,
693 `unknown field "spec.unknownDupe"`,
694 },
695 },
696 {
697 name: "post-ignore-validation-yaml",
698 opts: metav1.CreateOptions{
699 FieldValidation: "Ignore",
700 },
701 bodyBase: invalidBodyYAML,
702 contentType: "application/yaml",
703 },
704 {
705 name: "post-no-validation-yaml",
706 bodyBase: invalidBodyYAML,
707 contentType: "application/yaml",
708 strictDecodingWarnings: []string{
709 `line 5: key "name" already set in map`,
710 `line 12: key "unknownDupe" already set in map`,
711 `line 14: key "paused" already set in map`,
712 `line 28: key "imagePullPolicy" already set in map`,
713 `unknown field "metadata.unknownMeta"`,
714 `unknown field "spec.template.spec.containers[0].unknownNested"`,
715 `unknown field "spec.unknown1"`,
716 `unknown field "spec.unknownDupe"`,
717 },
718 },
719 }
720
721 for _, tc := range testcases {
722 t.Run(tc.name, func(t *testing.T) {
723 klog.Warningf("running tc named: %s", tc.name)
724 body := []byte(fmt.Sprintf(tc.bodyBase, fmt.Sprintf("test-deployment-%s", tc.name)))
725 req := client.CoreV1().RESTClient().Post().
726 AbsPath("/apis/apps/v1").
727 Namespace("default").
728 Resource("deployments").
729 SetHeader("Content-Type", tc.contentType).
730 VersionedParams(&tc.opts, metav1.ParameterCodec)
731 result := req.Body(body).Do(context.TODO())
732 if result.Error() == nil && tc.strictDecodingError != "" {
733 t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
734 }
735 if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
736 t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
737 }
738
739 if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
740 t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
741 }
742 for i, strictWarn := range tc.strictDecodingWarnings {
743 if strictWarn != result.Warnings()[i].Text {
744 t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
745 }
746
747 }
748 })
749 }
750 }
751
752
753
754
755 func testFieldValidationPut(t *testing.T, client clientset.Interface) {
756 deployName := "test-deployment-put"
757 postBody := []byte(fmt.Sprintf(string(validBodyJSON), deployName))
758
759 if _, err := client.CoreV1().RESTClient().Post().
760 AbsPath("/apis/apps/v1").
761 Namespace("default").
762 Resource("deployments").
763 Body(postBody).
764 DoRaw(context.TODO()); err != nil {
765 t.Fatalf("failed to create initial deployment: %v", err)
766 }
767
768 var testcases = []struct {
769 name string
770 opts metav1.UpdateOptions
771 putBodyBase string
772 contentType string
773 strictDecodingError string
774 strictDecodingWarnings []string
775 }{
776 {
777 name: "put-strict-validation",
778 opts: metav1.UpdateOptions{
779 FieldValidation: "Strict",
780 },
781 putBodyBase: invalidBodyJSON,
782 strictDecodingError: `strict decoding error: duplicate field "metadata.name", unknown field "metadata.unknownMeta", unknown field "spec.unknown1", unknown field "spec.unknownDupe", duplicate field "spec.paused", unknown field "spec.template.spec.containers[0].unknownNested", duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
783 },
784 {
785 name: "put-warn-validation",
786 opts: metav1.UpdateOptions{
787 FieldValidation: "Warn",
788 },
789 putBodyBase: invalidBodyJSON,
790 strictDecodingWarnings: []string{
791 `duplicate field "metadata.name"`,
792 `unknown field "metadata.unknownMeta"`,
793 `unknown field "spec.unknown1"`,
794 `unknown field "spec.unknownDupe"`,
795 `duplicate field "spec.paused"`,
796
797
798
799 `unknown field "spec.template.spec.containers[0].unknownNested"`,
800 `duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
801 },
802 },
803 {
804 name: "put-ignore-validation",
805 opts: metav1.UpdateOptions{
806 FieldValidation: "Ignore",
807 },
808 putBodyBase: invalidBodyJSON,
809 },
810 {
811 name: "put-no-validation",
812 putBodyBase: invalidBodyJSON,
813 strictDecodingWarnings: []string{
814 `duplicate field "metadata.name"`,
815 `unknown field "metadata.unknownMeta"`,
816 `unknown field "spec.unknown1"`,
817 `unknown field "spec.unknownDupe"`,
818 `duplicate field "spec.paused"`,
819
820
821
822 `unknown field "spec.template.spec.containers[0].unknownNested"`,
823 `duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
824 },
825 },
826 {
827 name: "put-strict-validation-yaml",
828 opts: metav1.UpdateOptions{
829 FieldValidation: "Strict",
830 },
831 putBodyBase: invalidBodyYAML,
832 contentType: "application/yaml",
833 strictDecodingError: `strict decoding error: yaml: unmarshal errors:
834 line 5: key "name" already set in map
835 line 12: key "unknownDupe" already set in map
836 line 14: key "paused" already set in map
837 line 28: key "imagePullPolicy" already set in map, unknown field "metadata.unknownMeta", unknown field "spec.template.spec.containers[0].unknownNested", unknown field "spec.unknown1", unknown field "spec.unknownDupe"`,
838 },
839 {
840 name: "put-warn-validation-yaml",
841 opts: metav1.UpdateOptions{
842 FieldValidation: "Warn",
843 },
844 putBodyBase: invalidBodyYAML,
845 contentType: "application/yaml",
846 strictDecodingWarnings: []string{
847 `line 5: key "name" already set in map`,
848 `line 12: key "unknownDupe" already set in map`,
849 `line 14: key "paused" already set in map`,
850 `line 28: key "imagePullPolicy" already set in map`,
851 `unknown field "metadata.unknownMeta"`,
852 `unknown field "spec.template.spec.containers[0].unknownNested"`,
853 `unknown field "spec.unknown1"`,
854 `unknown field "spec.unknownDupe"`,
855 },
856 },
857 {
858 name: "put-ignore-validation-yaml",
859 opts: metav1.UpdateOptions{
860 FieldValidation: "Ignore",
861 },
862 putBodyBase: invalidBodyYAML,
863 contentType: "application/yaml",
864 },
865 {
866 name: "put-no-validation-yaml",
867 putBodyBase: invalidBodyYAML,
868 contentType: "application/yaml",
869 strictDecodingWarnings: []string{
870 `line 5: key "name" already set in map`,
871 `line 12: key "unknownDupe" already set in map`,
872 `line 14: key "paused" already set in map`,
873 `line 28: key "imagePullPolicy" already set in map`,
874 `unknown field "metadata.unknownMeta"`,
875 `unknown field "spec.template.spec.containers[0].unknownNested"`,
876 `unknown field "spec.unknown1"`,
877 `unknown field "spec.unknownDupe"`,
878 },
879 },
880 }
881
882 for _, tc := range testcases {
883 t.Run(tc.name, func(t *testing.T) {
884 putBody := []byte(fmt.Sprintf(string(tc.putBodyBase), deployName))
885 req := client.CoreV1().RESTClient().Put().
886 AbsPath("/apis/apps/v1").
887 Namespace("default").
888 Resource("deployments").
889 SetHeader("Content-Type", tc.contentType).
890 Name(deployName).
891 VersionedParams(&tc.opts, metav1.ParameterCodec)
892 result := req.Body([]byte(putBody)).Do(context.TODO())
893 if result.Error() == nil && tc.strictDecodingError != "" {
894 t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
895 }
896 if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
897 t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
898 }
899
900 if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
901 t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
902 }
903 for i, strictWarn := range tc.strictDecodingWarnings {
904 if strictWarn != result.Warnings()[i].Text {
905 t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
906 }
907
908 }
909 })
910 }
911 }
912
913
914
915 func testFieldValidationPatchTyped(t *testing.T, client clientset.Interface) {
916 deployName := "test-deployment-patch-typed"
917 postBody := []byte(fmt.Sprintf(string(validBodyJSON), deployName))
918
919 if _, err := client.CoreV1().RESTClient().Post().
920 AbsPath("/apis/apps/v1").
921 Namespace("default").
922 Resource("deployments").
923 Body(postBody).
924 DoRaw(context.TODO()); err != nil {
925 t.Fatalf("failed to create initial deployment: %v", err)
926 }
927
928 mergePatchBody := `
929 {
930 "spec": {
931 "unknown1": "val1",
932 "unknownDupe": "valDupe",
933 "unknownDupe": "valDupe2",
934 "paused": true,
935 "paused": false,
936 "template": {
937 "spec": {
938 "containers": [{
939 "name": "nginx",
940 "image": "nginx:latest",
941 "unknownNested": "val1",
942 "imagePullPolicy": "Always",
943 "imagePullPolicy": "Never"
944 }]
945 }
946 }
947 }
948 }
949 `
950 jsonPatchBody := `
951 [
952 {"op": "add", "path": "/spec/unknown1", "value": "val1", "foo":"bar"},
953 {"op": "add", "path": "/spec/unknown2", "path": "/spec/unknown3", "value": "val1"},
954 {"op": "add", "path": "/spec/unknownDupe", "value": "valDupe"},
955 {"op": "add", "path": "/spec/unknownDupe", "value": "valDupe2"},
956 {"op": "add", "path": "/spec/paused", "value": true},
957 {"op": "add", "path": "/spec/paused", "value": false},
958 {"op": "add", "path": "/spec/template/spec/containers/0/unknownNested", "value": "val1"},
959 {"op": "add", "path": "/spec/template/spec/containers/0/imagePullPolicy", "value": "Always"},
960 {"op": "add", "path": "/spec/template/spec/containers/0/imagePullPolicy", "value": "Never"}
961 ]
962 `
963
964
965 nonconflictingMergePatchBody := `
966 {
967 "spec": {
968 "paused": true,
969 "paused": false,
970 "template": {
971 "spec": {
972 "containers": [{
973 "name": "nginx",
974 "image": "nginx:latest",
975 "imagePullPolicy": "Always",
976 "imagePullPolicy": "Never"
977 }]
978 }
979 }
980 }
981 }
982 `
983 var testcases = []struct {
984 name string
985 opts metav1.PatchOptions
986 patchType types.PatchType
987 body string
988 strictDecodingError string
989 strictDecodingWarnings []string
990 }{
991 {
992 name: "merge-patch-strict-validation",
993 opts: metav1.PatchOptions{
994 FieldValidation: "Strict",
995 },
996 patchType: types.MergePatchType,
997 body: mergePatchBody,
998 strictDecodingError: `strict decoding error: duplicate field "spec.unknownDupe", duplicate field "spec.paused", duplicate field "spec.template.spec.containers[0].imagePullPolicy", unknown field "spec.template.spec.containers[0].unknownNested", unknown field "spec.unknown1", unknown field "spec.unknownDupe"`,
999 },
1000 {
1001 name: "merge-patch-warn-validation",
1002 opts: metav1.PatchOptions{
1003 FieldValidation: "Warn",
1004 },
1005 patchType: types.MergePatchType,
1006 body: mergePatchBody,
1007 strictDecodingWarnings: []string{
1008 `duplicate field "spec.unknownDupe"`,
1009 `duplicate field "spec.paused"`,
1010 `duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
1011 `unknown field "spec.template.spec.containers[0].unknownNested"`,
1012 `unknown field "spec.unknown1"`,
1013 `unknown field "spec.unknownDupe"`,
1014 },
1015 },
1016 {
1017 name: "merge-patch-ignore-validation",
1018 opts: metav1.PatchOptions{
1019 FieldValidation: "Ignore",
1020 },
1021 patchType: types.MergePatchType,
1022 body: mergePatchBody,
1023 },
1024 {
1025 name: "merge-patch-no-validation",
1026 patchType: types.MergePatchType,
1027 body: mergePatchBody,
1028 strictDecodingWarnings: []string{
1029 `duplicate field "spec.unknownDupe"`,
1030 `duplicate field "spec.paused"`,
1031 `duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
1032 `unknown field "spec.template.spec.containers[0].unknownNested"`,
1033 `unknown field "spec.unknown1"`,
1034 `unknown field "spec.unknownDupe"`,
1035 },
1036 },
1037 {
1038 name: "json-patch-strict-validation",
1039 patchType: types.JSONPatchType,
1040 opts: metav1.PatchOptions{
1041 FieldValidation: "Strict",
1042 },
1043 body: jsonPatchBody,
1044 strictDecodingError: `strict decoding error: json patch unknown field "[0].foo", json patch duplicate field "[1].path", unknown field "spec.template.spec.containers[0].unknownNested", unknown field "spec.unknown1", unknown field "spec.unknown3", unknown field "spec.unknownDupe"`,
1045 },
1046 {
1047 name: "json-patch-warn-validation",
1048 patchType: types.JSONPatchType,
1049 opts: metav1.PatchOptions{
1050 FieldValidation: "Warn",
1051 },
1052 body: jsonPatchBody,
1053 strictDecodingWarnings: []string{
1054
1055
1056
1057
1058
1059 `json patch unknown field "[0].foo"`,
1060 `json patch duplicate field "[1].path"`,
1061 `unknown field "spec.template.spec.containers[0].unknownNested"`,
1062 `unknown field "spec.unknown1"`,
1063 `unknown field "spec.unknown3"`,
1064 `unknown field "spec.unknownDupe"`,
1065 },
1066 },
1067 {
1068 name: "json-patch-ignore-validation",
1069 patchType: types.JSONPatchType,
1070 opts: metav1.PatchOptions{
1071 FieldValidation: "Ignore",
1072 },
1073 body: jsonPatchBody,
1074 },
1075 {
1076 name: "json-patch-no-validation",
1077 patchType: types.JSONPatchType,
1078 body: jsonPatchBody,
1079 strictDecodingWarnings: []string{
1080
1081
1082
1083
1084
1085 `json patch unknown field "[0].foo"`,
1086 `json patch duplicate field "[1].path"`,
1087 `unknown field "spec.template.spec.containers[0].unknownNested"`,
1088 `unknown field "spec.unknown1"`,
1089 `unknown field "spec.unknown3"`,
1090 `unknown field "spec.unknownDupe"`,
1091 },
1092 },
1093 {
1094 name: "nonconflicting-merge-patch-strict-validation",
1095 opts: metav1.PatchOptions{
1096 FieldValidation: "Strict",
1097 },
1098 patchType: types.MergePatchType,
1099 body: nonconflictingMergePatchBody,
1100 strictDecodingError: `strict decoding error: duplicate field "spec.paused", duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
1101 },
1102 {
1103 name: "nonconflicting-merge-patch-warn-validation",
1104 opts: metav1.PatchOptions{
1105 FieldValidation: "Warn",
1106 },
1107 patchType: types.MergePatchType,
1108 body: nonconflictingMergePatchBody,
1109 strictDecodingWarnings: []string{
1110 `duplicate field "spec.paused"`,
1111 `duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
1112 },
1113 },
1114 {
1115 name: "nonconflicting-merge-patch-ignore-validation",
1116 opts: metav1.PatchOptions{
1117 FieldValidation: "Ignore",
1118 },
1119 patchType: types.MergePatchType,
1120 body: nonconflictingMergePatchBody,
1121 },
1122 {
1123 name: "nonconflicting-merge-patch-no-validation",
1124 patchType: types.MergePatchType,
1125 body: nonconflictingMergePatchBody,
1126 strictDecodingWarnings: []string{
1127 `duplicate field "spec.paused"`,
1128 `duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
1129 },
1130 },
1131 }
1132
1133 for _, tc := range testcases {
1134 t.Run(tc.name, func(t *testing.T) {
1135 req := client.CoreV1().RESTClient().Patch(tc.patchType).
1136 AbsPath("/apis/apps/v1").
1137 Namespace("default").
1138 Resource("deployments").
1139 Name(deployName).
1140 VersionedParams(&tc.opts, metav1.ParameterCodec)
1141 result := req.Body([]byte(tc.body)).Do(context.TODO())
1142 if result.Error() == nil && tc.strictDecodingError != "" {
1143 t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
1144 }
1145 if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
1146 t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
1147 }
1148
1149 if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
1150 t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
1151 }
1152 for i, strictWarn := range tc.strictDecodingWarnings {
1153 if strictWarn != result.Warnings()[i].Text {
1154 t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
1155 }
1156
1157 }
1158 })
1159 }
1160 }
1161
1162
1163
1164
1165 func testFieldValidationSMP(t *testing.T, client clientset.Interface) {
1166
1167
1168 nonconflictingSMPBody := `
1169 {
1170 "spec": {
1171 "paused": true,
1172 "paused": false,
1173 "selector": {
1174 "matchLabels": {
1175 "app": "nginx"
1176 }
1177 },
1178 "template": {
1179 "metadata": {
1180 "labels": {
1181 "app": "nginx"
1182 }
1183 },
1184 "spec": {
1185 "containers": [{
1186 "name": "nginx",
1187 "imagePullPolicy": "Always",
1188 "imagePullPolicy": "Never"
1189 }]
1190 }
1191 }
1192 }
1193 }
1194 `
1195
1196 smpBody := `
1197 {
1198 "spec": {
1199 "unknown1": "val1",
1200 "unknownDupe": "valDupe",
1201 "unknownDupe": "valDupe2",
1202 "paused": true,
1203 "paused": false,
1204 "selector": {
1205 "matchLabels": {
1206 "app": "nginx"
1207 }
1208 },
1209 "template": {
1210 "metadata": {
1211 "labels": {
1212 "app": "nginx"
1213 }
1214 },
1215 "spec": {
1216 "containers": [{
1217 "name": "nginx",
1218 "unknownNested": "val1",
1219 "imagePullPolicy": "Always",
1220 "imagePullPolicy": "Never"
1221 }]
1222 }
1223 }
1224 }
1225 }
1226 `
1227 var testcases = []struct {
1228 name string
1229 opts metav1.PatchOptions
1230 body string
1231 strictDecodingError string
1232 strictDecodingWarnings []string
1233 }{
1234 {
1235 name: "smp-strict-validation",
1236 opts: metav1.PatchOptions{
1237 FieldValidation: "Strict",
1238 },
1239 body: smpBody,
1240 strictDecodingError: `strict decoding error: duplicate field "spec.unknownDupe", duplicate field "spec.paused", duplicate field "spec.template.spec.containers[0].imagePullPolicy", unknown field "spec.template.spec.containers[0].unknownNested", unknown field "spec.unknown1", unknown field "spec.unknownDupe"`,
1241 },
1242 {
1243 name: "smp-warn-validation",
1244 opts: metav1.PatchOptions{
1245 FieldValidation: "Warn",
1246 },
1247 body: smpBody,
1248 strictDecodingWarnings: []string{
1249 `duplicate field "spec.unknownDupe"`,
1250 `duplicate field "spec.paused"`,
1251 `duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
1252 `unknown field "spec.template.spec.containers[0].unknownNested"`,
1253 `unknown field "spec.unknown1"`,
1254 `unknown field "spec.unknownDupe"`,
1255 },
1256 },
1257 {
1258 name: "smp-ignore-validation",
1259 opts: metav1.PatchOptions{
1260 FieldValidation: "Ignore",
1261 },
1262 body: smpBody,
1263 },
1264 {
1265 name: "smp-no-validation",
1266 body: smpBody,
1267 strictDecodingWarnings: []string{
1268 `duplicate field "spec.unknownDupe"`,
1269 `duplicate field "spec.paused"`,
1270 `duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
1271 `unknown field "spec.template.spec.containers[0].unknownNested"`,
1272 `unknown field "spec.unknown1"`,
1273 `unknown field "spec.unknownDupe"`,
1274 },
1275 },
1276 {
1277 name: "nonconflicting-smp-strict-validation",
1278 opts: metav1.PatchOptions{
1279 FieldValidation: "Strict",
1280 },
1281 body: nonconflictingSMPBody,
1282 strictDecodingError: `strict decoding error: duplicate field "spec.paused", duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
1283 },
1284 {
1285 name: "nonconflicting-smp-warn-validation",
1286 opts: metav1.PatchOptions{
1287 FieldValidation: "Warn",
1288 },
1289 body: nonconflictingSMPBody,
1290 strictDecodingWarnings: []string{
1291 `duplicate field "spec.paused"`,
1292 `duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
1293 },
1294 },
1295 {
1296 name: "nonconflicting-smp-ignore-validation",
1297 opts: metav1.PatchOptions{
1298 FieldValidation: "Ignore",
1299 },
1300 body: nonconflictingSMPBody,
1301 },
1302 {
1303 name: "nonconflicting-smp-no-validation",
1304 body: nonconflictingSMPBody,
1305 strictDecodingWarnings: []string{
1306 `duplicate field "spec.paused"`,
1307 `duplicate field "spec.template.spec.containers[0].imagePullPolicy"`,
1308 },
1309 },
1310 }
1311
1312 for _, tc := range testcases {
1313 t.Run(tc.name, func(t *testing.T) {
1314 body := []byte(fmt.Sprintf(validBodyJSON, tc.name))
1315 _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
1316 AbsPath("/apis/apps/v1").
1317 Namespace("default").
1318 Resource("deployments").
1319 Name(tc.name).
1320 Param("fieldManager", "apply_test").
1321 Body(body).
1322 Do(context.TODO()).
1323 Get()
1324 if err != nil {
1325 t.Fatalf("Failed to create object using Apply patch: %v", err)
1326 }
1327
1328 req := client.CoreV1().RESTClient().Patch(types.StrategicMergePatchType).
1329 AbsPath("/apis/apps/v1").
1330 Namespace("default").
1331 Resource("deployments").
1332 Name(tc.name).
1333 VersionedParams(&tc.opts, metav1.ParameterCodec)
1334 result := req.Body([]byte(tc.body)).Do(context.TODO())
1335 if result.Error() == nil && tc.strictDecodingError != "" {
1336 t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
1337 }
1338 if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
1339 t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
1340 }
1341
1342 if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
1343 t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
1344 }
1345
1346 for i, strictWarn := range tc.strictDecodingWarnings {
1347 if strictWarn != result.Warnings()[i].Text {
1348 t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
1349 }
1350
1351 }
1352 })
1353 }
1354 }
1355
1356
1357
1358 func testFieldValidationApplyCreate(t *testing.T, client clientset.Interface) {
1359 var testcases = []struct {
1360 name string
1361 opts metav1.PatchOptions
1362 strictDecodingError string
1363 strictDecodingWarnings []string
1364 }{
1365 {
1366 name: "strict-validation",
1367 opts: metav1.PatchOptions{
1368 FieldValidation: "Strict",
1369 FieldManager: "mgr",
1370 },
1371 strictDecodingError: `error strict decoding YAML: error converting YAML to JSON: yaml: unmarshal errors:
1372 line 10: key "paused" already set in map
1373 line 27: key "imagePullPolicy" already set in map`,
1374 },
1375 {
1376 name: "warn-validation",
1377 opts: metav1.PatchOptions{
1378 FieldValidation: "Warn",
1379 FieldManager: "mgr",
1380 },
1381 strictDecodingWarnings: []string{
1382 `line 10: key "paused" already set in map`,
1383 `line 27: key "imagePullPolicy" already set in map`,
1384 },
1385 },
1386 {
1387 name: "ignore-validation",
1388 opts: metav1.PatchOptions{
1389 FieldValidation: "Ignore",
1390 FieldManager: "mgr",
1391 },
1392 },
1393 {
1394 name: "no-validation",
1395 opts: metav1.PatchOptions{
1396 FieldManager: "mgr",
1397 },
1398 strictDecodingWarnings: []string{
1399 `line 10: key "paused" already set in map`,
1400 `line 27: key "imagePullPolicy" already set in map`,
1401 },
1402 },
1403 }
1404
1405 for _, tc := range testcases {
1406 t.Run(tc.name, func(t *testing.T) {
1407 name := fmt.Sprintf("apply-create-deployment-%s", tc.name)
1408 body := []byte(fmt.Sprintf(applyInvalidBody, name))
1409 req := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
1410 AbsPath("/apis/apps/v1").
1411 Namespace("default").
1412 Resource("deployments").
1413 Name(name).
1414 VersionedParams(&tc.opts, metav1.ParameterCodec)
1415 result := req.Body(body).Do(context.TODO())
1416 if result.Error() == nil && tc.strictDecodingError != "" {
1417 t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
1418 }
1419 if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
1420 t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
1421 }
1422
1423 if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
1424 t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
1425 }
1426 for i, strictWarn := range tc.strictDecodingWarnings {
1427 if strictWarn != result.Warnings()[i].Text {
1428 t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
1429 }
1430
1431 }
1432 })
1433 }
1434 }
1435
1436
1437
1438 func testFieldValidationApplyUpdate(t *testing.T, client clientset.Interface) {
1439 var testcases = []struct {
1440 name string
1441 opts metav1.PatchOptions
1442 strictDecodingError string
1443 strictDecodingWarnings []string
1444 }{
1445 {
1446 name: "strict-validation",
1447 opts: metav1.PatchOptions{
1448 FieldValidation: "Strict",
1449 FieldManager: "mgr",
1450 },
1451 strictDecodingError: `error strict decoding YAML: error converting YAML to JSON: yaml: unmarshal errors:
1452 line 10: key "paused" already set in map
1453 line 27: key "imagePullPolicy" already set in map`,
1454 },
1455 {
1456 name: "warn-validation",
1457 opts: metav1.PatchOptions{
1458 FieldValidation: "Warn",
1459 FieldManager: "mgr",
1460 },
1461 strictDecodingWarnings: []string{
1462 `line 10: key "paused" already set in map`,
1463 `line 27: key "imagePullPolicy" already set in map`,
1464 },
1465 },
1466 {
1467 name: "ignore-validation",
1468 opts: metav1.PatchOptions{
1469 FieldValidation: "Ignore",
1470 FieldManager: "mgr",
1471 },
1472 },
1473 {
1474 name: "no-validation",
1475 opts: metav1.PatchOptions{
1476 FieldManager: "mgr",
1477 },
1478 strictDecodingWarnings: []string{
1479 `line 10: key "paused" already set in map`,
1480 `line 27: key "imagePullPolicy" already set in map`,
1481 },
1482 },
1483 }
1484
1485 for _, tc := range testcases {
1486 t.Run(tc.name, func(t *testing.T) {
1487 name := fmt.Sprintf("apply-update-deployment-%s", tc.name)
1488 createBody := []byte(fmt.Sprintf(validBodyJSON, name))
1489 createReq := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
1490 AbsPath("/apis/apps/v1").
1491 Namespace("default").
1492 Resource("deployments").
1493 Name(name).
1494 VersionedParams(&tc.opts, metav1.ParameterCodec)
1495 createResult := createReq.Body(createBody).Do(context.TODO())
1496 if createResult.Error() != nil {
1497 t.Fatalf("unexpected apply create err: %v", createResult.Error())
1498 }
1499
1500 updateBody := []byte(fmt.Sprintf(applyInvalidBody, name))
1501 updateReq := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
1502 AbsPath("/apis/apps/v1").
1503 Namespace("default").
1504 Resource("deployments").
1505 Name(name).
1506 VersionedParams(&tc.opts, metav1.ParameterCodec)
1507 result := updateReq.Body(updateBody).Do(context.TODO())
1508 if result.Error() == nil && tc.strictDecodingError != "" {
1509 t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
1510 }
1511 if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
1512 t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
1513 }
1514
1515 if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
1516 t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
1517 }
1518 for i, strictWarn := range tc.strictDecodingWarnings {
1519 if strictWarn != result.Warnings()[i].Text {
1520 t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
1521 }
1522
1523 }
1524 })
1525 }
1526 }
1527
1528
1529
1530 func testFieldValidationPostCRD(t *testing.T, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
1531 var testcases = []struct {
1532 name string
1533 opts metav1.PatchOptions
1534 body string
1535 contentType string
1536 strictDecodingError string
1537 strictDecodingWarnings []string
1538 }{
1539 {
1540 name: "crd-post-strict-validation",
1541 opts: metav1.PatchOptions{
1542 FieldValidation: "Strict",
1543 },
1544 body: crdInvalidBody,
1545 strictDecodingError: `strict decoding error: duplicate field "metadata.name", duplicate field "spec.unknownDupe", duplicate field "spec.knownField1", duplicate field "spec.ports[0].hostPort", unknown field "metadata.unknownMeta", unknown field "spec.ports[0].unknownNested", unknown field "spec.unknown1", unknown field "spec.unknownDupe", unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
1546 },
1547 {
1548 name: "crd-post-warn-validation",
1549 opts: metav1.PatchOptions{
1550 FieldValidation: "Warn",
1551 },
1552 body: crdInvalidBody,
1553 strictDecodingWarnings: []string{
1554 `duplicate field "metadata.name"`,
1555 `duplicate field "spec.unknownDupe"`,
1556 `duplicate field "spec.knownField1"`,
1557 `duplicate field "spec.ports[0].hostPort"`,
1558 `unknown field "metadata.unknownMeta"`,
1559 `unknown field "spec.ports[0].unknownNested"`,
1560 `unknown field "spec.unknown1"`,
1561 `unknown field "spec.unknownDupe"`,
1562 `unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
1563 },
1564 },
1565 {
1566 name: "crd-post-ignore-validation",
1567 opts: metav1.PatchOptions{
1568 FieldValidation: "Ignore",
1569 },
1570 body: crdInvalidBody,
1571 },
1572 {
1573 name: "crd-post-no-validation",
1574 body: crdInvalidBody,
1575 strictDecodingWarnings: []string{
1576 `duplicate field "metadata.name"`,
1577 `duplicate field "spec.unknownDupe"`,
1578 `duplicate field "spec.knownField1"`,
1579 `duplicate field "spec.ports[0].hostPort"`,
1580 `unknown field "metadata.unknownMeta"`,
1581 `unknown field "spec.ports[0].unknownNested"`,
1582 `unknown field "spec.unknown1"`,
1583 `unknown field "spec.unknownDupe"`,
1584 `unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
1585 },
1586 },
1587 {
1588 name: "crd-post-strict-validation-yaml",
1589 opts: metav1.PatchOptions{
1590 FieldValidation: "Strict",
1591 },
1592 body: crdInvalidBodyYAML,
1593 contentType: "application/yaml",
1594 strictDecodingError: `strict decoding error: yaml: unmarshal errors:
1595 line 6: key "name" already set in map
1596 line 12: key "unknownDupe" already set in map
1597 line 14: key "knownField1" already set in map
1598 line 20: key "hostPort" already set in map, unknown field "metadata.unknownMeta", unknown field "spec.ports[0].unknownNested", unknown field "spec.unknown1", unknown field "spec.unknownDupe", unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
1599 },
1600 {
1601 name: "crd-post-warn-validation-yaml",
1602 opts: metav1.PatchOptions{
1603 FieldValidation: "Warn",
1604 },
1605 body: crdInvalidBodyYAML,
1606 contentType: "application/yaml",
1607 strictDecodingWarnings: []string{
1608 `line 6: key "name" already set in map`,
1609 `line 12: key "unknownDupe" already set in map`,
1610 `line 14: key "knownField1" already set in map`,
1611 `line 20: key "hostPort" already set in map`,
1612 `unknown field "metadata.unknownMeta"`,
1613 `unknown field "spec.ports[0].unknownNested"`,
1614 `unknown field "spec.unknown1"`,
1615 `unknown field "spec.unknownDupe"`,
1616 `unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
1617 },
1618 },
1619 {
1620 name: "crd-post-ignore-validation-yaml",
1621 opts: metav1.PatchOptions{
1622 FieldValidation: "Ignore",
1623 },
1624 body: crdInvalidBodyYAML,
1625 contentType: "application/yaml",
1626 },
1627 {
1628 name: "crd-post-no-validation-yaml",
1629 body: crdInvalidBodyYAML,
1630 contentType: "application/yaml",
1631 strictDecodingWarnings: []string{
1632 `line 6: key "name" already set in map`,
1633 `line 12: key "unknownDupe" already set in map`,
1634 `line 14: key "knownField1" already set in map`,
1635 `line 20: key "hostPort" already set in map`,
1636 `unknown field "metadata.unknownMeta"`,
1637 `unknown field "spec.ports[0].unknownNested"`,
1638 `unknown field "spec.unknown1"`,
1639 `unknown field "spec.unknownDupe"`,
1640 `unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
1641 },
1642 },
1643 }
1644 for _, tc := range testcases {
1645 t.Run(tc.name, func(t *testing.T) {
1646 klog.Warningf("running tc named: %s", tc.name)
1647 kind := gvk.Kind
1648 apiVersion := gvk.Group + "/" + gvk.Version
1649
1650
1651 jsonBody := []byte(fmt.Sprintf(tc.body, apiVersion, kind, tc.name))
1652 req := rest.Post().
1653 AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
1654 SetHeader("Content-Type", tc.contentType).
1655 VersionedParams(&tc.opts, metav1.ParameterCodec)
1656 result := req.Body([]byte(jsonBody)).Do(context.TODO())
1657 if result.Error() == nil && tc.strictDecodingError != "" {
1658 t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
1659 }
1660 if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
1661 t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
1662 }
1663
1664 if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
1665 t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
1666 }
1667
1668 for i, strictWarn := range tc.strictDecodingWarnings {
1669 if strictWarn != result.Warnings()[i].Text {
1670 t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
1671 }
1672
1673 }
1674 })
1675 }
1676 }
1677
1678
1679
1680
1681 func testFieldValidationPostCRDSchemaless(t *testing.T, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
1682 var testcases = []struct {
1683 name string
1684 opts metav1.PatchOptions
1685 body string
1686 contentType string
1687 strictDecodingError string
1688 strictDecodingWarnings []string
1689 }{
1690 {
1691 name: "schemaless-crd-post-strict-validation",
1692 opts: metav1.PatchOptions{
1693 FieldValidation: "Strict",
1694 },
1695 body: crdInvalidBody,
1696 strictDecodingError: `strict decoding error: duplicate field "metadata.name", duplicate field "spec.unknownDupe", duplicate field "spec.knownField1", duplicate field "spec.ports[0].hostPort", unknown field "metadata.unknownMeta", unknown field "spec.ports[0].unknownNested", unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
1697 },
1698 {
1699 name: "schemaless-crd-post-warn-validation",
1700 opts: metav1.PatchOptions{
1701 FieldValidation: "Warn",
1702 },
1703 body: crdInvalidBody,
1704 strictDecodingWarnings: []string{
1705 `duplicate field "metadata.name"`,
1706 `duplicate field "spec.unknownDupe"`,
1707 `duplicate field "spec.knownField1"`,
1708 `duplicate field "spec.ports[0].hostPort"`,
1709 `unknown field "metadata.unknownMeta"`,
1710 `unknown field "spec.ports[0].unknownNested"`,
1711 `unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
1712 },
1713 },
1714 {
1715 name: "schemaless-crd-post-ignore-validation",
1716 opts: metav1.PatchOptions{
1717 FieldValidation: "Ignore",
1718 },
1719 body: crdInvalidBody,
1720 },
1721 {
1722 name: "schemaless-crd-post-no-validation",
1723 body: crdInvalidBody,
1724 strictDecodingWarnings: []string{
1725 `duplicate field "metadata.name"`,
1726 `duplicate field "spec.unknownDupe"`,
1727 `duplicate field "spec.knownField1"`,
1728 `duplicate field "spec.ports[0].hostPort"`,
1729 `unknown field "metadata.unknownMeta"`,
1730 `unknown field "spec.ports[0].unknownNested"`,
1731 `unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
1732 },
1733 },
1734 {
1735 name: "schemaless-crd-post-strict-validation-yaml",
1736 opts: metav1.PatchOptions{
1737 FieldValidation: "Strict",
1738 },
1739 body: crdInvalidBodyYAML,
1740 contentType: "application/yaml",
1741 strictDecodingError: `strict decoding error: yaml: unmarshal errors:
1742 line 6: key "name" already set in map
1743 line 12: key "unknownDupe" already set in map
1744 line 14: key "knownField1" already set in map
1745 line 20: key "hostPort" already set in map, unknown field "metadata.unknownMeta", unknown field "spec.ports[0].unknownNested", unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
1746 },
1747 {
1748 name: "schemaless-crd-post-warn-validation-yaml",
1749 opts: metav1.PatchOptions{
1750 FieldValidation: "Warn",
1751 },
1752 body: crdInvalidBodyYAML,
1753 contentType: "application/yaml",
1754 strictDecodingWarnings: []string{
1755 `line 6: key "name" already set in map`,
1756 `line 12: key "unknownDupe" already set in map`,
1757 `line 14: key "knownField1" already set in map`,
1758 `line 20: key "hostPort" already set in map`,
1759 `unknown field "metadata.unknownMeta"`,
1760 `unknown field "spec.ports[0].unknownNested"`,
1761 `unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
1762 },
1763 },
1764 {
1765 name: "schemaless-crd-post-ignore-validation-yaml",
1766 opts: metav1.PatchOptions{
1767 FieldValidation: "Ignore",
1768 },
1769 body: crdInvalidBodyYAML,
1770 contentType: "application/yaml",
1771 },
1772 {
1773 name: "schemaless-crd-post-no-validation-yaml",
1774 body: crdInvalidBodyYAML,
1775 contentType: "application/yaml",
1776 strictDecodingWarnings: []string{
1777 `line 6: key "name" already set in map`,
1778 `line 12: key "unknownDupe" already set in map`,
1779 `line 14: key "knownField1" already set in map`,
1780 `line 20: key "hostPort" already set in map`,
1781 `unknown field "metadata.unknownMeta"`,
1782 `unknown field "spec.ports[0].unknownNested"`,
1783 `unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
1784 },
1785 },
1786 }
1787 for _, tc := range testcases {
1788 t.Run(tc.name, func(t *testing.T) {
1789
1790 kind := gvk.Kind
1791 apiVersion := gvk.Group + "/" + gvk.Version
1792
1793
1794 jsonBody := []byte(fmt.Sprintf(tc.body, apiVersion, kind, tc.name))
1795 req := rest.Post().
1796 AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
1797 SetHeader("Content-Type", tc.contentType).
1798 VersionedParams(&tc.opts, metav1.ParameterCodec)
1799 result := req.Body([]byte(jsonBody)).Do(context.TODO())
1800 if result.Error() == nil && tc.strictDecodingError != "" {
1801 t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
1802 }
1803 if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
1804 t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
1805 }
1806
1807 if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
1808 t.Logf("expected:")
1809 for _, w := range tc.strictDecodingWarnings {
1810 t.Logf("\t%v", w)
1811 }
1812 t.Logf("got:")
1813 for _, w := range result.Warnings() {
1814 t.Logf("\t%v", w.Text)
1815 }
1816 t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
1817 }
1818
1819 for i, strictWarn := range tc.strictDecodingWarnings {
1820 if strictWarn != result.Warnings()[i].Text {
1821 t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
1822 }
1823
1824 }
1825 })
1826 }
1827 }
1828
1829
1830
1831 func testFieldValidationPutCRD(t *testing.T, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
1832 var testcases = []struct {
1833 name string
1834 opts metav1.PatchOptions
1835 putBody string
1836 contentType string
1837 strictDecodingError string
1838 strictDecodingWarnings []string
1839 }{
1840 {
1841 name: "crd-put-strict-validation",
1842 opts: metav1.PatchOptions{
1843 FieldValidation: "Strict",
1844 },
1845 putBody: crdInvalidBody,
1846 strictDecodingError: `strict decoding error: duplicate field "metadata.name", duplicate field "spec.unknownDupe", duplicate field "spec.knownField1", duplicate field "spec.ports[0].hostPort", unknown field "metadata.unknownMeta", unknown field "spec.ports[0].unknownNested", unknown field "spec.unknown1", unknown field "spec.unknownDupe", unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
1847 },
1848 {
1849 name: "crd-put-warn-validation",
1850 opts: metav1.PatchOptions{
1851 FieldValidation: "Warn",
1852 },
1853 putBody: crdInvalidBody,
1854 strictDecodingWarnings: []string{
1855 `duplicate field "metadata.name"`,
1856 `duplicate field "spec.unknownDupe"`,
1857 `duplicate field "spec.knownField1"`,
1858 `duplicate field "spec.ports[0].hostPort"`,
1859 `unknown field "metadata.unknownMeta"`,
1860 `unknown field "spec.ports[0].unknownNested"`,
1861 `unknown field "spec.unknown1"`,
1862 `unknown field "spec.unknownDupe"`,
1863 `unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
1864 },
1865 },
1866 {
1867 name: "crd-put-ignore-validation",
1868 opts: metav1.PatchOptions{
1869 FieldValidation: "Ignore",
1870 },
1871 putBody: crdInvalidBody,
1872 },
1873 {
1874 name: "crd-put-no-validation",
1875 putBody: crdInvalidBody,
1876 strictDecodingWarnings: []string{
1877 `duplicate field "metadata.name"`,
1878 `duplicate field "spec.unknownDupe"`,
1879 `duplicate field "spec.knownField1"`,
1880 `duplicate field "spec.ports[0].hostPort"`,
1881 `unknown field "metadata.unknownMeta"`,
1882 `unknown field "spec.ports[0].unknownNested"`,
1883 `unknown field "spec.unknown1"`,
1884 `unknown field "spec.unknownDupe"`,
1885 `unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
1886 },
1887 },
1888 {
1889 name: "crd-put-strict-validation-yaml",
1890 opts: metav1.PatchOptions{
1891 FieldValidation: "Strict",
1892 },
1893 putBody: crdInvalidBodyYAML,
1894 contentType: "application/yaml",
1895 strictDecodingError: `strict decoding error: yaml: unmarshal errors:
1896 line 6: key "name" already set in map
1897 line 12: key "unknownDupe" already set in map
1898 line 14: key "knownField1" already set in map
1899 line 20: key "hostPort" already set in map, unknown field "metadata.unknownMeta", unknown field "spec.ports[0].unknownNested", unknown field "spec.unknown1", unknown field "spec.unknownDupe", unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
1900 },
1901 {
1902 name: "crd-put-warn-validation-yaml",
1903 opts: metav1.PatchOptions{
1904 FieldValidation: "Warn",
1905 },
1906 putBody: crdInvalidBodyYAML,
1907 contentType: "application/yaml",
1908 strictDecodingWarnings: []string{
1909 `line 6: key "name" already set in map`,
1910 `line 12: key "unknownDupe" already set in map`,
1911 `line 14: key "knownField1" already set in map`,
1912 `line 20: key "hostPort" already set in map`,
1913 `unknown field "metadata.unknownMeta"`,
1914 `unknown field "spec.ports[0].unknownNested"`,
1915 `unknown field "spec.unknown1"`,
1916 `unknown field "spec.unknownDupe"`,
1917 `unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
1918 },
1919 },
1920 {
1921 name: "crd-put-ignore-validation-yaml",
1922 opts: metav1.PatchOptions{
1923 FieldValidation: "Ignore",
1924 },
1925 putBody: crdInvalidBodyYAML,
1926 contentType: "application/yaml",
1927 },
1928 {
1929 name: "crd-put-no-validation-yaml",
1930 putBody: crdInvalidBodyYAML,
1931 contentType: "application/yaml",
1932 strictDecodingWarnings: []string{
1933 `line 6: key "name" already set in map`,
1934 `line 12: key "unknownDupe" already set in map`,
1935 `line 14: key "knownField1" already set in map`,
1936 `line 20: key "hostPort" already set in map`,
1937 `unknown field "metadata.unknownMeta"`,
1938 `unknown field "spec.ports[0].unknownNested"`,
1939 `unknown field "spec.unknown1"`,
1940 `unknown field "spec.unknownDupe"`,
1941 `unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
1942 },
1943 },
1944 }
1945 for _, tc := range testcases {
1946 t.Run(tc.name, func(t *testing.T) {
1947 kind := gvk.Kind
1948 apiVersion := gvk.Group + "/" + gvk.Version
1949
1950
1951 jsonPostBody := []byte(fmt.Sprintf(crdValidBody, apiVersion, kind, tc.name))
1952 postReq := rest.Post().
1953 AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
1954 VersionedParams(&tc.opts, metav1.ParameterCodec)
1955 postResult, err := postReq.Body([]byte(jsonPostBody)).Do(context.TODO()).Raw()
1956 if err != nil {
1957 t.Fatalf("unexpeted error on CR creation: %v", err)
1958 }
1959 postUnstructured := &unstructured.Unstructured{}
1960 if err := postUnstructured.UnmarshalJSON(postResult); err != nil {
1961 t.Fatalf("unexpeted error unmarshalling created CR: %v", err)
1962 }
1963
1964
1965 putBody := []byte(fmt.Sprintf(tc.putBody, apiVersion, kind, tc.name, postUnstructured.GetResourceVersion()))
1966 putReq := rest.Put().
1967 AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
1968 Name(tc.name).
1969 SetHeader("Content-Type", tc.contentType).
1970 VersionedParams(&tc.opts, metav1.ParameterCodec)
1971 result := putReq.Body([]byte(putBody)).Do(context.TODO())
1972 if result.Error() == nil && tc.strictDecodingError != "" {
1973 t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
1974 }
1975 if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
1976 t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
1977 }
1978
1979 if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
1980 t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
1981 }
1982
1983 for i, strictWarn := range tc.strictDecodingWarnings {
1984 if strictWarn != result.Warnings()[i].Text {
1985 t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
1986 }
1987
1988 }
1989 })
1990 }
1991 }
1992
1993
1994
1995
1996 func testFieldValidationPutCRDSchemaless(t *testing.T, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
1997 var testcases = []struct {
1998 name string
1999 opts metav1.PatchOptions
2000 putBody string
2001 contentType string
2002 strictDecodingError string
2003 strictDecodingWarnings []string
2004 }{
2005 {
2006 name: "schemaless-crd-put-strict-validation",
2007 opts: metav1.PatchOptions{
2008 FieldValidation: "Strict",
2009 },
2010 putBody: crdInvalidBody,
2011 strictDecodingError: `strict decoding error: duplicate field "metadata.name", duplicate field "spec.unknownDupe", duplicate field "spec.knownField1", duplicate field "spec.ports[0].hostPort", unknown field "metadata.unknownMeta", unknown field "spec.ports[0].unknownNested", unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
2012 },
2013 {
2014 name: "schemaless-crd-put-warn-validation",
2015 opts: metav1.PatchOptions{
2016 FieldValidation: "Warn",
2017 },
2018 putBody: crdInvalidBody,
2019 strictDecodingWarnings: []string{
2020 `duplicate field "metadata.name"`,
2021 `duplicate field "spec.unknownDupe"`,
2022 `duplicate field "spec.knownField1"`,
2023 `duplicate field "spec.ports[0].hostPort"`,
2024 `unknown field "metadata.unknownMeta"`,
2025 `unknown field "spec.ports[0].unknownNested"`,
2026 `unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
2027 },
2028 },
2029 {
2030 name: "schemaless-crd-put-ignore-validation",
2031 opts: metav1.PatchOptions{
2032 FieldValidation: "Ignore",
2033 },
2034 putBody: crdInvalidBody,
2035 },
2036 {
2037 name: "schemaless-crd-put-no-validation",
2038 putBody: crdInvalidBody,
2039 strictDecodingWarnings: []string{
2040 `duplicate field "metadata.name"`,
2041 `duplicate field "spec.unknownDupe"`,
2042 `duplicate field "spec.knownField1"`,
2043 `duplicate field "spec.ports[0].hostPort"`,
2044 `unknown field "metadata.unknownMeta"`,
2045 `unknown field "spec.ports[0].unknownNested"`,
2046 `unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
2047 },
2048 },
2049 {
2050 name: "schemaless-crd-put-strict-validation-yaml",
2051 opts: metav1.PatchOptions{
2052 FieldValidation: "Strict",
2053 },
2054 putBody: crdInvalidBodyYAML,
2055 contentType: "application/yaml",
2056 strictDecodingError: `strict decoding error: yaml: unmarshal errors:
2057 line 6: key "name" already set in map
2058 line 12: key "unknownDupe" already set in map
2059 line 14: key "knownField1" already set in map
2060 line 20: key "hostPort" already set in map, unknown field "metadata.unknownMeta", unknown field "spec.ports[0].unknownNested", unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
2061 },
2062 {
2063 name: "schemaless-crd-put-warn-validation-yaml",
2064 opts: metav1.PatchOptions{
2065 FieldValidation: "Warn",
2066 },
2067 putBody: crdInvalidBodyYAML,
2068 contentType: "application/yaml",
2069 strictDecodingWarnings: []string{
2070 `line 6: key "name" already set in map`,
2071 `line 12: key "unknownDupe" already set in map`,
2072 `line 14: key "knownField1" already set in map`,
2073 `line 20: key "hostPort" already set in map`,
2074 `unknown field "metadata.unknownMeta"`,
2075 `unknown field "spec.ports[0].unknownNested"`,
2076 `unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
2077 },
2078 },
2079 {
2080 name: "schemaless-crd-put-ignore-validation-yaml",
2081 opts: metav1.PatchOptions{
2082 FieldValidation: "Ignore",
2083 },
2084 putBody: crdInvalidBodyYAML,
2085 contentType: "application/yaml",
2086 },
2087 {
2088 name: "schemaless-crd-put-no-validation-yaml",
2089 putBody: crdInvalidBodyYAML,
2090 contentType: "application/yaml",
2091 strictDecodingWarnings: []string{
2092 `line 6: key "name" already set in map`,
2093 `line 12: key "unknownDupe" already set in map`,
2094 `line 14: key "knownField1" already set in map`,
2095 `line 20: key "hostPort" already set in map`,
2096 `unknown field "metadata.unknownMeta"`,
2097 `unknown field "spec.ports[0].unknownNested"`,
2098 `unknown field "spec.embeddedObj.metadata.unknownEmbeddedMeta"`,
2099 },
2100 },
2101 }
2102 for _, tc := range testcases {
2103 t.Run(tc.name, func(t *testing.T) {
2104 kind := gvk.Kind
2105 apiVersion := gvk.Group + "/" + gvk.Version
2106
2107
2108 jsonPostBody := []byte(fmt.Sprintf(crdValidBody, apiVersion, kind, tc.name))
2109 postReq := rest.Post().
2110 AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
2111 VersionedParams(&tc.opts, metav1.ParameterCodec)
2112 postResult, err := postReq.Body([]byte(jsonPostBody)).Do(context.TODO()).Raw()
2113 if err != nil {
2114 t.Fatalf("unexpeted error on CR creation: %v", err)
2115 }
2116 postUnstructured := &unstructured.Unstructured{}
2117 if err := postUnstructured.UnmarshalJSON(postResult); err != nil {
2118 t.Fatalf("unexpeted error unmarshalling created CR: %v", err)
2119 }
2120
2121
2122 putBody := []byte(fmt.Sprintf(tc.putBody, apiVersion, kind, tc.name, postUnstructured.GetResourceVersion()))
2123 putReq := rest.Put().
2124 AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
2125 Name(tc.name).
2126 SetHeader("Content-Type", tc.contentType).
2127 VersionedParams(&tc.opts, metav1.ParameterCodec)
2128 result := putReq.Body([]byte(putBody)).Do(context.TODO())
2129 if result.Error() == nil && tc.strictDecodingError != "" {
2130 t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
2131 }
2132 if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
2133 t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
2134 }
2135
2136 if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
2137 t.Logf("expected:")
2138 for _, w := range tc.strictDecodingWarnings {
2139 t.Logf("\t%v", w)
2140 }
2141 t.Logf("got:")
2142 for _, w := range result.Warnings() {
2143 t.Logf("\t%v", w.Text)
2144 }
2145 t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
2146 }
2147
2148 for i, strictWarn := range tc.strictDecodingWarnings {
2149 if strictWarn != result.Warnings()[i].Text {
2150 t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
2151 }
2152
2153 }
2154 })
2155 }
2156 }
2157
2158
2159
2160
2161 func testFieldValidationPatchCRD(t *testing.T, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
2162 patchYAMLBody := `
2163 apiVersion: %s
2164 kind: %s
2165 metadata:
2166 name: %s
2167 finalizers:
2168 - test/finalizer
2169 spec:
2170 cronSpec: "* * * * */5"
2171 ports:
2172 - name: x
2173 containerPort: 80
2174 protocol: TCP`
2175
2176 mergePatchBody := `
2177 {
2178 "spec": {
2179 "unknown1": "val1",
2180 "unknownDupe": "valDupe",
2181 "unknownDupe": "valDupe2",
2182 "knownField1": "val1",
2183 "knownField1": "val2",
2184 "ports": [{
2185 "name": "portName",
2186 "containerPort": 8080,
2187 "protocol": "TCP",
2188 "hostPort": 8081,
2189 "hostPort": 8082,
2190 "unknownNested": "val"
2191 }]
2192 }
2193 }
2194 `
2195 jsonPatchBody := `
2196 [
2197 {"op": "add", "path": "/spec/unknown1", "value": "val1", "foo": "bar"},
2198 {"op": "add", "path": "/spec/unknown2", "path": "/spec/unknown3", "value": "val2"},
2199 {"op": "add", "path": "/spec/unknownDupe", "value": "valDupe"},
2200 {"op": "add", "path": "/spec/unknownDupe", "value": "valDupe2"},
2201 {"op": "add", "path": "/spec/knownField1", "value": "val1"},
2202 {"op": "add", "path": "/spec/knownField1", "value": "val2"},
2203 {"op": "add", "path": "/spec/ports/0/name", "value": "portName"},
2204 {"op": "add", "path": "/spec/ports/0/containerPort", "value": 8080},
2205 {"op": "add", "path": "/spec/ports/0/protocol", "value": "TCP"},
2206 {"op": "add", "path": "/spec/ports/0/hostPort", "value": 8081},
2207 {"op": "add", "path": "/spec/ports/0/hostPort", "value": 8082},
2208 {"op": "add", "path": "/spec/ports/0/unknownNested", "value": "val"}
2209 ]
2210 `
2211 var testcases = []struct {
2212 name string
2213 patchType types.PatchType
2214 opts metav1.PatchOptions
2215 body string
2216 strictDecodingError string
2217 strictDecodingWarnings []string
2218 }{
2219 {
2220 name: "crd-merge-patch-strict-validation",
2221 patchType: types.MergePatchType,
2222 opts: metav1.PatchOptions{
2223 FieldValidation: "Strict",
2224 },
2225 body: mergePatchBody,
2226 strictDecodingError: `strict decoding error: duplicate field "spec.unknownDupe", duplicate field "spec.knownField1", duplicate field "spec.ports[0].hostPort", unknown field "spec.ports[0].unknownNested", unknown field "spec.unknown1", unknown field "spec.unknownDupe"`,
2227 },
2228 {
2229 name: "crd-merge-patch-warn-validation",
2230 patchType: types.MergePatchType,
2231 opts: metav1.PatchOptions{
2232 FieldValidation: "Warn",
2233 },
2234 body: mergePatchBody,
2235 strictDecodingWarnings: []string{
2236 `duplicate field "spec.unknownDupe"`,
2237 `duplicate field "spec.knownField1"`,
2238 `duplicate field "spec.ports[0].hostPort"`,
2239 `unknown field "spec.ports[0].unknownNested"`,
2240 `unknown field "spec.unknown1"`,
2241 `unknown field "spec.unknownDupe"`,
2242 },
2243 },
2244 {
2245 name: "crd-merge-patch-ignore-validation",
2246 patchType: types.MergePatchType,
2247 opts: metav1.PatchOptions{
2248 FieldValidation: "Ignore",
2249 },
2250 body: mergePatchBody,
2251 },
2252 {
2253 name: "crd-merge-patch-no-validation",
2254 patchType: types.MergePatchType,
2255 body: mergePatchBody,
2256 strictDecodingWarnings: []string{
2257 `duplicate field "spec.unknownDupe"`,
2258 `duplicate field "spec.knownField1"`,
2259 `duplicate field "spec.ports[0].hostPort"`,
2260 `unknown field "spec.ports[0].unknownNested"`,
2261 `unknown field "spec.unknown1"`,
2262 `unknown field "spec.unknownDupe"`,
2263 },
2264 },
2265 {
2266 name: "crd-json-patch-strict-validation",
2267 patchType: types.JSONPatchType,
2268 opts: metav1.PatchOptions{
2269 FieldValidation: "Strict",
2270 },
2271 body: jsonPatchBody,
2272
2273
2274
2275
2276
2277 strictDecodingError: `strict decoding error: json patch unknown field "[0].foo", json patch duplicate field "[1].path", unknown field "spec.ports[0].unknownNested", unknown field "spec.unknown1", unknown field "spec.unknown3", unknown field "spec.unknownDupe"`,
2278 },
2279 {
2280 name: "crd-json-patch-warn-validation",
2281 patchType: types.JSONPatchType,
2282 opts: metav1.PatchOptions{
2283 FieldValidation: "Warn",
2284 },
2285 body: jsonPatchBody,
2286 strictDecodingWarnings: []string{
2287
2288
2289
2290
2291
2292 `json patch unknown field "[0].foo"`,
2293 `json patch duplicate field "[1].path"`,
2294 `unknown field "spec.ports[0].unknownNested"`,
2295 `unknown field "spec.unknown1"`,
2296 `unknown field "spec.unknown3"`,
2297 `unknown field "spec.unknownDupe"`,
2298 },
2299 },
2300 {
2301 name: "crd-json-patch-ignore-validation",
2302 patchType: types.JSONPatchType,
2303 opts: metav1.PatchOptions{
2304 FieldValidation: "Ignore",
2305 },
2306 body: jsonPatchBody,
2307 },
2308 {
2309 name: "crd-json-patch-no-validation",
2310 patchType: types.JSONPatchType,
2311 body: jsonPatchBody,
2312 strictDecodingWarnings: []string{
2313
2314
2315
2316
2317
2318 `json patch unknown field "[0].foo"`,
2319 `json patch duplicate field "[1].path"`,
2320 `unknown field "spec.ports[0].unknownNested"`,
2321 `unknown field "spec.unknown1"`,
2322 `unknown field "spec.unknown3"`,
2323 `unknown field "spec.unknownDupe"`,
2324 },
2325 },
2326 }
2327 for _, tc := range testcases {
2328 t.Run(tc.name, func(t *testing.T) {
2329 kind := gvk.Kind
2330 apiVersion := gvk.Group + "/" + gvk.Version
2331
2332 yamlBody := []byte(fmt.Sprintf(string(patchYAMLBody), apiVersion, kind, tc.name))
2333 createResult, err := rest.Patch(types.ApplyPatchType).
2334 AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
2335 Name(tc.name).
2336 Param("fieldManager", "apply_test").
2337 Body(yamlBody).
2338 DoRaw(context.TODO())
2339 if err != nil {
2340 t.Fatalf("failed to create custom resource with apply: %v:\n%v", err, string(createResult))
2341 }
2342
2343
2344 req := rest.Patch(tc.patchType).
2345 AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
2346 Name(tc.name).
2347 VersionedParams(&tc.opts, metav1.ParameterCodec)
2348 result := req.Body([]byte(tc.body)).Do(context.TODO())
2349 if result.Error() == nil && tc.strictDecodingError != "" {
2350 t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
2351 }
2352 if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
2353 t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
2354 }
2355
2356 if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
2357 t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
2358 }
2359
2360 for i, strictWarn := range tc.strictDecodingWarnings {
2361 if strictWarn != result.Warnings()[i].Text {
2362 t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
2363 }
2364
2365 }
2366 })
2367 }
2368 }
2369
2370
2371
2372
2373
2374 func testFieldValidationPatchCRDSchemaless(t *testing.T, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
2375 mergePatchBody := `
2376 {
2377 "spec": {
2378 "unknown1": "val1",
2379 "unknownDupe": "valDupe",
2380 "unknownDupe": "valDupe2",
2381 "knownField1": "val1",
2382 "knownField1": "val2",
2383 "ports": [{
2384 "name": "portName",
2385 "containerPort": 8080,
2386 "protocol": "TCP",
2387 "hostPort": 8081,
2388 "hostPort": 8082,
2389 "unknownNested": "val"
2390 }]
2391 }
2392 }
2393 `
2394 jsonPatchBody := `
2395 [
2396 {"op": "add", "path": "/spec/unknown1", "value": "val1", "foo": "bar"},
2397 {"op": "add", "path": "/spec/unknown2", "path": "/spec/unknown3", "value": "val2"},
2398 {"op": "add", "path": "/spec/unknownDupe", "value": "valDupe"},
2399 {"op": "add", "path": "/spec/unknownDupe", "value": "valDupe2"},
2400 {"op": "add", "path": "/spec/knownField1", "value": "val1"},
2401 {"op": "add", "path": "/spec/knownField1", "value": "val2"},
2402 {"op": "add", "path": "/spec/ports/0/name", "value": "portName"},
2403 {"op": "add", "path": "/spec/ports/0/containerPort", "value": 8080},
2404 {"op": "add", "path": "/spec/ports/0/protocol", "value": "TCP"},
2405 {"op": "add", "path": "/spec/ports/0/hostPort", "value": 8081},
2406 {"op": "add", "path": "/spec/ports/0/hostPort", "value": 8082},
2407 {"op": "add", "path": "/spec/ports/0/unknownNested", "value": "val"}
2408 ]
2409 `
2410 var testcases = []struct {
2411 name string
2412 patchType types.PatchType
2413 opts metav1.PatchOptions
2414 body string
2415 strictDecodingError string
2416 strictDecodingWarnings []string
2417 }{
2418 {
2419 name: "schemaless-crd-merge-patch-strict-validation",
2420 patchType: types.MergePatchType,
2421 opts: metav1.PatchOptions{
2422 FieldValidation: "Strict",
2423 },
2424 body: mergePatchBody,
2425 strictDecodingError: `strict decoding error: duplicate field "spec.unknownDupe", duplicate field "spec.knownField1", duplicate field "spec.ports[0].hostPort", unknown field "spec.ports[0].unknownNested"`,
2426 },
2427 {
2428 name: "schemaless-crd-merge-patch-warn-validation",
2429 patchType: types.MergePatchType,
2430 opts: metav1.PatchOptions{
2431 FieldValidation: "Warn",
2432 },
2433 body: mergePatchBody,
2434 strictDecodingWarnings: []string{
2435 `duplicate field "spec.unknownDupe"`,
2436 `duplicate field "spec.knownField1"`,
2437 `duplicate field "spec.ports[0].hostPort"`,
2438 `unknown field "spec.ports[0].unknownNested"`,
2439 },
2440 },
2441 {
2442 name: "schemaless-crd-merge-patch-ignore-validation",
2443 patchType: types.MergePatchType,
2444 opts: metav1.PatchOptions{
2445 FieldValidation: "Ignore",
2446 },
2447 body: mergePatchBody,
2448 },
2449 {
2450 name: "schemaless-crd-merge-patch-no-validation",
2451 patchType: types.MergePatchType,
2452 body: mergePatchBody,
2453 strictDecodingWarnings: []string{
2454 `duplicate field "spec.unknownDupe"`,
2455 `duplicate field "spec.knownField1"`,
2456 `duplicate field "spec.ports[0].hostPort"`,
2457 `unknown field "spec.ports[0].unknownNested"`,
2458 },
2459 },
2460 {
2461 name: "schemaless-crd-json-patch-strict-validation",
2462 patchType: types.JSONPatchType,
2463 opts: metav1.PatchOptions{
2464 FieldValidation: "Strict",
2465 },
2466 body: jsonPatchBody,
2467
2468
2469
2470
2471
2472 strictDecodingError: `strict decoding error: json patch unknown field "[0].foo", json patch duplicate field "[1].path", unknown field "spec.ports[0].unknownNested"`,
2473 },
2474 {
2475 name: "schemaless-crd-json-patch-warn-validation",
2476 patchType: types.JSONPatchType,
2477 opts: metav1.PatchOptions{
2478 FieldValidation: "Warn",
2479 },
2480 body: jsonPatchBody,
2481 strictDecodingWarnings: []string{
2482
2483
2484
2485
2486
2487 `json patch unknown field "[0].foo"`,
2488 `json patch duplicate field "[1].path"`,
2489 `unknown field "spec.ports[0].unknownNested"`,
2490 },
2491 },
2492 {
2493 name: "schemaless-crd-json-patch-ignore-validation",
2494 patchType: types.JSONPatchType,
2495 opts: metav1.PatchOptions{
2496 FieldValidation: "Ignore",
2497 },
2498 body: jsonPatchBody,
2499 },
2500 {
2501 name: "schemaless-crd-json-patch-no-validation",
2502 patchType: types.JSONPatchType,
2503 body: jsonPatchBody,
2504 strictDecodingWarnings: []string{
2505
2506
2507
2508
2509
2510 `json patch unknown field "[0].foo"`,
2511 `json patch duplicate field "[1].path"`,
2512 `unknown field "spec.ports[0].unknownNested"`,
2513 },
2514 },
2515 }
2516 for _, tc := range testcases {
2517 t.Run(tc.name, func(t *testing.T) {
2518 kind := gvk.Kind
2519 apiVersion := gvk.Group + "/" + gvk.Version
2520
2521 yamlBody := []byte(fmt.Sprintf(string(patchYAMLBody), apiVersion, kind, tc.name))
2522 createResult, err := rest.Patch(types.ApplyPatchType).
2523 AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
2524 Name(tc.name).
2525 Param("fieldManager", "apply_test").
2526 Body(yamlBody).
2527 DoRaw(context.TODO())
2528 if err != nil {
2529 t.Fatalf("failed to create custom resource with apply: %v:\n%v", err, string(createResult))
2530 }
2531
2532
2533 req := rest.Patch(tc.patchType).
2534 AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
2535 Name(tc.name).
2536 VersionedParams(&tc.opts, metav1.ParameterCodec)
2537 result := req.Body([]byte(tc.body)).Do(context.TODO())
2538 if result.Error() == nil && tc.strictDecodingError != "" {
2539 t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
2540 }
2541 if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
2542 t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
2543 }
2544
2545 if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
2546 t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
2547 }
2548
2549 for i, strictWarn := range tc.strictDecodingWarnings {
2550 if strictWarn != result.Warnings()[i].Text {
2551 t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
2552 }
2553
2554 }
2555 })
2556 }
2557 }
2558
2559
2560
2561
2562
2563 func testFieldValidationApplyCreateCRD(t *testing.T, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
2564 var testcases = []struct {
2565 name string
2566 opts metav1.PatchOptions
2567 strictDecodingError string
2568 strictDecodingWarnings []string
2569 }{
2570 {
2571 name: "strict-validation",
2572 opts: metav1.PatchOptions{
2573 FieldValidation: "Strict",
2574 FieldManager: "mgr",
2575 },
2576 strictDecodingError: `error strict decoding YAML: error converting YAML to JSON: yaml: unmarshal errors:
2577 line 10: key "knownField1" already set in map
2578 line 16: key "hostPort" already set in map`,
2579 },
2580 {
2581 name: "warn-validation",
2582 opts: metav1.PatchOptions{
2583 FieldValidation: "Warn",
2584 FieldManager: "mgr",
2585 },
2586 strictDecodingWarnings: []string{
2587 `line 10: key "knownField1" already set in map`,
2588 `line 16: key "hostPort" already set in map`,
2589 },
2590 },
2591 {
2592 name: "ignore-validation",
2593 opts: metav1.PatchOptions{
2594 FieldValidation: "Ignore",
2595 FieldManager: "mgr",
2596 },
2597 },
2598 {
2599 name: "no-validation",
2600 opts: metav1.PatchOptions{
2601 FieldManager: "mgr",
2602 },
2603 strictDecodingWarnings: []string{
2604 `line 10: key "knownField1" already set in map`,
2605 `line 16: key "hostPort" already set in map`,
2606 },
2607 },
2608 }
2609
2610 for _, tc := range testcases {
2611 t.Run(tc.name, func(t *testing.T) {
2612 kind := gvk.Kind
2613 apiVersion := gvk.Group + "/" + gvk.Version
2614
2615
2616 name := fmt.Sprintf("apply-create-crd-%s", tc.name)
2617 applyCreateBody := []byte(fmt.Sprintf(crdApplyInvalidBody, apiVersion, kind, name))
2618
2619 req := rest.Patch(types.ApplyPatchType).
2620 AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
2621 Name(name).
2622 VersionedParams(&tc.opts, metav1.ParameterCodec)
2623 result := req.Body(applyCreateBody).Do(context.TODO())
2624 if result.Error() == nil && tc.strictDecodingError != "" {
2625 t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
2626 }
2627 if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
2628 t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
2629 }
2630
2631 if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
2632 t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
2633 }
2634 for i, strictWarn := range tc.strictDecodingWarnings {
2635 if strictWarn != result.Warnings()[i].Text {
2636 t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
2637 }
2638
2639 }
2640 })
2641 }
2642 }
2643
2644
2645
2646
2647
2648
2649 func testFieldValidationApplyCreateCRDSchemaless(t *testing.T, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
2650 var testcases = []struct {
2651 name string
2652 opts metav1.PatchOptions
2653 strictDecodingError string
2654 strictDecodingWarnings []string
2655 }{
2656 {
2657 name: "schemaless-strict-validation",
2658 opts: metav1.PatchOptions{
2659 FieldValidation: "Strict",
2660 FieldManager: "mgr",
2661 },
2662 strictDecodingError: `error strict decoding YAML: error converting YAML to JSON: yaml: unmarshal errors:
2663 line 10: key "knownField1" already set in map
2664 line 16: key "hostPort" already set in map`,
2665 },
2666 {
2667 name: "schemaless-warn-validation",
2668 opts: metav1.PatchOptions{
2669 FieldValidation: "Warn",
2670 FieldManager: "mgr",
2671 },
2672 strictDecodingWarnings: []string{
2673 `line 10: key "knownField1" already set in map`,
2674 `line 16: key "hostPort" already set in map`,
2675 },
2676 },
2677 {
2678 name: "schemaless-ignore-validation",
2679 opts: metav1.PatchOptions{
2680 FieldValidation: "Ignore",
2681 FieldManager: "mgr",
2682 },
2683 },
2684 {
2685 name: "schemaless-no-validation",
2686 opts: metav1.PatchOptions{
2687 FieldManager: "mgr",
2688 },
2689 strictDecodingWarnings: []string{
2690 `line 10: key "knownField1" already set in map`,
2691 `line 16: key "hostPort" already set in map`,
2692 },
2693 },
2694 }
2695
2696 for _, tc := range testcases {
2697 t.Run(tc.name, func(t *testing.T) {
2698 kind := gvk.Kind
2699 apiVersion := gvk.Group + "/" + gvk.Version
2700
2701
2702 name := fmt.Sprintf("apply-create-crd-schemaless-%s", tc.name)
2703 applyCreateBody := []byte(fmt.Sprintf(crdApplyInvalidBody, apiVersion, kind, name))
2704
2705 req := rest.Patch(types.ApplyPatchType).
2706 AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
2707 Name(name).
2708 VersionedParams(&tc.opts, metav1.ParameterCodec)
2709 result := req.Body(applyCreateBody).Do(context.TODO())
2710 if result.Error() == nil && tc.strictDecodingError != "" {
2711 t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
2712 }
2713 if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
2714 t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
2715 }
2716
2717 if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
2718 t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
2719 }
2720 for i, strictWarn := range tc.strictDecodingWarnings {
2721 if strictWarn != result.Warnings()[i].Text {
2722 t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
2723 }
2724
2725 }
2726 })
2727 }
2728 }
2729
2730
2731
2732
2733
2734 func testFieldValidationApplyUpdateCRD(t *testing.T, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
2735 var testcases = []struct {
2736 name string
2737 opts metav1.PatchOptions
2738 strictDecodingError string
2739 strictDecodingWarnings []string
2740 }{
2741 {
2742 name: "strict-validation",
2743 opts: metav1.PatchOptions{
2744 FieldValidation: "Strict",
2745 FieldManager: "mgr",
2746 },
2747 strictDecodingError: `error strict decoding YAML: error converting YAML to JSON: yaml: unmarshal errors:
2748 line 10: key "knownField1" already set in map
2749 line 16: key "hostPort" already set in map`,
2750 },
2751 {
2752 name: "warn-validation",
2753 opts: metav1.PatchOptions{
2754 FieldValidation: "Warn",
2755 FieldManager: "mgr",
2756 },
2757 strictDecodingWarnings: []string{
2758 `line 10: key "knownField1" already set in map`,
2759 `line 16: key "hostPort" already set in map`,
2760 },
2761 },
2762 {
2763 name: "ignore-validation",
2764 opts: metav1.PatchOptions{
2765 FieldValidation: "Ignore",
2766 FieldManager: "mgr",
2767 },
2768 },
2769 {
2770 name: "no-validation",
2771 opts: metav1.PatchOptions{
2772 FieldManager: "mgr",
2773 },
2774 strictDecodingWarnings: []string{
2775 `line 10: key "knownField1" already set in map`,
2776 `line 16: key "hostPort" already set in map`,
2777 },
2778 },
2779 }
2780
2781 for _, tc := range testcases {
2782 t.Run(tc.name, func(t *testing.T) {
2783 kind := gvk.Kind
2784 apiVersion := gvk.Group + "/" + gvk.Version
2785
2786
2787 name := fmt.Sprintf("apply-update-crd-%s", tc.name)
2788 applyCreateBody := []byte(fmt.Sprintf(crdApplyValidBody, apiVersion, kind, name))
2789 createReq := rest.Patch(types.ApplyPatchType).
2790 AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
2791 Name(name).
2792 VersionedParams(&tc.opts, metav1.ParameterCodec)
2793 createResult := createReq.Body(applyCreateBody).Do(context.TODO())
2794 if createResult.Error() != nil {
2795 t.Fatalf("unexpected apply create err: %v", createResult.Error())
2796 }
2797
2798 applyUpdateBody := []byte(fmt.Sprintf(crdApplyInvalidBody, apiVersion, kind, name))
2799 updateReq := rest.Patch(types.ApplyPatchType).
2800 AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
2801 Name(name).
2802 VersionedParams(&tc.opts, metav1.ParameterCodec)
2803 result := updateReq.Body(applyUpdateBody).Do(context.TODO())
2804 if result.Error() == nil && tc.strictDecodingError != "" {
2805 t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
2806 }
2807 if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
2808 t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
2809 }
2810
2811 if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
2812 t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
2813 }
2814 for i, strictWarn := range tc.strictDecodingWarnings {
2815 if strictWarn != result.Warnings()[i].Text {
2816 t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
2817 }
2818
2819 }
2820 })
2821 }
2822 }
2823
2824
2825
2826
2827
2828
2829 func testFieldValidationApplyUpdateCRDSchemaless(t *testing.T, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
2830 var testcases = []struct {
2831 name string
2832 opts metav1.PatchOptions
2833 strictDecodingError string
2834 strictDecodingWarnings []string
2835 }{
2836 {
2837 name: "schemaless-strict-validation",
2838 opts: metav1.PatchOptions{
2839 FieldValidation: "Strict",
2840 FieldManager: "mgr",
2841 },
2842 strictDecodingError: `error strict decoding YAML: error converting YAML to JSON: yaml: unmarshal errors:
2843 line 10: key "knownField1" already set in map
2844 line 16: key "hostPort" already set in map`,
2845 },
2846 {
2847 name: "schemaless-warn-validation",
2848 opts: metav1.PatchOptions{
2849 FieldValidation: "Warn",
2850 FieldManager: "mgr",
2851 },
2852 strictDecodingWarnings: []string{
2853 `line 10: key "knownField1" already set in map`,
2854 `line 16: key "hostPort" already set in map`,
2855 },
2856 },
2857 {
2858 name: "schemaless-ignore-validation",
2859 opts: metav1.PatchOptions{
2860 FieldValidation: "Ignore",
2861 FieldManager: "mgr",
2862 },
2863 },
2864 {
2865 name: "schemaless-no-validation",
2866 opts: metav1.PatchOptions{
2867 FieldManager: "mgr",
2868 },
2869 strictDecodingWarnings: []string{
2870 `line 10: key "knownField1" already set in map`,
2871 `line 16: key "hostPort" already set in map`,
2872 },
2873 },
2874 }
2875
2876 for _, tc := range testcases {
2877 t.Run(tc.name, func(t *testing.T) {
2878 kind := gvk.Kind
2879 apiVersion := gvk.Group + "/" + gvk.Version
2880
2881
2882 name := fmt.Sprintf("apply-update-crd-schemaless-%s", tc.name)
2883 applyCreateBody := []byte(fmt.Sprintf(crdApplyValidBody, apiVersion, kind, name))
2884 createReq := rest.Patch(types.ApplyPatchType).
2885 AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
2886 Name(name).
2887 VersionedParams(&tc.opts, metav1.ParameterCodec)
2888 createResult := createReq.Body(applyCreateBody).Do(context.TODO())
2889 if createResult.Error() != nil {
2890 t.Fatalf("unexpected apply create err: %v", createResult.Error())
2891 }
2892
2893 applyUpdateBody := []byte(fmt.Sprintf(crdApplyInvalidBody, apiVersion, kind, name))
2894 updateReq := rest.Patch(types.ApplyPatchType).
2895 AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
2896 Name(name).
2897 VersionedParams(&tc.opts, metav1.ParameterCodec)
2898 result := updateReq.Body(applyUpdateBody).Do(context.TODO())
2899
2900 if result.Error() == nil && tc.strictDecodingError != "" {
2901 t.Fatalf("received nil error when expecting: %q", tc.strictDecodingError)
2902 }
2903 if result.Error() != nil && (tc.strictDecodingError == "" || !strings.HasSuffix(result.Error().Error(), tc.strictDecodingError)) {
2904 t.Fatalf("expected error: %q, got: %v", tc.strictDecodingError, result.Error())
2905 }
2906
2907 if len(result.Warnings()) != len(tc.strictDecodingWarnings) {
2908 t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.strictDecodingWarnings), len(result.Warnings()))
2909 }
2910 for i, strictWarn := range tc.strictDecodingWarnings {
2911 if strictWarn != result.Warnings()[i].Text {
2912 t.Fatalf("expected warning: %s, got warning: %s", strictWarn, result.Warnings()[i].Text)
2913 }
2914
2915 }
2916 })
2917 }
2918 }
2919
2920 func testFinalizerValidationApplyCreateAndUpdateCRD(t *testing.T, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
2921 var testcases = []struct {
2922 name string
2923 finalizer []string
2924 updatedFinalizer []string
2925 opts metav1.PatchOptions
2926 expectUpdateWarnings []string
2927 expectCreateWarnings []string
2928 }{
2929 {
2930 name: "create-crd-with-invalid-finalizer",
2931 finalizer: []string{"invalid-finalizer"},
2932 expectCreateWarnings: []string{
2933 `metadata.finalizers: "invalid-finalizer": prefer a domain-qualified finalizer name to avoid accidental conflicts with other finalizer writers`,
2934 },
2935 },
2936 {
2937 name: "create-crd-with-valid-finalizer",
2938 finalizer: []string{"kubernetes.io/valid-finalizer"},
2939 },
2940 {
2941 name: "update-crd-with-invalid-finalizer",
2942 finalizer: []string{"invalid-finalizer"},
2943 updatedFinalizer: []string{"another-invalid-finalizer"},
2944 expectCreateWarnings: []string{
2945 `metadata.finalizers: "invalid-finalizer": prefer a domain-qualified finalizer name to avoid accidental conflicts with other finalizer writers`,
2946 },
2947 expectUpdateWarnings: []string{
2948 `metadata.finalizers: "another-invalid-finalizer": prefer a domain-qualified finalizer name to avoid accidental conflicts with other finalizer writers`,
2949 },
2950 },
2951 {
2952 name: "update-crd-with-valid-finalizer",
2953 finalizer: []string{"kubernetes.io/valid-finalizer"},
2954 updatedFinalizer: []string{"kubernetes.io/another-valid-finalizer"},
2955 },
2956 {
2957 name: "update-crd-with-valid-finalizer-leaving-an-existing-invalid-finalizer",
2958 finalizer: []string{"invalid-finalizer"},
2959 updatedFinalizer: []string{"kubernetes.io/another-valid-finalizer"},
2960 expectCreateWarnings: []string{
2961 `metadata.finalizers: "invalid-finalizer": prefer a domain-qualified finalizer name to avoid accidental conflicts with other finalizer writers`,
2962 },
2963 },
2964 }
2965
2966 for _, tc := range testcases {
2967 t.Run(tc.name, func(t *testing.T) {
2968 kind := gvk.Kind
2969 apiVersion := gvk.Group + "/" + gvk.Version
2970
2971
2972 name := fmt.Sprintf("apply-create-crd-%s", tc.name)
2973 finalizerVal, _ := json.Marshal(tc.finalizer)
2974 applyCreateBody := []byte(fmt.Sprintf(crdApplyFinalizerBody, apiVersion, kind, name, finalizerVal))
2975
2976 req := rest.Patch(types.ApplyPatchType).
2977 AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
2978 Name(name).
2979 Param("fieldManager", "apply_test").
2980 VersionedParams(&tc.opts, metav1.ParameterCodec)
2981 result := req.Body(applyCreateBody).Do(context.TODO())
2982 if result.Error() != nil {
2983 t.Fatalf("unexpected error: %v", result.Error())
2984 }
2985
2986 if len(result.Warnings()) != len(tc.expectCreateWarnings) {
2987 for _, r := range result.Warnings() {
2988 t.Logf("received warning: %v", r)
2989 }
2990 t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.expectCreateWarnings), len(result.Warnings()))
2991 }
2992 for i, expectedWarning := range tc.expectCreateWarnings {
2993 if expectedWarning != result.Warnings()[i].Text {
2994 t.Fatalf("expected warning: %s, got warning: %s", expectedWarning, result.Warnings()[i].Text)
2995 }
2996 }
2997
2998 if len(tc.updatedFinalizer) != 0 {
2999 finalizerVal, _ := json.Marshal(tc.updatedFinalizer)
3000 applyUpdateBody := []byte(fmt.Sprintf(crdApplyFinalizerBody, apiVersion, kind, name, finalizerVal))
3001 updateReq := rest.Patch(types.ApplyPatchType).
3002 AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
3003 Name(name).
3004 Param("fieldManager", "apply_test").
3005 VersionedParams(&tc.opts, metav1.ParameterCodec)
3006 result = updateReq.Body(applyUpdateBody).Do(context.TODO())
3007
3008 if result.Error() != nil {
3009 t.Fatalf("unexpected error: %v", result.Error())
3010 }
3011
3012 if len(result.Warnings()) != len(tc.expectUpdateWarnings) {
3013 t.Fatalf("unexpected number of warnings, expected: %d, got: %d", len(tc.expectUpdateWarnings), len(result.Warnings()))
3014 }
3015 for i, expectedWarning := range tc.expectUpdateWarnings {
3016 if expectedWarning != result.Warnings()[i].Text {
3017 t.Fatalf("expected warning: %s, got warning: %s", expectedWarning, result.Warnings()[i].Text)
3018 }
3019 }
3020 }
3021 })
3022 }
3023 }
3024
3025 func setupCRD(t testing.TB, config *rest.Config, apiGroup string, schemaless bool) *apiextensionsv1.CustomResourceDefinition {
3026 apiExtensionClient, err := apiextensionsclient.NewForConfig(config)
3027 if err != nil {
3028 t.Fatal(err)
3029 }
3030 dynamicClient, err := dynamic.NewForConfig(config)
3031 if err != nil {
3032 t.Fatal(err)
3033 }
3034
3035 preserveUnknownFields := ""
3036 if schemaless {
3037 preserveUnknownFields = `"x-kubernetes-preserve-unknown-fields": true,`
3038 }
3039 crdSchema := fmt.Sprintf(crdSchemaBase, preserveUnknownFields)
3040
3041
3042 crd := fixtures.NewNoxuV1CustomResourceDefinition(apiextensionsv1.ClusterScoped)
3043
3044
3045 crd.Name = crd.Spec.Names.Plural + "." + apiGroup
3046 crd.Spec.Group = apiGroup
3047
3048 var c apiextensionsv1.CustomResourceValidation
3049 err = json.Unmarshal([]byte(crdSchema), &c)
3050 if err != nil {
3051 t.Fatal(err)
3052 }
3053
3054 for i := range crd.Spec.Versions {
3055 crd.Spec.Versions[i].Schema = &c
3056 }
3057
3058 crd, err = fixtures.CreateNewV1CustomResourceDefinition(crd, apiExtensionClient, dynamicClient)
3059 if err != nil {
3060 t.Fatal(err)
3061 }
3062
3063 return crd
3064 }
3065
3066 func BenchmarkFieldValidation(b *testing.B) {
3067 flag.Lookup("v").Value.Set("0")
3068 server, err := kubeapiservertesting.StartTestServer(b, kubeapiservertesting.NewDefaultTestServerOptions(), nil, framework.SharedEtcd())
3069 if err != nil {
3070 b.Fatal(err)
3071 }
3072 config := server.ClientConfig
3073 defer server.TearDownFn()
3074
3075
3076 config.WarningHandler = rest.NoWarnings{}
3077
3078 client := clientset.NewForConfigOrDie(config)
3079
3080 schemaCRD := setupCRD(b, config, "schema.example.com", false)
3081 schemaGVR := schema.GroupVersionResource{
3082 Group: schemaCRD.Spec.Group,
3083 Version: schemaCRD.Spec.Versions[0].Name,
3084 Resource: schemaCRD.Spec.Names.Plural,
3085 }
3086 schemaGVK := schema.GroupVersionKind{
3087 Group: schemaCRD.Spec.Group,
3088 Version: schemaCRD.Spec.Versions[0].Name,
3089 Kind: schemaCRD.Spec.Names.Kind,
3090 }
3091
3092 schemalessCRD := setupCRD(b, config, "schemaless.example.com", true)
3093 schemalessGVR := schema.GroupVersionResource{
3094 Group: schemalessCRD.Spec.Group,
3095 Version: schemalessCRD.Spec.Versions[0].Name,
3096 Resource: schemalessCRD.Spec.Names.Plural,
3097 }
3098 schemalessGVK := schema.GroupVersionKind{
3099 Group: schemalessCRD.Spec.Group,
3100 Version: schemalessCRD.Spec.Versions[0].Name,
3101 Kind: schemalessCRD.Spec.Names.Kind,
3102 }
3103
3104 rest := client.Discovery().RESTClient()
3105
3106 b.Run("Post", func(b *testing.B) { benchFieldValidationPost(b, client) })
3107 b.Run("Put", func(b *testing.B) { benchFieldValidationPut(b, client) })
3108 b.Run("PatchTyped", func(b *testing.B) { benchFieldValidationPatchTyped(b, client) })
3109 b.Run("SMP", func(b *testing.B) { benchFieldValidationSMP(b, client) })
3110 b.Run("ApplyCreate", func(b *testing.B) { benchFieldValidationApplyCreate(b, client) })
3111 b.Run("ApplyUpdate", func(b *testing.B) { benchFieldValidationApplyUpdate(b, client) })
3112
3113 b.Run("PostCRD", func(b *testing.B) { benchFieldValidationPostCRD(b, rest, schemaGVK, schemaGVR) })
3114 b.Run("PutCRD", func(b *testing.B) { benchFieldValidationPutCRD(b, rest, schemaGVK, schemaGVR) })
3115 b.Run("PatchCRD", func(b *testing.B) { benchFieldValidationPatchCRD(b, rest, schemaGVK, schemaGVR) })
3116 b.Run("ApplyCreateCRD", func(b *testing.B) { benchFieldValidationApplyCreateCRD(b, rest, schemaGVK, schemaGVR) })
3117 b.Run("ApplyUpdateCRD", func(b *testing.B) { benchFieldValidationApplyUpdateCRD(b, rest, schemaGVK, schemaGVR) })
3118
3119 b.Run("PostCRDSchemaless", func(b *testing.B) { benchFieldValidationPostCRD(b, rest, schemalessGVK, schemalessGVR) })
3120 b.Run("PutCRDSchemaless", func(b *testing.B) { benchFieldValidationPutCRD(b, rest, schemalessGVK, schemalessGVR) })
3121 b.Run("PatchCRDSchemaless", func(b *testing.B) { benchFieldValidationPatchCRD(b, rest, schemalessGVK, schemalessGVR) })
3122 b.Run("ApplyCreateCRDSchemaless", func(b *testing.B) { benchFieldValidationApplyCreateCRD(b, rest, schemalessGVK, schemalessGVR) })
3123 b.Run("ApplyUpdateCRDSchemaless", func(b *testing.B) { benchFieldValidationApplyUpdateCRD(b, rest, schemalessGVK, schemalessGVR) })
3124
3125 }
3126
3127 func benchFieldValidationPost(b *testing.B, client clientset.Interface) {
3128 var benchmarks = []struct {
3129 name string
3130 bodyBase string
3131 opts metav1.CreateOptions
3132 contentType string
3133 }{
3134 {
3135 name: "post-strict-validation",
3136 opts: metav1.CreateOptions{
3137 FieldValidation: "Strict",
3138 },
3139 bodyBase: validBodyJSON,
3140 },
3141 {
3142 name: "post-warn-validation",
3143 opts: metav1.CreateOptions{
3144 FieldValidation: "Warn",
3145 },
3146 bodyBase: validBodyJSON,
3147 },
3148 {
3149 name: "post-ignore-validation",
3150 opts: metav1.CreateOptions{
3151 FieldValidation: "Ignore",
3152 },
3153 bodyBase: validBodyJSON,
3154 },
3155 {
3156 name: "post-strict-validation-yaml",
3157 opts: metav1.CreateOptions{
3158 FieldValidation: "Strict",
3159 },
3160 bodyBase: validBodyYAML,
3161 contentType: "application/yaml",
3162 },
3163 {
3164 name: "post-warn-validation-yaml",
3165 opts: metav1.CreateOptions{
3166 FieldValidation: "Warn",
3167 },
3168 bodyBase: validBodyYAML,
3169 contentType: "application/yaml",
3170 },
3171 {
3172 name: "post-ignore-validation-yaml",
3173 opts: metav1.CreateOptions{
3174 FieldValidation: "Ignore",
3175 },
3176 bodyBase: validBodyYAML,
3177 contentType: "application/yaml",
3178 },
3179 }
3180
3181 for _, bm := range benchmarks {
3182 b.Run(bm.name, func(b *testing.B) {
3183 b.ResetTimer()
3184 b.ReportAllocs()
3185 for n := 0; n < b.N; n++ {
3186 body := []byte(fmt.Sprintf(bm.bodyBase, fmt.Sprintf("test-deployment-%s-%d-%d-%d", bm.name, n, b.N, time.Now().UnixNano())))
3187 req := client.CoreV1().RESTClient().Post().
3188 AbsPath("/apis/apps/v1").
3189 Namespace("default").
3190 Resource("deployments").
3191 SetHeader("Content-Type", bm.contentType).
3192 VersionedParams(&bm.opts, metav1.ParameterCodec)
3193 result := req.Body(body).Do(context.TODO())
3194 if result.Error() != nil {
3195 b.Fatalf("unexpected request err: %v", result.Error())
3196 }
3197 }
3198 })
3199 }
3200 }
3201
3202 func benchFieldValidationPut(b *testing.B, client clientset.Interface) {
3203 var testcases = []struct {
3204 name string
3205 opts metav1.UpdateOptions
3206 putBodyBase string
3207 contentType string
3208 }{
3209 {
3210 name: "put-strict-validation",
3211 opts: metav1.UpdateOptions{
3212 FieldValidation: "Strict",
3213 },
3214 putBodyBase: validBodyJSON,
3215 },
3216 {
3217 name: "put-warn-validation",
3218 opts: metav1.UpdateOptions{
3219 FieldValidation: "Warn",
3220 },
3221 putBodyBase: validBodyJSON,
3222 },
3223 {
3224 name: "put-ignore-validation",
3225 opts: metav1.UpdateOptions{
3226 FieldValidation: "Ignore",
3227 },
3228 putBodyBase: validBodyJSON,
3229 },
3230 {
3231 name: "put-strict-validation-yaml",
3232 opts: metav1.UpdateOptions{
3233 FieldValidation: "Strict",
3234 },
3235 putBodyBase: validBodyYAML,
3236 contentType: "application/yaml",
3237 },
3238 {
3239 name: "put-warn-validation-yaml",
3240 opts: metav1.UpdateOptions{
3241 FieldValidation: "Warn",
3242 },
3243 putBodyBase: validBodyYAML,
3244 contentType: "application/yaml",
3245 },
3246 {
3247 name: "put-ignore-validation-yaml",
3248 opts: metav1.UpdateOptions{
3249 FieldValidation: "Ignore",
3250 },
3251 putBodyBase: validBodyYAML,
3252 contentType: "application/yaml",
3253 },
3254 }
3255
3256 for _, tc := range testcases {
3257 b.Run(tc.name, func(b *testing.B) {
3258 names := make([]string, b.N)
3259 for n := 0; n < b.N; n++ {
3260 deployName := fmt.Sprintf("%s-%d-%d-%d", tc.name, n, b.N, time.Now().UnixNano())
3261 names[n] = deployName
3262 postBody := []byte(fmt.Sprintf(string(validBodyJSON), deployName))
3263
3264 if _, err := client.CoreV1().RESTClient().Post().
3265 AbsPath("/apis/apps/v1").
3266 Namespace("default").
3267 Resource("deployments").
3268 Body(postBody).
3269 DoRaw(context.TODO()); err != nil {
3270 b.Fatalf("failed to create initial deployment: %v", err)
3271 }
3272
3273 }
3274 b.ResetTimer()
3275 b.ReportAllocs()
3276 for n := 0; n < b.N; n++ {
3277 deployName := names[n]
3278 putBody := []byte(fmt.Sprintf(string(tc.putBodyBase), deployName))
3279 req := client.CoreV1().RESTClient().Put().
3280 AbsPath("/apis/apps/v1").
3281 Namespace("default").
3282 Resource("deployments").
3283 SetHeader("Content-Type", tc.contentType).
3284 Name(deployName).
3285 VersionedParams(&tc.opts, metav1.ParameterCodec)
3286 result := req.Body([]byte(putBody)).Do(context.TODO())
3287 if result.Error() != nil {
3288 b.Fatalf("unexpected request err: %v", result.Error())
3289 }
3290 }
3291 })
3292 }
3293 }
3294
3295 func benchFieldValidationPatchTyped(b *testing.B, client clientset.Interface) {
3296 mergePatchBodyValid := `
3297 {
3298 "spec": {
3299 "paused": false,
3300 "template": {
3301 "spec": {
3302 "containers": [{
3303 "name": "nginx",
3304 "image": "nginx:latest",
3305 "imagePullPolicy": "Always"
3306 }]
3307 }
3308 },
3309 "replicas": 2
3310 }
3311 }
3312 `
3313
3314 jsonPatchBodyValid := `
3315 [
3316 {"op": "add", "path": "/spec/paused", "value": true},
3317 {"op": "add", "path": "/spec/template/spec/containers/0/imagePullPolicy", "value": "Never"},
3318 {"op": "add", "path": "/spec/replicas", "value": 2}
3319 ]
3320 `
3321
3322 var testcases = []struct {
3323 name string
3324 opts metav1.PatchOptions
3325 patchType types.PatchType
3326 body string
3327 }{
3328 {
3329 name: "merge-patch-strict-validation",
3330 opts: metav1.PatchOptions{
3331 FieldValidation: "Strict",
3332 },
3333 patchType: types.MergePatchType,
3334 body: mergePatchBodyValid,
3335 },
3336 {
3337 name: "merge-patch-warn-validation",
3338 opts: metav1.PatchOptions{
3339 FieldValidation: "Warn",
3340 },
3341 patchType: types.MergePatchType,
3342 body: mergePatchBodyValid,
3343 },
3344 {
3345 name: "merge-patch-ignore-validation",
3346 opts: metav1.PatchOptions{
3347 FieldValidation: "Ignore",
3348 },
3349 patchType: types.MergePatchType,
3350 body: mergePatchBodyValid,
3351 },
3352 {
3353 name: "json-patch-strict-validation",
3354 patchType: types.JSONPatchType,
3355 opts: metav1.PatchOptions{
3356 FieldValidation: "Strict",
3357 },
3358 body: jsonPatchBodyValid,
3359 },
3360 {
3361 name: "json-patch-warn-validation",
3362 patchType: types.JSONPatchType,
3363 opts: metav1.PatchOptions{
3364 FieldValidation: "Warn",
3365 },
3366 body: jsonPatchBodyValid,
3367 },
3368 {
3369 name: "json-patch-ignore-validation",
3370 patchType: types.JSONPatchType,
3371 opts: metav1.PatchOptions{
3372 FieldValidation: "Ignore",
3373 },
3374 body: jsonPatchBodyValid,
3375 },
3376 }
3377
3378 for _, tc := range testcases {
3379 b.Run(tc.name, func(b *testing.B) {
3380 names := make([]string, b.N)
3381 for n := 0; n < b.N; n++ {
3382 deployName := fmt.Sprintf("%s-%d-%d-%d", tc.name, n, b.N, time.Now().UnixNano())
3383 names[n] = deployName
3384 postBody := []byte(fmt.Sprintf(string(validBodyJSON), deployName))
3385
3386 if _, err := client.CoreV1().RESTClient().Post().
3387 AbsPath("/apis/apps/v1").
3388 Namespace("default").
3389 Resource("deployments").
3390 Body(postBody).
3391 DoRaw(context.TODO()); err != nil {
3392 b.Fatalf("failed to create initial deployment: %v", err)
3393 }
3394 }
3395 b.ResetTimer()
3396 b.ReportAllocs()
3397 for n := 0; n < b.N; n++ {
3398 deployName := names[n]
3399 req := client.CoreV1().RESTClient().Patch(tc.patchType).
3400 AbsPath("/apis/apps/v1").
3401 Namespace("default").
3402 Resource("deployments").
3403 Name(deployName).
3404 VersionedParams(&tc.opts, metav1.ParameterCodec)
3405 result := req.Body([]byte(tc.body)).Do(context.TODO())
3406 if result.Error() != nil {
3407 b.Fatalf("unexpected request err: %v", result.Error())
3408 }
3409 }
3410
3411 })
3412 }
3413 }
3414
3415 func benchFieldValidationSMP(b *testing.B, client clientset.Interface) {
3416 smpBodyValid := `
3417 {
3418 "spec": {
3419 "replicas": 3,
3420 "paused": false,
3421 "selector": {
3422 "matchLabels": {
3423 "app": "nginx"
3424 }
3425 },
3426 "template": {
3427 "metadata": {
3428 "labels": {
3429 "app": "nginx"
3430 }
3431 },
3432 "spec": {
3433 "containers": [{
3434 "name": "nginx",
3435 "imagePullPolicy": "Never"
3436 }]
3437 }
3438 }
3439 }
3440 }
3441 `
3442 var testcases = []struct {
3443 name string
3444 opts metav1.PatchOptions
3445 body string
3446 }{
3447 {
3448 name: "smp-strict-validation",
3449 opts: metav1.PatchOptions{
3450 FieldValidation: "Strict",
3451 },
3452 body: smpBodyValid,
3453 },
3454 {
3455 name: "smp-warn-validation",
3456 opts: metav1.PatchOptions{
3457 FieldValidation: "Warn",
3458 },
3459 body: smpBodyValid,
3460 },
3461 {
3462 name: "smp-ignore-validation",
3463 opts: metav1.PatchOptions{
3464 FieldValidation: "Ignore",
3465 },
3466 body: smpBodyValid,
3467 },
3468 }
3469
3470 for _, tc := range testcases {
3471 b.Run(tc.name, func(b *testing.B) {
3472 names := make([]string, b.N)
3473 for n := 0; n < b.N; n++ {
3474 name := fmt.Sprintf("%s-%d-%d-%d", tc.name, n, b.N, time.Now().UnixNano())
3475 names[n] = name
3476 body := []byte(fmt.Sprintf(validBodyJSON, name))
3477 _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
3478 AbsPath("/apis/apps/v1").
3479 Namespace("default").
3480 Resource("deployments").
3481 Name(name).
3482 Param("fieldManager", "apply_test").
3483 Body(body).
3484 Do(context.TODO()).
3485 Get()
3486 if err != nil {
3487 b.Fatalf("Failed to create object using Apply patch: %v", err)
3488 }
3489 }
3490 b.ResetTimer()
3491 b.ReportAllocs()
3492 for n := 0; n < b.N; n++ {
3493 name := names[n]
3494 req := client.CoreV1().RESTClient().Patch(types.StrategicMergePatchType).
3495 AbsPath("/apis/apps/v1").
3496 Namespace("default").
3497 Resource("deployments").
3498 Name(name).
3499 VersionedParams(&tc.opts, metav1.ParameterCodec)
3500 result := req.Body([]byte(tc.body)).Do(context.TODO())
3501 if result.Error() != nil {
3502 b.Fatalf("unexpected request err: %v", result.Error())
3503 }
3504 }
3505 })
3506 }
3507
3508 }
3509
3510 func benchFieldValidationApplyCreate(b *testing.B, client clientset.Interface) {
3511 var testcases = []struct {
3512 name string
3513 opts metav1.PatchOptions
3514 }{
3515 {
3516 name: "strict-validation",
3517 opts: metav1.PatchOptions{
3518 FieldValidation: "Strict",
3519 FieldManager: "mgr",
3520 },
3521 },
3522 {
3523 name: "warn-validation",
3524 opts: metav1.PatchOptions{
3525 FieldValidation: "Warn",
3526 FieldManager: "mgr",
3527 },
3528 },
3529 {
3530 name: "ignore-validation",
3531 opts: metav1.PatchOptions{
3532 FieldValidation: "Ignore",
3533 FieldManager: "mgr",
3534 },
3535 },
3536 }
3537
3538 for _, tc := range testcases {
3539 b.Run(tc.name, func(b *testing.B) {
3540 b.ResetTimer()
3541 b.ReportAllocs()
3542 for n := 0; n < b.N; n++ {
3543 name := fmt.Sprintf("apply-create-deployment-%s-%d-%d-%d", tc.name, n, b.N, time.Now().UnixNano())
3544 body := []byte(fmt.Sprintf(validBodyJSON, name))
3545 req := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
3546 AbsPath("/apis/apps/v1").
3547 Namespace("default").
3548 Resource("deployments").
3549 Name(name).
3550 VersionedParams(&tc.opts, metav1.ParameterCodec)
3551 result := req.Body(body).Do(context.TODO())
3552 if result.Error() != nil {
3553 b.Fatalf("unexpected request err: %v", result.Error())
3554 }
3555 }
3556 })
3557 }
3558 }
3559
3560 func benchFieldValidationApplyUpdate(b *testing.B, client clientset.Interface) {
3561 var testcases = []struct {
3562 name string
3563 opts metav1.PatchOptions
3564 }{
3565 {
3566 name: "strict-validation",
3567 opts: metav1.PatchOptions{
3568 FieldValidation: "Strict",
3569 FieldManager: "mgr",
3570 },
3571 },
3572 {
3573 name: "warn-validation",
3574 opts: metav1.PatchOptions{
3575 FieldValidation: "Warn",
3576 FieldManager: "mgr",
3577 },
3578 },
3579 {
3580 name: "ignore-validation",
3581 opts: metav1.PatchOptions{
3582 FieldValidation: "Ignore",
3583 FieldManager: "mgr",
3584 },
3585 },
3586 }
3587
3588 for _, tc := range testcases {
3589 b.Run(tc.name, func(b *testing.B) {
3590 names := make([]string, b.N)
3591 for n := 0; n < b.N; n++ {
3592 name := fmt.Sprintf("apply-update-deployment-%s-%d-%d-%d", tc.name, n, b.N, time.Now().UnixNano())
3593 names[n] = name
3594 createBody := []byte(fmt.Sprintf(validBodyJSON, name))
3595 createReq := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
3596 AbsPath("/apis/apps/v1").
3597 Namespace("default").
3598 Resource("deployments").
3599 Name(name).
3600 VersionedParams(&tc.opts, metav1.ParameterCodec)
3601 createResult := createReq.Body(createBody).Do(context.TODO())
3602 if createResult.Error() != nil {
3603 b.Fatalf("unexpected apply create err: %v", createResult.Error())
3604 }
3605 }
3606 b.ResetTimer()
3607 b.ReportAllocs()
3608 for n := 0; n < b.N; n++ {
3609 name := names[n]
3610 updateBody := []byte(fmt.Sprintf(applyValidBody, name))
3611 updateReq := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
3612 AbsPath("/apis/apps/v1").
3613 Namespace("default").
3614 Resource("deployments").
3615 Name(name).
3616 VersionedParams(&tc.opts, metav1.ParameterCodec)
3617 result := updateReq.Body(updateBody).Do(context.TODO())
3618 if result.Error() != nil {
3619 b.Fatalf("unexpected request err: %v", result.Error())
3620 }
3621 }
3622 })
3623 }
3624 }
3625
3626 func benchFieldValidationPostCRD(b *testing.B, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
3627 var testcases = []struct {
3628 name string
3629 opts metav1.PatchOptions
3630 body string
3631 contentType string
3632 }{
3633 {
3634 name: "crd-post-strict-validation",
3635 opts: metav1.PatchOptions{
3636 FieldValidation: "Strict",
3637 },
3638 body: crdValidBody,
3639 },
3640 {
3641 name: "crd-post-warn-validation",
3642 opts: metav1.PatchOptions{
3643 FieldValidation: "Warn",
3644 },
3645 body: crdValidBody,
3646 },
3647 {
3648 name: "crd-post-ignore-validation",
3649 opts: metav1.PatchOptions{
3650 FieldValidation: "Ignore",
3651 },
3652 body: crdValidBody,
3653 },
3654 {
3655 name: "crd-post-no-validation",
3656 body: crdValidBody,
3657 },
3658 {
3659 name: "crd-post-strict-validation-yaml",
3660 opts: metav1.PatchOptions{
3661 FieldValidation: "Strict",
3662 },
3663 body: crdValidBodyYAML,
3664 contentType: "application/yaml",
3665 },
3666 {
3667 name: "crd-post-warn-validation-yaml",
3668 opts: metav1.PatchOptions{
3669 FieldValidation: "Warn",
3670 },
3671 body: crdValidBodyYAML,
3672 contentType: "application/yaml",
3673 },
3674 {
3675 name: "crd-post-ignore-validation-yaml",
3676 opts: metav1.PatchOptions{
3677 FieldValidation: "Ignore",
3678 },
3679 body: crdValidBodyYAML,
3680 contentType: "application/yaml",
3681 },
3682 {
3683 name: "crd-post-no-validation-yaml",
3684 body: crdValidBodyYAML,
3685 contentType: "application/yaml",
3686 },
3687 }
3688 for _, tc := range testcases {
3689 b.Run(tc.name, func(b *testing.B) {
3690 b.ResetTimer()
3691 b.ReportAllocs()
3692 for n := 0; n < b.N; n++ {
3693 kind := gvk.Kind
3694 apiVersion := gvk.Group + "/" + gvk.Version
3695
3696
3697 jsonBody := []byte(fmt.Sprintf(tc.body, apiVersion, kind, fmt.Sprintf("test-dep-%s-%d-%d-%d", tc.name, n, b.N, time.Now().UnixNano())))
3698 req := rest.Post().
3699 AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
3700 SetHeader("Content-Type", tc.contentType).
3701 VersionedParams(&tc.opts, metav1.ParameterCodec)
3702 result := req.Body([]byte(jsonBody)).Do(context.TODO())
3703
3704 if result.Error() != nil {
3705 b.Fatalf("unexpected post err: %v", result.Error())
3706 }
3707 }
3708 })
3709 }
3710 }
3711
3712 func benchFieldValidationPutCRD(b *testing.B, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
3713 var testcases = []struct {
3714 name string
3715 opts metav1.PatchOptions
3716 putBody string
3717 contentType string
3718 }{
3719 {
3720 name: "crd-put-strict-validation",
3721 opts: metav1.PatchOptions{
3722 FieldValidation: "Strict",
3723 },
3724 putBody: crdValidBody,
3725 },
3726 {
3727 name: "crd-put-warn-validation",
3728 opts: metav1.PatchOptions{
3729 FieldValidation: "Warn",
3730 },
3731 putBody: crdValidBody,
3732 },
3733 {
3734 name: "crd-put-ignore-validation",
3735 opts: metav1.PatchOptions{
3736 FieldValidation: "Ignore",
3737 },
3738 putBody: crdValidBody,
3739 },
3740 {
3741 name: "crd-put-no-validation",
3742 putBody: crdValidBody,
3743 },
3744 {
3745 name: "crd-put-strict-validation-yaml",
3746 opts: metav1.PatchOptions{
3747 FieldValidation: "Strict",
3748 },
3749 putBody: crdValidBodyYAML,
3750 contentType: "application/yaml",
3751 },
3752 {
3753 name: "crd-put-warn-validation-yaml",
3754 opts: metav1.PatchOptions{
3755 FieldValidation: "Warn",
3756 },
3757 putBody: crdValidBodyYAML,
3758 contentType: "application/yaml",
3759 },
3760 {
3761 name: "crd-put-ignore-validation-yaml",
3762 opts: metav1.PatchOptions{
3763 FieldValidation: "Ignore",
3764 },
3765 putBody: crdValidBodyYAML,
3766 contentType: "application/yaml",
3767 },
3768 {
3769 name: "crd-put-no-validation-yaml",
3770 putBody: crdValidBodyYAML,
3771 contentType: "application/yaml",
3772 },
3773 }
3774 for _, tc := range testcases {
3775 b.Run(tc.name, func(b *testing.B) {
3776 kind := gvk.Kind
3777 apiVersion := gvk.Group + "/" + gvk.Version
3778 names := make([]string, b.N)
3779 resourceVersions := make([]string, b.N)
3780 for n := 0; n < b.N; n++ {
3781 deployName := fmt.Sprintf("test-dep-%s-%d-%d-%d", tc.name, n, b.N, time.Now().UnixNano())
3782 names[n] = deployName
3783
3784
3785 jsonPostBody := []byte(fmt.Sprintf(crdValidBody, apiVersion, kind, deployName))
3786 postReq := rest.Post().
3787 AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
3788 VersionedParams(&tc.opts, metav1.ParameterCodec)
3789 postResult, err := postReq.Body([]byte(jsonPostBody)).Do(context.TODO()).Raw()
3790 if err != nil {
3791 b.Fatalf("unexpeted error on CR creation: %v", err)
3792 }
3793 postUnstructured := &unstructured.Unstructured{}
3794 if err := postUnstructured.UnmarshalJSON(postResult); err != nil {
3795 b.Fatalf("unexpeted error unmarshalling created CR: %v", err)
3796 }
3797 resourceVersions[n] = postUnstructured.GetResourceVersion()
3798 }
3799 b.ResetTimer()
3800 b.ReportAllocs()
3801 for n := 0; n < b.N; n++ {
3802
3803 putBody := []byte(fmt.Sprintf(tc.putBody, apiVersion, kind, names[n], resourceVersions[n]))
3804 putReq := rest.Put().
3805 AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
3806 Name(names[n]).
3807 SetHeader("Content-Type", tc.contentType).
3808 VersionedParams(&tc.opts, metav1.ParameterCodec)
3809 result := putReq.Body([]byte(putBody)).Do(context.TODO())
3810 if result.Error() != nil {
3811 b.Fatalf("unexpected put err: %v", result.Error())
3812 }
3813 }
3814 })
3815 }
3816 }
3817
3818 func benchFieldValidationPatchCRD(b *testing.B, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
3819 patchYAMLBody := `
3820 apiVersion: %s
3821 kind: %s
3822 metadata:
3823 name: %s
3824 finalizers:
3825 - test/finalizer
3826 spec:
3827 cronSpec: "* * * * */5"
3828 ports:
3829 - name: x
3830 containerPort: 80
3831 protocol: TCP`
3832
3833 mergePatchBody := `
3834 {
3835 "spec": {
3836 "knownField1": "val1",
3837 "ports": [{
3838 "name": "portName",
3839 "containerPort": 8080,
3840 "protocol": "TCP",
3841 "hostPort": 8081
3842 }]
3843 }
3844 }
3845 `
3846 jsonPatchBody := `
3847 [
3848 {"op": "add", "path": "/spec/knownField1", "value": "val1"},
3849 {"op": "add", "path": "/spec/ports/0/name", "value": "portName"},
3850 {"op": "add", "path": "/spec/ports/0/containerPort", "value": 8080},
3851 {"op": "add", "path": "/spec/ports/0/protocol", "value": "TCP"},
3852 {"op": "add", "path": "/spec/ports/0/hostPort", "value": 8081}
3853 ]
3854 `
3855 var testcases = []struct {
3856 name string
3857 patchType types.PatchType
3858 opts metav1.PatchOptions
3859 body string
3860 }{
3861 {
3862 name: "crd-merge-patch-strict-validation",
3863 patchType: types.MergePatchType,
3864 opts: metav1.PatchOptions{
3865 FieldValidation: "Strict",
3866 },
3867 body: mergePatchBody,
3868 },
3869 {
3870 name: "crd-merge-patch-warn-validation",
3871 patchType: types.MergePatchType,
3872 opts: metav1.PatchOptions{
3873 FieldValidation: "Warn",
3874 },
3875 body: mergePatchBody,
3876 },
3877 {
3878 name: "crd-merge-patch-ignore-validation",
3879 patchType: types.MergePatchType,
3880 opts: metav1.PatchOptions{
3881 FieldValidation: "Ignore",
3882 },
3883 body: mergePatchBody,
3884 },
3885 {
3886 name: "crd-merge-patch-no-validation",
3887 patchType: types.MergePatchType,
3888 body: mergePatchBody,
3889 },
3890 {
3891 name: "crd-json-patch-strict-validation",
3892 patchType: types.JSONPatchType,
3893 opts: metav1.PatchOptions{
3894 FieldValidation: "Strict",
3895 },
3896 body: jsonPatchBody,
3897 },
3898 {
3899 name: "crd-json-patch-warn-validation",
3900 patchType: types.JSONPatchType,
3901 opts: metav1.PatchOptions{
3902 FieldValidation: "Warn",
3903 },
3904 body: jsonPatchBody,
3905 },
3906 {
3907 name: "crd-json-patch-ignore-validation",
3908 patchType: types.JSONPatchType,
3909 opts: metav1.PatchOptions{
3910 FieldValidation: "Ignore",
3911 },
3912 body: jsonPatchBody,
3913 },
3914 {
3915 name: "crd-json-patch-no-validation",
3916 patchType: types.JSONPatchType,
3917 body: jsonPatchBody,
3918 },
3919 }
3920 for _, tc := range testcases {
3921 b.Run(tc.name, func(b *testing.B) {
3922 kind := gvk.Kind
3923 apiVersion := gvk.Group + "/" + gvk.Version
3924 names := make([]string, b.N)
3925 for n := 0; n < b.N; n++ {
3926 deployName := fmt.Sprintf("test-dep-%s-%d-%d-%d", tc.name, n, b.N, time.Now().UnixNano())
3927 names[n] = deployName
3928
3929
3930 yamlBody := []byte(fmt.Sprintf(string(patchYAMLBody), apiVersion, kind, deployName))
3931 createResult, err := rest.Patch(types.ApplyPatchType).
3932 AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
3933 Name(deployName).
3934 Param("fieldManager", "apply_test").
3935 Body(yamlBody).
3936 DoRaw(context.TODO())
3937 if err != nil {
3938 b.Fatalf("failed to create custom resource with apply: %v:\n%v", err, string(createResult))
3939 }
3940 }
3941 b.ResetTimer()
3942 b.ReportAllocs()
3943 for n := 0; n < b.N; n++ {
3944
3945 req := rest.Patch(tc.patchType).
3946 AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
3947 Name(names[n]).
3948 VersionedParams(&tc.opts, metav1.ParameterCodec)
3949 result := req.Body([]byte(tc.body)).Do(context.TODO())
3950 if result.Error() != nil {
3951 b.Fatalf("unexpected patch err: %v", result.Error())
3952 }
3953 }
3954
3955 })
3956 }
3957 }
3958
3959 func benchFieldValidationApplyCreateCRD(b *testing.B, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
3960 var testcases = []struct {
3961 name string
3962 opts metav1.PatchOptions
3963 }{
3964 {
3965 name: "strict-validation",
3966 opts: metav1.PatchOptions{
3967 FieldValidation: "Strict",
3968 FieldManager: "mgr",
3969 },
3970 },
3971 {
3972 name: "warn-validation",
3973 opts: metav1.PatchOptions{
3974 FieldValidation: "Warn",
3975 FieldManager: "mgr",
3976 },
3977 },
3978 {
3979 name: "ignore-validation",
3980 opts: metav1.PatchOptions{
3981 FieldValidation: "Ignore",
3982 FieldManager: "mgr",
3983 },
3984 },
3985 {
3986 name: "no-validation",
3987 opts: metav1.PatchOptions{
3988 FieldManager: "mgr",
3989 },
3990 },
3991 }
3992
3993 for _, tc := range testcases {
3994 b.Run(tc.name, func(b *testing.B) {
3995 b.ResetTimer()
3996 b.ReportAllocs()
3997 for n := 0; n < b.N; n++ {
3998 kind := gvk.Kind
3999 apiVersion := gvk.Group + "/" + gvk.Version
4000 name := fmt.Sprintf("test-dep-%s-%d-%d-%d", tc.name, n, b.N, time.Now().UnixNano())
4001
4002
4003 applyCreateBody := []byte(fmt.Sprintf(crdApplyValidBody, apiVersion, kind, name))
4004
4005 req := rest.Patch(types.ApplyPatchType).
4006 AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
4007 Name(name).
4008 VersionedParams(&tc.opts, metav1.ParameterCodec)
4009 result := req.Body(applyCreateBody).Do(context.TODO())
4010 if result.Error() != nil {
4011 b.Fatalf("unexpected apply err: %v", result.Error())
4012 }
4013
4014 }
4015 })
4016 }
4017 }
4018
4019 func benchFieldValidationApplyUpdateCRD(b *testing.B, rest rest.Interface, gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) {
4020 var testcases = []struct {
4021 name string
4022 opts metav1.PatchOptions
4023 }{
4024 {
4025 name: "strict-validation",
4026 opts: metav1.PatchOptions{
4027 FieldValidation: "Strict",
4028 FieldManager: "mgr",
4029 },
4030 },
4031 {
4032 name: "warn-validation",
4033 opts: metav1.PatchOptions{
4034 FieldValidation: "Warn",
4035 FieldManager: "mgr",
4036 },
4037 },
4038 {
4039 name: "ignore-validation",
4040 opts: metav1.PatchOptions{
4041 FieldValidation: "Ignore",
4042 FieldManager: "mgr",
4043 },
4044 },
4045 {
4046 name: "no-validation",
4047 opts: metav1.PatchOptions{
4048 FieldManager: "mgr",
4049 },
4050 },
4051 }
4052
4053 for _, tc := range testcases {
4054 b.Run(tc.name, func(b *testing.B) {
4055 kind := gvk.Kind
4056 apiVersion := gvk.Group + "/" + gvk.Version
4057 names := make([]string, b.N)
4058
4059 for n := 0; n < b.N; n++ {
4060 names[n] = fmt.Sprintf("apply-update-crd-%s-%d-%d-%d", tc.name, n, b.N, time.Now().UnixNano())
4061 applyCreateBody := []byte(fmt.Sprintf(crdApplyValidBody, apiVersion, kind, names[n]))
4062 createReq := rest.Patch(types.ApplyPatchType).
4063 AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
4064 Name(names[n]).
4065 VersionedParams(&tc.opts, metav1.ParameterCodec)
4066 createResult := createReq.Body(applyCreateBody).Do(context.TODO())
4067 if createResult.Error() != nil {
4068 b.Fatalf("unexpected apply create err: %v", createResult.Error())
4069 }
4070 }
4071 b.ResetTimer()
4072 b.ReportAllocs()
4073 for n := 0; n < b.N; n++ {
4074 applyUpdateBody := []byte(fmt.Sprintf(crdApplyValidBody2, apiVersion, kind, names[n]))
4075 updateReq := rest.Patch(types.ApplyPatchType).
4076 AbsPath("/apis", gvr.Group, gvr.Version, gvr.Resource).
4077 Name(names[n]).
4078 VersionedParams(&tc.opts, metav1.ParameterCodec)
4079 result := updateReq.Body(applyUpdateBody).Do(context.TODO())
4080
4081 if result.Error() != nil {
4082 b.Fatalf("unexpected apply err: %v", result.Error())
4083 }
4084 }
4085
4086 })
4087 }
4088 }
4089
View as plain text