1
16
17 package integration
18
19 import (
20 "context"
21 "fmt"
22 "math"
23 "reflect"
24 "sort"
25 "strings"
26 "testing"
27 "time"
28
29 autoscaling "k8s.io/api/autoscaling/v1"
30 apierrors "k8s.io/apimachinery/pkg/api/errors"
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
37 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
38 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
39 "k8s.io/apiextensions-apiserver/test/integration/fixtures"
40 )
41
42 var labelSelectorPath = ".status.labelSelector"
43 var anotherLabelSelectorPath = ".status.anotherLabelSelector"
44
45 func NewNoxuSubresourcesCRDs(scope apiextensionsv1.ResourceScope) []*apiextensionsv1.CustomResourceDefinition {
46 return []*apiextensionsv1.CustomResourceDefinition{
47
48 {
49 ObjectMeta: metav1.ObjectMeta{Name: "noxus.mygroup.example.com"},
50 Spec: apiextensionsv1.CustomResourceDefinitionSpec{
51 Group: "mygroup.example.com",
52 Names: apiextensionsv1.CustomResourceDefinitionNames{
53 Plural: "noxus",
54 Singular: "nonenglishnoxu",
55 Kind: "WishIHadChosenNoxu",
56 ShortNames: []string{"foo", "bar", "abc", "def"},
57 ListKind: "NoxuItemList",
58 },
59 Scope: scope,
60 Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
61 {
62 Name: "v1beta1",
63 Served: true,
64 Storage: true,
65 Subresources: &apiextensionsv1.CustomResourceSubresources{
66 Status: &apiextensionsv1.CustomResourceSubresourceStatus{},
67 Scale: &apiextensionsv1.CustomResourceSubresourceScale{
68 SpecReplicasPath: ".spec.replicas",
69 StatusReplicasPath: ".status.replicas",
70 LabelSelectorPath: &labelSelectorPath,
71 },
72 },
73 Schema: fixtures.AllowAllSchema(),
74 },
75 {
76 Name: "v1",
77 Served: true,
78 Storage: false,
79 Subresources: &apiextensionsv1.CustomResourceSubresources{
80 Status: &apiextensionsv1.CustomResourceSubresourceStatus{},
81 Scale: &apiextensionsv1.CustomResourceSubresourceScale{
82 SpecReplicasPath: ".spec.replicas",
83 StatusReplicasPath: ".status.replicas",
84 LabelSelectorPath: &anotherLabelSelectorPath,
85 },
86 },
87 Schema: fixtures.AllowAllSchema(),
88 },
89 },
90 },
91 },
92 }
93 }
94
95 func NewNoxuSubresourceInstance(namespace, name, version string) *unstructured.Unstructured {
96 return &unstructured.Unstructured{
97 Object: map[string]interface{}{
98 "apiVersion": fmt.Sprintf("mygroup.example.com/%s", version),
99 "kind": "WishIHadChosenNoxu",
100 "metadata": map[string]interface{}{
101 "namespace": namespace,
102 "name": name,
103 },
104 "spec": map[string]interface{}{
105 "num": int64(10),
106 "replicas": int64(3),
107 },
108 "status": map[string]interface{}{
109 "replicas": int64(7),
110 },
111 },
112 }
113 }
114
115 func NewNoxuSubresourceInstanceWithReplicas(namespace, name, version, replicasField string) *unstructured.Unstructured {
116 return &unstructured.Unstructured{
117 Object: map[string]interface{}{
118 "apiVersion": fmt.Sprintf("mygroup.example.com/%s", version),
119 "kind": "WishIHadChosenNoxu",
120 "metadata": map[string]interface{}{
121 "namespace": namespace,
122 "name": name,
123 },
124 "spec": map[string]interface{}{
125 "num": int64(10),
126 replicasField: int64(3),
127 },
128 "status": map[string]interface{}{
129 "replicas": int64(7),
130 },
131 },
132 }
133 }
134
135 func TestStatusSubresource(t *testing.T) {
136 tearDown, apiExtensionClient, dynamicClient, err := fixtures.StartDefaultServerWithClients(t)
137 if err != nil {
138 t.Fatal(err)
139 }
140 defer tearDown()
141
142 noxuDefinitions := NewNoxuSubresourcesCRDs(apiextensionsv1.NamespaceScoped)
143 for _, noxuDefinition := range noxuDefinitions {
144 noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
145 if err != nil {
146 t.Fatal(err)
147 }
148
149 ns := "not-the-default"
150 for _, v := range noxuDefinition.Spec.Versions {
151 noxuResourceClient := newNamespacedCustomResourceVersionedClient(ns, dynamicClient, noxuDefinition, v.Name)
152 _, err = instantiateVersionedCustomResource(t, NewNoxuSubresourceInstance(ns, "foo", v.Name), noxuResourceClient, noxuDefinition, v.Name)
153 if err != nil {
154 t.Fatalf("unable to create noxu instance: %v", err)
155 }
156 gottenNoxuInstance, err := noxuResourceClient.Get(context.TODO(), "foo", metav1.GetOptions{})
157 if err != nil {
158 t.Fatal(err)
159 }
160
161 if val, ok := gottenNoxuInstance.Object["status"]; ok {
162 t.Fatalf("status should not be set after creation, got %v", val)
163 }
164
165
166 err = unstructured.SetNestedField(gottenNoxuInstance.Object, int64(20), "status", "num")
167 if err != nil {
168 t.Fatalf("unexpected error: %v", err)
169 }
170
171
172 err = unstructured.SetNestedField(gottenNoxuInstance.Object, int64(20), "spec", "num")
173 if err != nil {
174 t.Fatalf("unexpected error: %v", err)
175 }
176
177
178
179 updatedStatusInstance, err := noxuResourceClient.UpdateStatus(context.TODO(), gottenNoxuInstance, metav1.UpdateOptions{})
180 if err != nil {
181 t.Fatalf("unable to update status: %v", err)
182 }
183
184 specNum, found, err := unstructured.NestedInt64(updatedStatusInstance.Object, "spec", "num")
185 if !found || err != nil {
186 t.Fatalf("unable to get .spec.num")
187 }
188 if specNum != int64(10) {
189 t.Fatalf(".spec.num: expected: %v, got: %v", int64(10), specNum)
190 }
191
192 statusNum, found, err := unstructured.NestedInt64(updatedStatusInstance.Object, "status", "num")
193 if !found || err != nil {
194 t.Fatalf("unable to get .status.num")
195 }
196 if statusNum != int64(20) {
197 t.Fatalf(".status.num: expected: %v, got: %v", int64(20), statusNum)
198 }
199
200 gottenNoxuInstance, err = noxuResourceClient.Get(context.TODO(), "foo", metav1.GetOptions{})
201 if err != nil {
202 t.Fatal(err)
203 }
204
205
206 err = unstructured.SetNestedField(gottenNoxuInstance.Object, int64(40), "status", "num")
207 if err != nil {
208 t.Fatalf("unexpected error: %v", err)
209 }
210
211
212 err = unstructured.SetNestedField(gottenNoxuInstance.Object, int64(40), "spec", "num")
213 if err != nil {
214 t.Fatalf("unexpected error: %v", err)
215 }
216
217
218
219 updatedInstance, err := noxuResourceClient.Update(context.TODO(), gottenNoxuInstance, metav1.UpdateOptions{})
220 if err != nil {
221 t.Fatalf("unable to update instance: %v", err)
222 }
223
224 specNum, found, err = unstructured.NestedInt64(updatedInstance.Object, "spec", "num")
225 if !found || err != nil {
226 t.Fatalf("unable to get .spec.num")
227 }
228 if specNum != int64(40) {
229 t.Fatalf(".spec.num: expected: %v, got: %v", int64(40), specNum)
230 }
231
232 statusNum, found, err = unstructured.NestedInt64(updatedInstance.Object, "status", "num")
233 if !found || err != nil {
234 t.Fatalf("unable to get .status.num")
235 }
236 if statusNum != int64(20) {
237 t.Fatalf(".status.num: expected: %v, got: %v", int64(20), statusNum)
238 }
239 noxuResourceClient.Delete(context.TODO(), "foo", metav1.DeleteOptions{})
240 }
241 if err := fixtures.DeleteV1CustomResourceDefinition(noxuDefinition, apiExtensionClient); err != nil {
242 t.Fatal(err)
243 }
244 }
245 }
246
247 func TestScaleSubresource(t *testing.T) {
248 groupResource := schema.GroupResource{
249 Group: "mygroup.example.com",
250 Resource: "noxus",
251 }
252
253 tearDown, config, _, err := fixtures.StartDefaultServer(t)
254 if err != nil {
255 t.Fatal(err)
256 }
257 defer tearDown()
258
259 apiExtensionClient, err := clientset.NewForConfig(config)
260 if err != nil {
261 t.Fatal(err)
262 }
263 dynamicClient, err := dynamic.NewForConfig(config)
264 if err != nil {
265 t.Fatal(err)
266 }
267
268 noxuDefinitions := NewNoxuSubresourcesCRDs(apiextensionsv1.NamespaceScoped)
269 for _, noxuDefinition := range noxuDefinitions {
270 for _, v := range noxuDefinition.Spec.Versions {
271
272 noxuDefinition := noxuDefinition.DeepCopy()
273
274 subresources, err := getSubresourcesForVersion(noxuDefinition, v.Name)
275 if err != nil {
276 t.Fatal(err)
277 }
278
279 subresources.Scale.SpecReplicasPath = "foo,bar"
280 _, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
281 if err == nil {
282 t.Fatalf("unexpected non-error: specReplicasPath should be a valid json path under .spec")
283 }
284
285 subresources.Scale.SpecReplicasPath = ".spec.replicas"
286 noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
287 if err != nil {
288 t.Fatal(err)
289 }
290
291 ns := "not-the-default"
292 noxuResourceClient := newNamespacedCustomResourceVersionedClient(ns, dynamicClient, noxuDefinition, v.Name)
293 _, err = instantiateVersionedCustomResource(t, NewNoxuSubresourceInstance(ns, "foo", v.Name), noxuResourceClient, noxuDefinition, v.Name)
294 if err != nil {
295 t.Fatalf("unable to create noxu instance: %v", err)
296 }
297
298 scaleClient, err := fixtures.CreateNewVersionedScaleClient(noxuDefinition, config, v.Name)
299 if err != nil {
300 t.Fatal(err)
301 }
302
303
304 gottenNoxuInstance, err := noxuResourceClient.Get(context.TODO(), "foo", metav1.GetOptions{})
305 if err != nil {
306 t.Fatal(err)
307 }
308 err = unstructured.SetNestedField(gottenNoxuInstance.Object, "bar", strings.Split((*subresources.Scale.LabelSelectorPath)[1:], ".")...)
309 if err != nil {
310 t.Fatalf("unexpected error: %v", err)
311 }
312 _, err = noxuResourceClient.UpdateStatus(context.TODO(), gottenNoxuInstance, metav1.UpdateOptions{})
313 if err != nil {
314 t.Fatalf("unable to update status: %v", err)
315 }
316
317
318 gottenScale, err := scaleClient.Scales("not-the-default").Get(context.TODO(), groupResource, "foo", metav1.GetOptions{})
319 if err != nil {
320 t.Fatal(err)
321 }
322 if gottenScale.Spec.Replicas != 3 {
323 t.Fatalf("Scale.Spec.Replicas: expected: %v, got: %v", 3, gottenScale.Spec.Replicas)
324 }
325 if gottenScale.Status.Selector != "bar" {
326 t.Fatalf("Scale.Status.Selector: expected: %v, got: %v", "bar", gottenScale.Status.Selector)
327 }
328
329
330
331 gottenScale.Spec.Replicas = 5
332 gottenScale.Status.Selector = "baz"
333 updatedScale, err := scaleClient.Scales("not-the-default").Update(context.TODO(), groupResource, gottenScale, metav1.UpdateOptions{})
334 if err != nil {
335 t.Fatal(err)
336 }
337 if updatedScale.Spec.Replicas != 5 {
338 t.Fatalf("replicas: expected: %v, got: %v", 5, updatedScale.Spec.Replicas)
339 }
340 if updatedScale.Status.Selector != "bar" {
341 t.Fatalf("scale should not update status: expected %v, got: %v", "bar", updatedScale.Status.Selector)
342 }
343
344
345 updatedNoxuInstance, err := noxuResourceClient.Get(context.TODO(), "foo", metav1.GetOptions{})
346 if err != nil {
347 t.Fatal(err)
348 }
349 specReplicas, found, err := unstructured.NestedInt64(updatedNoxuInstance.Object, "spec", "replicas")
350 if !found || err != nil {
351 t.Fatalf("unable to get .spec.replicas")
352 }
353 if specReplicas != 5 {
354 t.Fatalf("replicas: expected: %v, got: %v", 5, specReplicas)
355 }
356 statusLabelSelector, found, err := unstructured.NestedString(updatedNoxuInstance.Object, strings.Split((*subresources.Scale.LabelSelectorPath)[1:], ".")...)
357 if !found || err != nil {
358 t.Fatalf("unable to get %s", *subresources.Scale.LabelSelectorPath)
359 }
360 if statusLabelSelector != "bar" {
361 t.Fatalf("scale should not update status: expected %v, got: %v", "bar", statusLabelSelector)
362 }
363
364
365
366 gottenNoxuInstance, err = noxuResourceClient.Get(context.TODO(), "foo", metav1.GetOptions{})
367 if err != nil {
368 t.Fatal(err)
369 }
370 err = unstructured.SetNestedField(gottenNoxuInstance.Object, int64(math.MaxInt64), "spec", "replicas")
371 if err != nil {
372 t.Fatalf("unexpected error: %v", err)
373 }
374 _, err = noxuResourceClient.Update(context.TODO(), gottenNoxuInstance, metav1.UpdateOptions{})
375 if err == nil {
376 t.Fatalf("unexpected non-error: .spec.replicas should be less than 2147483647")
377 }
378 noxuResourceClient.Delete(context.TODO(), "foo", metav1.DeleteOptions{})
379 if err := fixtures.DeleteV1CustomResourceDefinition(noxuDefinition, apiExtensionClient); err != nil {
380 t.Fatal(err)
381 }
382 }
383 }
384 }
385
386 func TestApplyScaleSubresource(t *testing.T) {
387 tearDown, config, _, err := fixtures.StartDefaultServer(t)
388 if err != nil {
389 t.Fatal(err)
390 }
391 defer tearDown()
392
393 apiExtensionClient, err := clientset.NewForConfig(config)
394 if err != nil {
395 t.Fatal(err)
396 }
397 dynamicClient, err := dynamic.NewForConfig(config)
398 if err != nil {
399 t.Fatal(err)
400 }
401
402 noxuDefinition := NewNoxuSubresourcesCRDs(apiextensionsv1.NamespaceScoped)[0]
403 subresources, err := getSubresourcesForVersion(noxuDefinition, "v1beta1")
404 if err != nil {
405 t.Fatal(err)
406 }
407 subresources.Scale.SpecReplicasPath = ".spec.replicas[0]"
408 noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
409 if err != nil {
410 t.Fatal(err)
411 }
412
413
414 ns := "not-the-default"
415 noxuResourceClient := newNamespacedCustomResourceVersionedClient(ns, dynamicClient, noxuDefinition, "v1beta1")
416
417 obj := NewNoxuSubresourceInstanceWithReplicas(ns, "foo", "v1beta1", "replicas[0]")
418 obj, err = noxuResourceClient.Create(context.TODO(), obj, metav1.CreateOptions{})
419 if err != nil {
420 t.Logf("%#v", obj)
421 t.Fatalf("Failed to create CustomResource: %v", err)
422 }
423
424 noxuResourceClient = newNamespacedCustomResourceVersionedClient(ns, dynamicClient, noxuDefinition, "v1")
425 patch := `{"metadata": {"name": "foo"}, "kind": "WishIHadChosenNoxu", "apiVersion": "mygroup.example.com/v1", "spec": {"replicas": 3}}`
426 obj, err = noxuResourceClient.Patch(context.TODO(), "foo", types.ApplyPatchType, []byte(patch), metav1.PatchOptions{FieldManager: "applier"})
427 if err != nil {
428 t.Logf("%#v", obj)
429 t.Fatalf("Failed to Apply CustomResource: %v", err)
430 }
431
432 if got := len(obj.GetManagedFields()); got != 2 {
433 t.Fatalf("Expected 2 managed fields, got %v: %v", got, obj.GetManagedFields())
434 }
435
436 _, err = noxuResourceClient.Patch(context.TODO(), "foo", types.MergePatchType, []byte(`{"spec": {"replicas": 5}}`), metav1.PatchOptions{FieldManager: "scaler"}, "scale")
437 if err != nil {
438 t.Fatal(err)
439 }
440
441 obj, err = noxuResourceClient.Get(context.TODO(), "foo", metav1.GetOptions{})
442 if err != nil {
443 t.Fatalf("Failed to Get CustomResource: %v", err)
444 }
445
446
447 managedFields := obj.GetManagedFields()
448 if len(managedFields) != 3 {
449 t.Fatalf("Expected 3 managed fields, got %v: %v", len(managedFields), obj.GetManagedFields())
450 }
451 specEntry := managedFields[0]
452 if specEntry.Manager != "applier" || specEntry.APIVersion != "mygroup.example.com/v1" || specEntry.Operation != "Apply" || string(specEntry.FieldsV1.Raw) != `{"f:spec":{}}` || specEntry.Subresource != "" {
453 t.Fatalf("Unexpected entry: %v", specEntry)
454 }
455 scaleEntry := managedFields[1]
456 if scaleEntry.Manager != "scaler" || scaleEntry.APIVersion != "mygroup.example.com/v1" || scaleEntry.Operation != "Update" || string(scaleEntry.FieldsV1.Raw) != `{"f:spec":{"f:replicas":{}}}` || scaleEntry.Subresource != "scale" {
457 t.Fatalf("Unexpected entry: %v", scaleEntry)
458 }
459 restEntry := managedFields[2]
460 if restEntry.Manager != "integration.test" || restEntry.APIVersion != "mygroup.example.com/v1beta1" {
461 t.Fatalf("Unexpected entry: %v", restEntry)
462 }
463 }
464
465 func TestValidationSchemaWithStatus(t *testing.T) {
466 tearDown, config, _, err := fixtures.StartDefaultServer(t)
467 if err != nil {
468 t.Fatal(err)
469 }
470 defer tearDown()
471
472 apiExtensionClient, err := clientset.NewForConfig(config)
473 if err != nil {
474 t.Fatal(err)
475 }
476 dynamicClient, err := dynamic.NewForConfig(config)
477 if err != nil {
478 t.Fatal(err)
479 }
480
481 noxuDefinition := newNoxuValidationCRDs()[0]
482
483
484 noxuDefinition.Spec.Versions[0].Schema.OpenAPIV3Schema = &apiextensionsv1.JSONSchemaProps{
485 Type: "object",
486 Properties: map[string]apiextensionsv1.JSONSchemaProps{
487 "spec": {
488 Type: "object",
489 Description: "Validation for spec",
490 Properties: map[string]apiextensionsv1.JSONSchemaProps{
491 "replicas": {
492 Type: "integer",
493 },
494 },
495 },
496 },
497 Required: []string{"spec"},
498 Description: "This is a description at the root of the schema",
499 }
500 noxuDefinition.Spec.Versions[1].Schema.OpenAPIV3Schema = noxuDefinition.Spec.Versions[0].Schema.OpenAPIV3Schema
501
502 _, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
503 if err != nil {
504 t.Fatalf("unable to created crd %v: %v", noxuDefinition.Name, err)
505 }
506 }
507
508 func TestValidateOnlyStatus(t *testing.T) {
509 tearDown, apiExtensionClient, dynamicClient, err := fixtures.StartDefaultServerWithClients(t)
510 if err != nil {
511 t.Fatal(err)
512 }
513 defer tearDown()
514
515
516
517
518
519
520
521
522 schema := &apiextensionsv1.JSONSchemaProps{
523 Type: "object",
524 Properties: map[string]apiextensionsv1.JSONSchemaProps{
525 "spec": {
526 Type: "object",
527 Properties: map[string]apiextensionsv1.JSONSchemaProps{
528 "num": {
529 Type: "integer",
530 Maximum: float64Ptr(10),
531 },
532 },
533 },
534 "status": {
535 Type: "object",
536 Properties: map[string]apiextensionsv1.JSONSchemaProps{
537 "num": {
538 Type: "integer",
539 Maximum: float64Ptr(10),
540 },
541 },
542 },
543 },
544 }
545
546 noxuDefinitions := NewNoxuSubresourcesCRDs(apiextensionsv1.NamespaceScoped)
547 for _, noxuDefinition := range noxuDefinitions {
548 noxuDefinition.Spec.Versions[0].Schema = &apiextensionsv1.CustomResourceValidation{
549 OpenAPIV3Schema: schema.DeepCopy(),
550 }
551 noxuDefinition.Spec.Versions[1].Schema = &apiextensionsv1.CustomResourceValidation{
552 OpenAPIV3Schema: schema.DeepCopy(),
553 }
554
555 noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
556 if err != nil {
557 t.Fatal(err)
558 }
559 ns := "not-the-default"
560 for _, v := range noxuDefinition.Spec.Versions {
561 noxuResourceClient := newNamespacedCustomResourceVersionedClient(ns, dynamicClient, noxuDefinition, v.Name)
562
563
564 noxuInstance := NewNoxuSubresourceInstance(ns, "foo", v.Name)
565 err = unstructured.SetNestedField(noxuInstance.Object, int64(10), "status", "num")
566 if err != nil {
567 t.Fatalf("unexpected error: %v", err)
568 }
569
570 createdNoxuInstance, err := instantiateVersionedCustomResource(t, noxuInstance, noxuResourceClient, noxuDefinition, v.Name)
571 if err != nil {
572 t.Fatalf("unable to create noxu instance: %v", err)
573 }
574
575
576 err = unstructured.SetNestedField(createdNoxuInstance.Object, int64(15), "spec", "num")
577 if err != nil {
578 t.Fatalf("unexpected error setting .spec.num: %v", err)
579 }
580 createdNoxuInstance, err = noxuResourceClient.UpdateStatus(context.TODO(), createdNoxuInstance, metav1.UpdateOptions{})
581 if err != nil {
582 t.Fatalf("unexpected error: %v", err)
583 }
584
585
586 err = unstructured.SetNestedField(createdNoxuInstance.Object, int64(15), "status", "num")
587 if err != nil {
588 t.Fatalf("unexpected error setting .status.num: %v", err)
589 }
590 _, err = noxuResourceClient.UpdateStatus(context.TODO(), createdNoxuInstance, metav1.UpdateOptions{})
591 if err == nil {
592 t.Fatal("expected error, but got none")
593 }
594 statusError, isStatus := err.(*apierrors.StatusError)
595 if !isStatus || statusError == nil {
596 t.Fatalf("expected status error, got %T: %v", err, err)
597 }
598 if !strings.Contains(statusError.Error(), "Invalid value") {
599 t.Fatalf("expected 'Invalid value' in error, got: %v", err)
600 }
601 noxuResourceClient.Delete(context.TODO(), "foo", metav1.DeleteOptions{})
602 }
603 if err := fixtures.DeleteV1CustomResourceDefinition(noxuDefinition, apiExtensionClient); err != nil {
604 t.Fatal(err)
605 }
606 }
607 }
608
609 func TestSubresourcesDiscovery(t *testing.T) {
610 tearDown, config, _, err := fixtures.StartDefaultServer(t)
611 if err != nil {
612 t.Fatal(err)
613 }
614 defer tearDown()
615
616 apiExtensionClient, err := clientset.NewForConfig(config)
617 if err != nil {
618 t.Fatal(err)
619 }
620 dynamicClient, err := dynamic.NewForConfig(config)
621 if err != nil {
622 t.Fatal(err)
623 }
624
625 noxuDefinitions := NewNoxuSubresourcesCRDs(apiextensionsv1.NamespaceScoped)
626 for _, noxuDefinition := range noxuDefinitions {
627 noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
628 if err != nil {
629 t.Fatal(err)
630 }
631
632 for _, v := range noxuDefinition.Spec.Versions {
633 group := "mygroup.example.com"
634 version := v.Name
635
636 resources, err := apiExtensionClient.Discovery().ServerResourcesForGroupVersion(group + "/" + version)
637 if err != nil {
638 t.Fatal(err)
639 }
640
641 if len(resources.APIResources) != 3 {
642 t.Fatalf("Expected exactly the resources \"noxus\", \"noxus/status\" and \"noxus/scale\" in group version %v/%v via discovery, got: %v", group, version, resources.APIResources)
643 }
644
645
646 status := resources.APIResources[1]
647
648 if status.Name != "noxus/status" {
649 t.Fatalf("incorrect status via discovery: expected name: %v, got: %v", "noxus/status", status.Name)
650 }
651
652 if status.Namespaced != true {
653 t.Fatalf("incorrect status via discovery: expected namespace: %v, got: %v", true, status.Namespaced)
654 }
655
656 if status.Kind != "WishIHadChosenNoxu" {
657 t.Fatalf("incorrect status via discovery: expected kind: %v, got: %v", "WishIHadChosenNoxu", status.Kind)
658 }
659
660 expectedVerbs := []string{"get", "patch", "update"}
661 sort.Strings(status.Verbs)
662 if !reflect.DeepEqual([]string(status.Verbs), expectedVerbs) {
663 t.Fatalf("incorrect status via discovery: expected: %v, got: %v", expectedVerbs, status.Verbs)
664 }
665
666
667 scale := resources.APIResources[2]
668
669 if scale.Group != autoscaling.GroupName {
670 t.Fatalf("incorrect scale via discovery: expected group: %v, got: %v", autoscaling.GroupName, scale.Group)
671 }
672
673 if scale.Version != "v1" {
674 t.Fatalf("incorrect scale via discovery: expected version: %v, got %v", "v1", scale.Version)
675 }
676
677 if scale.Name != "noxus/scale" {
678 t.Fatalf("incorrect scale via discovery: expected name: %v, got: %v", "noxus/scale", scale.Name)
679 }
680
681 if scale.Namespaced != true {
682 t.Fatalf("incorrect scale via discovery: expected namespace: %v, got: %v", true, scale.Namespaced)
683 }
684
685 if scale.Kind != "Scale" {
686 t.Fatalf("incorrect scale via discovery: expected kind: %v, got: %v", "Scale", scale.Kind)
687 }
688
689 sort.Strings(scale.Verbs)
690 if !reflect.DeepEqual([]string(scale.Verbs), expectedVerbs) {
691 t.Fatalf("incorrect scale via discovery: expected: %v, got: %v", expectedVerbs, scale.Verbs)
692 }
693 }
694 if err := fixtures.DeleteV1CustomResourceDefinition(noxuDefinition, apiExtensionClient); err != nil {
695 t.Fatal(err)
696 }
697 }
698 }
699
700 func TestGeneration(t *testing.T) {
701 tearDown, apiExtensionClient, dynamicClient, err := fixtures.StartDefaultServerWithClients(t)
702 if err != nil {
703 t.Fatal(err)
704 }
705 defer tearDown()
706
707 noxuDefinitions := NewNoxuSubresourcesCRDs(apiextensionsv1.NamespaceScoped)
708 for _, noxuDefinition := range noxuDefinitions {
709 noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
710 if err != nil {
711 t.Fatal(err)
712 }
713
714 ns := "not-the-default"
715 for _, v := range noxuDefinition.Spec.Versions {
716 noxuResourceClient := newNamespacedCustomResourceVersionedClient(ns, dynamicClient, noxuDefinition, v.Name)
717 _, err = instantiateVersionedCustomResource(t, NewNoxuSubresourceInstance(ns, "foo", v.Name), noxuResourceClient, noxuDefinition, v.Name)
718 if err != nil {
719 t.Fatalf("unable to create noxu instance: %v", err)
720 }
721
722
723 gottenNoxuInstance, err := noxuResourceClient.Get(context.TODO(), "foo", metav1.GetOptions{})
724 if err != nil {
725 t.Fatal(err)
726 }
727 if gottenNoxuInstance.GetGeneration() != 1 {
728 t.Fatalf(".metadata.generation should be 1 after creation")
729 }
730
731
732 err = unstructured.SetNestedField(gottenNoxuInstance.Object, int64(20), "status", "num")
733 if err != nil {
734 t.Fatalf("unexpected error: %v", err)
735 }
736
737
738 updatedStatusInstance, err := noxuResourceClient.UpdateStatus(context.TODO(), gottenNoxuInstance, metav1.UpdateOptions{})
739 if err != nil {
740 t.Fatalf("unable to update status: %v", err)
741 }
742 if updatedStatusInstance.GetGeneration() != 1 {
743 t.Fatalf("updating status should not increment .metadata.generation: expected: %v, got: %v", 1, updatedStatusInstance.GetGeneration())
744 }
745
746 gottenNoxuInstance, err = noxuResourceClient.Get(context.TODO(), "foo", metav1.GetOptions{})
747 if err != nil {
748 t.Fatal(err)
749 }
750
751
752 err = unstructured.SetNestedField(gottenNoxuInstance.Object, int64(20), "spec", "num")
753 if err != nil {
754 t.Fatalf("unexpected error: %v", err)
755 }
756
757
758 updatedInstance, err := noxuResourceClient.Update(context.TODO(), gottenNoxuInstance, metav1.UpdateOptions{})
759 if err != nil {
760 t.Fatalf("unable to update instance: %v", err)
761 }
762 if updatedInstance.GetGeneration() != 2 {
763 t.Fatalf("updating spec should increment .metadata.generation: expected: %v, got: %v", 2, updatedStatusInstance.GetGeneration())
764 }
765 noxuResourceClient.Delete(context.TODO(), "foo", metav1.DeleteOptions{})
766 }
767 if err := fixtures.DeleteV1CustomResourceDefinition(noxuDefinition, apiExtensionClient); err != nil {
768 t.Fatal(err)
769 }
770 }
771 }
772
773 func TestSubresourcePatch(t *testing.T) {
774 groupResource := schema.GroupResource{
775 Group: "mygroup.example.com",
776 Resource: "noxus",
777 }
778
779 tearDown, config, _, err := fixtures.StartDefaultServer(t)
780 if err != nil {
781 t.Fatal(err)
782 }
783 defer tearDown()
784
785 apiExtensionClient, err := clientset.NewForConfig(config)
786 if err != nil {
787 t.Fatal(err)
788 }
789 dynamicClient, err := dynamic.NewForConfig(config)
790 if err != nil {
791 t.Fatal(err)
792 }
793
794 noxuDefinitions := NewNoxuSubresourcesCRDs(apiextensionsv1.NamespaceScoped)
795 for _, noxuDefinition := range noxuDefinitions {
796 noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
797 if err != nil {
798 t.Fatal(err)
799 }
800
801 ns := "not-the-default"
802 for _, v := range noxuDefinition.Spec.Versions {
803 noxuResourceClient := newNamespacedCustomResourceVersionedClient(ns, dynamicClient, noxuDefinition, v.Name)
804
805 t.Logf("Creating foo")
806 _, err = instantiateVersionedCustomResource(t, NewNoxuSubresourceInstance(ns, "foo", v.Name), noxuResourceClient, noxuDefinition, v.Name)
807 if err != nil {
808 t.Fatalf("unable to create noxu instance: %v", err)
809 }
810
811 scaleClient, err := fixtures.CreateNewVersionedScaleClient(noxuDefinition, config, v.Name)
812 if err != nil {
813 t.Fatal(err)
814 }
815
816 t.Logf("Patching .status.num to 999")
817 patch := []byte(`{"spec": {"num":999}, "status": {"num":999}}`)
818 patchedNoxuInstance, err := noxuResourceClient.Patch(context.TODO(), "foo", types.MergePatchType, patch, metav1.PatchOptions{}, "status")
819 if err != nil {
820 t.Fatalf("unexpected error: %v", err)
821 }
822
823 expectInt64(t, patchedNoxuInstance.UnstructuredContent(), 999, "status", "num")
824 expectInt64(t, patchedNoxuInstance.UnstructuredContent(), 10, "spec", "num")
825
826
827
828 time.Sleep(time.Second)
829
830 rv := patchedNoxuInstance.GetResourceVersion()
831 found := false
832 t.Logf("Patching .status.num again to 999")
833 patchedNoxuInstance, err = noxuResourceClient.Patch(context.TODO(), "foo", types.MergePatchType, patch, metav1.PatchOptions{}, "status")
834 if err != nil {
835 t.Fatalf("unexpected error: %v", err)
836 }
837
838 expectInt64(t, patchedNoxuInstance.UnstructuredContent(), 999, "status", "num")
839 expectInt64(t, patchedNoxuInstance.UnstructuredContent(), 10, "spec", "num")
840 expectString(t, patchedNoxuInstance.UnstructuredContent(), rv, "metadata", "resourceVersion")
841
842
843 t.Logf("Applying empty patch")
844 patchedNoxuInstance, err = noxuResourceClient.Patch(context.TODO(), "foo", types.MergePatchType, []byte(`{}`), metav1.PatchOptions{}, "status")
845 if err != nil {
846 t.Fatalf("unexpected error: %v", err)
847 }
848
849
850 expectInt64(t, patchedNoxuInstance.UnstructuredContent(), 999, "status", "num")
851 expectInt64(t, patchedNoxuInstance.UnstructuredContent(), 10, "spec", "num")
852 expectString(t, patchedNoxuInstance.UnstructuredContent(), rv, "metadata", "resourceVersion")
853
854 t.Logf("Patching .spec.replicas to 7")
855 patch = []byte(`{"spec": {"replicas":7}, "status": {"replicas":7}}`)
856 patchedNoxuInstance, err = noxuResourceClient.Patch(context.TODO(), "foo", types.MergePatchType, patch, metav1.PatchOptions{}, "scale")
857 if err != nil {
858 t.Fatalf("unexpected error: %v", err)
859 }
860
861 expectInt64(t, patchedNoxuInstance.UnstructuredContent(), 7, "spec", "replicas")
862 expectInt64(t, patchedNoxuInstance.UnstructuredContent(), 0, "status", "replicas")
863 rv, found, err = unstructured.NestedString(patchedNoxuInstance.UnstructuredContent(), "metadata", "resourceVersion")
864 if err != nil {
865 t.Fatal(err)
866 }
867 if !found {
868 t.Fatalf("metadata.resourceVersion not found")
869 }
870
871
872 gottenScale, err := scaleClient.Scales("not-the-default").Get(context.TODO(), groupResource, "foo", metav1.GetOptions{})
873 if err != nil {
874 t.Fatal(err)
875 }
876 if gottenScale.Spec.Replicas != 7 {
877 t.Fatalf("Scale.Spec.Replicas: expected: %v, got: %v", 7, gottenScale.Spec.Replicas)
878 }
879 if gottenScale.Status.Replicas != 0 {
880 t.Fatalf("Scale.Status.Replicas: expected: %v, got: %v", 0, gottenScale.Spec.Replicas)
881 }
882
883
884 t.Logf("Patching .spec.replicas again to 7")
885 patchedNoxuInstance, err = noxuResourceClient.Patch(context.TODO(), "foo", types.MergePatchType, patch, metav1.PatchOptions{}, "scale")
886 if err != nil {
887 t.Fatalf("unexpected error: %v", err)
888 }
889
890 expectInt64(t, patchedNoxuInstance.UnstructuredContent(), 7, "spec", "replicas")
891 expectInt64(t, patchedNoxuInstance.UnstructuredContent(), 0, "status", "replicas")
892 expectString(t, patchedNoxuInstance.UnstructuredContent(), rv, "metadata", "resourceVersion")
893
894
895 t.Logf("Applying empty patch")
896 patchedNoxuInstance, err = noxuResourceClient.Patch(context.TODO(), "foo", types.MergePatchType, []byte(`{}`), metav1.PatchOptions{}, "scale")
897 if err != nil {
898 t.Fatalf("unexpected error: %v", err)
899 }
900
901 expectInt64(t, patchedNoxuInstance.UnstructuredContent(), 7, "spec", "replicas")
902 expectInt64(t, patchedNoxuInstance.UnstructuredContent(), 0, "status", "replicas")
903 expectString(t, patchedNoxuInstance.UnstructuredContent(), rv, "metadata", "resourceVersion")
904
905
906 _, err = noxuResourceClient.Patch(context.TODO(), "foo", types.StrategicMergePatchType, patch, metav1.PatchOptions{}, "status")
907 if err == nil {
908 t.Fatalf("unexpected non-error: strategic merge patch is not supported for custom resources")
909 }
910
911 _, err = noxuResourceClient.Patch(context.TODO(), "foo", types.StrategicMergePatchType, patch, metav1.PatchOptions{}, "scale")
912 if err == nil {
913 t.Fatalf("unexpected non-error: strategic merge patch is not supported for custom resources")
914 }
915 noxuResourceClient.Delete(context.TODO(), "foo", metav1.DeleteOptions{})
916 }
917 if err := fixtures.DeleteV1CustomResourceDefinition(noxuDefinition, apiExtensionClient); err != nil {
918 t.Fatal(err)
919 }
920 }
921 }
922
View as plain text