1
16
17 package customresource_test
18
19 import (
20 "context"
21 "fmt"
22 "reflect"
23 "strings"
24 "testing"
25 "time"
26
27 "github.com/google/go-cmp/cmp"
28
29 autoscalingv1 "k8s.io/api/autoscaling/v1"
30 apiequality "k8s.io/apimachinery/pkg/api/equality"
31 "k8s.io/apimachinery/pkg/api/errors"
32 metainternal "k8s.io/apimachinery/pkg/apis/meta/internalversion"
33 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
34 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
35 "k8s.io/apimachinery/pkg/runtime"
36 "k8s.io/apimachinery/pkg/runtime/schema"
37 "k8s.io/apimachinery/pkg/util/managedfields"
38 genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
39 "k8s.io/apiserver/pkg/registry/generic"
40 registrytest "k8s.io/apiserver/pkg/registry/generic/testing"
41 "k8s.io/apiserver/pkg/registry/rest"
42 etcd3testing "k8s.io/apiserver/pkg/storage/etcd3/testing"
43
44 apiextensionsinternal "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
45 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
46 "k8s.io/apiextensions-apiserver/pkg/apiserver"
47 "k8s.io/apiextensions-apiserver/pkg/crdserverscheme"
48 "k8s.io/apiextensions-apiserver/pkg/registry/customresource"
49 "k8s.io/apiextensions-apiserver/pkg/registry/customresource/tableconvertor"
50 )
51
52 func newStorage(t *testing.T) (customresource.CustomResourceStorage, *etcd3testing.EtcdTestServer) {
53 server, etcdStorage := etcd3testing.NewUnsecuredEtcd3TestClientServer(t)
54 etcdStorage.Codec = unstructured.UnstructuredJSONScheme
55 groupResource := schema.GroupResource{Group: "mygroup.example.com", Resource: "noxus"}
56 restOptions := generic.RESTOptions{StorageConfig: etcdStorage.ForResource(groupResource), Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1, ResourcePrefix: "noxus"}
57
58 parameterScheme := runtime.NewScheme()
59 parameterScheme.AddUnversionedTypes(schema.GroupVersion{Group: "mygroup.example.com", Version: "v1beta1"},
60 &metav1.ListOptions{},
61 &metav1.GetOptions{},
62 &metav1.DeleteOptions{},
63 )
64
65 typer := apiserver.UnstructuredObjectTyper{
66 Delegate: parameterScheme,
67 UnstructuredTyper: crdserverscheme.NewUnstructuredObjectTyper(),
68 }
69
70 kind := schema.GroupVersionKind{Group: "mygroup.example.com", Version: "v1beta1", Kind: "Noxu"}
71
72 labelSelectorPath := ".status.labelSelector"
73 scale := &apiextensionsinternal.CustomResourceSubresourceScale{
74 SpecReplicasPath: ".spec.replicas",
75 StatusReplicasPath: ".status.replicas",
76 LabelSelectorPath: &labelSelectorPath,
77 }
78
79 status := &apiextensionsinternal.CustomResourceSubresourceStatus{}
80
81 headers := []apiextensionsv1.CustomResourceColumnDefinition{
82 {Name: "Age", Type: "date", JSONPath: ".metadata.creationTimestamp"},
83 {Name: "Replicas", Type: "integer", JSONPath: ".spec.replicas"},
84 {Name: "Missing", Type: "string", JSONPath: ".spec.missing"},
85 {Name: "Invalid", Type: "integer", JSONPath: ".spec.string"},
86 {Name: "String", Type: "string", JSONPath: ".spec.string"},
87 {Name: "StringFloat64", Type: "string", JSONPath: ".spec.float64"},
88 {Name: "StringInt64", Type: "string", JSONPath: ".spec.replicas"},
89 {Name: "StringBool", Type: "string", JSONPath: ".spec.bool"},
90 {Name: "Float64", Type: "number", JSONPath: ".spec.float64"},
91 {Name: "Bool", Type: "boolean", JSONPath: ".spec.bool"},
92 }
93 table, _ := tableconvertor.New(headers)
94
95 storage := customresource.NewStorage(
96 groupResource,
97 groupResource,
98 kind,
99 schema.GroupVersionKind{Group: "mygroup.example.com", Version: "v1beta1", Kind: "NoxuItemList"},
100 customresource.NewStrategy(
101 typer,
102 true,
103 kind,
104 nil,
105 nil,
106 nil,
107 status,
108 scale,
109 nil,
110 ),
111 restOptions,
112 []string{"all"},
113 table,
114 managedfields.ResourcePathMappings{},
115 )
116
117 return storage, server
118 }
119
120
121 func createCustomResource(storage *customresource.REST, cr unstructured.Unstructured, t *testing.T) (unstructured.Unstructured, error) {
122 ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), cr.GetNamespace())
123 obj, err := storage.Create(ctx, &cr, rest.ValidateAllObjectFunc, &metav1.CreateOptions{})
124 if err != nil {
125 t.Errorf("Failed to create CustomResource, %v", err)
126 }
127 newCR := obj.(*unstructured.Unstructured)
128 return *newCR, nil
129 }
130
131 func validNewCustomResource() *unstructured.Unstructured {
132 return &unstructured.Unstructured{
133 Object: map[string]interface{}{
134 "apiVersion": "mygroup.example.com/v1beta1",
135 "kind": "Noxu",
136 "metadata": map[string]interface{}{
137 "namespace": "default",
138 "name": "foo",
139 "creationTimestamp": time.Now().Add(-time.Hour*12 - 30*time.Minute).UTC().Format(time.RFC3339),
140 },
141 "spec": map[string]interface{}{
142 "replicas": int64(7),
143 "string": "string",
144 "float64": float64(3.1415926),
145 "bool": true,
146 "stringList": []interface{}{"foo", "bar"},
147 "mixedList": []interface{}{"foo", int64(42)},
148 "nonPrimitiveList": []interface{}{"foo", []interface{}{int64(1), int64(2)}},
149 },
150 },
151 }
152 }
153
154 var validCustomResource = *validNewCustomResource()
155
156 func TestCreate(t *testing.T) {
157 storage, server := newStorage(t)
158 defer server.Terminate(t)
159 defer storage.CustomResource.Store.DestroyFunc()
160 test := registrytest.New(t, storage.CustomResource.Store)
161 cr := validNewCustomResource()
162 cr.SetNamespace("")
163 test.TestCreate(
164 cr,
165 )
166 }
167
168 func TestGet(t *testing.T) {
169 storage, server := newStorage(t)
170 defer server.Terminate(t)
171 defer storage.CustomResource.Store.DestroyFunc()
172 test := registrytest.New(t, storage.CustomResource.Store)
173 test.TestGet(validNewCustomResource())
174 }
175
176 func TestList(t *testing.T) {
177 storage, server := newStorage(t)
178 defer server.Terminate(t)
179 defer storage.CustomResource.Store.DestroyFunc()
180 test := registrytest.New(t, storage.CustomResource.Store)
181 test.TestList(validNewCustomResource())
182 }
183
184 func TestDelete(t *testing.T) {
185 storage, server := newStorage(t)
186 defer server.Terminate(t)
187 defer storage.CustomResource.Store.DestroyFunc()
188 test := registrytest.New(t, storage.CustomResource.Store)
189 test.TestDelete(validNewCustomResource())
190 }
191
192 func TestGenerationNumber(t *testing.T) {
193 storage, server := newStorage(t)
194 defer server.Terminate(t)
195 defer storage.CustomResource.Store.DestroyFunc()
196 modifiedRno := *validNewCustomResource()
197 modifiedRno.SetGeneration(10)
198 ctx := genericapirequest.NewDefaultContext()
199 cr, err := createCustomResource(storage.CustomResource, modifiedRno, t)
200 if err != nil {
201 t.Errorf("unexpected error: %v", err)
202 }
203 etcdCR, err := storage.CustomResource.Get(ctx, cr.GetName(), &metav1.GetOptions{})
204 if err != nil {
205 t.Errorf("unexpected error: %v", err)
206 }
207 storedCR, _ := etcdCR.(*unstructured.Unstructured)
208
209
210 if storedCR.GetGeneration() != 1 {
211 t.Fatalf("Unexpected generation number %v", storedCR.GetGeneration())
212 }
213
214
215 setSpecReplicas(storedCR, getSpecReplicas(storedCR)+1)
216 if _, _, err := storage.CustomResource.Update(ctx, storedCR.GetName(), rest.DefaultUpdatedObjectInfo(storedCR), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}); err != nil {
217 t.Errorf("unexpected error: %v", err)
218 }
219 etcdCR, err = storage.CustomResource.Get(ctx, cr.GetName(), &metav1.GetOptions{})
220 if err != nil {
221 t.Errorf("unexpected error: %v", err)
222 }
223 storedCR, _ = etcdCR.(*unstructured.Unstructured)
224 if storedCR.GetGeneration() != 2 {
225 t.Fatalf("Unexpected generation, spec: %v", storedCR.GetGeneration())
226 }
227
228
229 setStatusReplicas(storedCR, getStatusReplicas(storedCR)+1)
230 if _, _, err := storage.CustomResource.Update(ctx, storedCR.GetName(), rest.DefaultUpdatedObjectInfo(storedCR), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}); err != nil {
231 t.Errorf("unexpected error: %v", err)
232 }
233 etcdCR, err = storage.CustomResource.Get(ctx, cr.GetName(), &metav1.GetOptions{})
234 if err != nil {
235 t.Errorf("unexpected error: %v", err)
236 }
237 storedCR, _ = etcdCR.(*unstructured.Unstructured)
238 if storedCR.GetGeneration() != 2 {
239 t.Fatalf("Unexpected generation, spec: %v", storedCR.GetGeneration())
240 }
241
242 }
243
244 func TestCategories(t *testing.T) {
245 storage, server := newStorage(t)
246 defer server.Terminate(t)
247 defer storage.CustomResource.Store.DestroyFunc()
248
249 expected := []string{"all"}
250 actual := storage.CustomResource.Categories()
251 ok := reflect.DeepEqual(actual, expected)
252 if !ok {
253 t.Errorf("categories are not equal. expected = %v actual = %v", expected, actual)
254 }
255 }
256
257 func TestColumns(t *testing.T) {
258 storage, server := newStorage(t)
259 defer server.Terminate(t)
260 defer storage.CustomResource.Store.DestroyFunc()
261
262 ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
263 key := "/noxus/" + metav1.NamespaceDefault + "/foo"
264 validCustomResource := validNewCustomResource()
265 if err := storage.CustomResource.Storage.Create(ctx, key, validCustomResource, nil, 0, false); err != nil {
266 t.Fatalf("unexpected error: %v", err)
267 }
268
269 gottenList, err := storage.CustomResource.List(ctx, &metainternal.ListOptions{})
270 if err != nil {
271 t.Fatalf("unexpected error: %v", err)
272 }
273
274 tbl, err := storage.CustomResource.ConvertToTable(ctx, gottenList, &metav1.TableOptions{})
275 if err != nil {
276 t.Fatalf("unexpected error: %v", err)
277 }
278
279 expectedColumns := []struct {
280 Name, Type string
281 }{
282 {"Name", "string"},
283 {"Age", "date"},
284 {"Replicas", "integer"},
285 {"Missing", "string"},
286 {"Invalid", "integer"},
287 {"String", "string"},
288 {"StringFloat64", "string"},
289 {"StringInt64", "string"},
290 {"StringBool", "string"},
291 {"Float64", "number"},
292 {"Bool", "boolean"},
293 }
294 if len(tbl.ColumnDefinitions) != len(expectedColumns) {
295 t.Fatalf("got %d columns, expected %d. Got: %+v", len(tbl.ColumnDefinitions), len(expectedColumns), tbl.ColumnDefinitions)
296 }
297 for i, d := range tbl.ColumnDefinitions {
298 if d.Name != expectedColumns[i].Name {
299 t.Errorf("got column %d name %q, expected %q", i, d.Name, expectedColumns[i].Name)
300 }
301 if d.Type != expectedColumns[i].Type {
302 t.Errorf("got column %d type %q, expected %q", i, d.Type, expectedColumns[i].Type)
303 }
304 }
305
306 expectedRows := [][]interface{}{
307 {
308 "foo",
309 "12h",
310 int64(7),
311 nil,
312 nil,
313 "string",
314 "3.1415926",
315 "7",
316 "true",
317 float64(3.1415926),
318 true,
319 },
320 }
321 for i, r := range tbl.Rows {
322 if !reflect.DeepEqual(r.Cells, expectedRows[i]) {
323 t.Errorf("got row %d with cells %#v, expected %#v", i, r.Cells, expectedRows[i])
324 }
325 }
326 }
327
328 func TestStatusUpdate(t *testing.T) {
329 storage, server := newStorage(t)
330 defer server.Terminate(t)
331 defer storage.CustomResource.Store.DestroyFunc()
332 ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
333 key := "/noxus/" + metav1.NamespaceDefault + "/foo"
334 validCustomResource := validNewCustomResource()
335 if err := storage.CustomResource.Storage.Create(ctx, key, validCustomResource, nil, 0, false); err != nil {
336 t.Fatalf("unexpected error: %v", err)
337 }
338
339 gottenObj, err := storage.CustomResource.Get(ctx, "foo", &metav1.GetOptions{})
340 if err != nil {
341 t.Fatalf("unexpected error: %v", err)
342 }
343
344 update := gottenObj.(*unstructured.Unstructured)
345 updateContent := update.Object
346 updateContent["status"] = map[string]interface{}{
347 "replicas": int64(7),
348 }
349
350 if _, _, err := storage.Status.Update(ctx, update.GetName(), rest.DefaultUpdatedObjectInfo(update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}); err != nil {
351 t.Fatalf("unexpected error: %v", err)
352 }
353
354 obj, err := storage.CustomResource.Get(ctx, "foo", &metav1.GetOptions{})
355 if err != nil {
356 t.Fatalf("unexpected error: %v", err)
357 }
358
359 cr, ok := obj.(*unstructured.Unstructured)
360 if !ok {
361 t.Fatal("unexpected error: custom resource should be of type Unstructured")
362 }
363 content := cr.UnstructuredContent()
364
365 spec := content["spec"].(map[string]interface{})
366 status := content["status"].(map[string]interface{})
367
368 if spec["replicas"].(int64) != 7 {
369 t.Errorf("we expected .spec.replicas to not be updated but it was updated to %v", spec["replicas"].(int64))
370 }
371 if status["replicas"].(int64) != 7 {
372 t.Errorf("we expected .status.replicas to be updated to %d but it was %v", 7, status["replicas"].(int64))
373 }
374 }
375
376 func TestScaleGet(t *testing.T) {
377 storage, server := newStorage(t)
378 defer server.Terminate(t)
379 defer storage.CustomResource.Store.DestroyFunc()
380
381 name := "foo"
382
383 var cr unstructured.Unstructured
384 ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
385 key := "/noxus/" + metav1.NamespaceDefault + "/" + name
386 if err := storage.CustomResource.Storage.Create(ctx, key, &validCustomResource, &cr, 0, false); err != nil {
387 t.Fatalf("error setting new custom resource (key: %s) %v: %v", key, validCustomResource, err)
388 }
389
390 want := &autoscalingv1.Scale{
391 TypeMeta: metav1.TypeMeta{
392 Kind: "Scale",
393 APIVersion: "autoscaling/v1",
394 },
395 ObjectMeta: metav1.ObjectMeta{
396 Name: cr.GetName(),
397 Namespace: metav1.NamespaceDefault,
398 UID: cr.GetUID(),
399 ResourceVersion: cr.GetResourceVersion(),
400 CreationTimestamp: cr.GetCreationTimestamp(),
401 },
402 Spec: autoscalingv1.ScaleSpec{
403 Replicas: int32(7),
404 },
405 }
406
407 obj, err := storage.Scale.Get(ctx, name, &metav1.GetOptions{})
408 if err != nil {
409 t.Fatalf("error fetching scale for %s: %v", name, err)
410 }
411
412 got := obj.(*autoscalingv1.Scale)
413 if !apiequality.Semantic.DeepEqual(got, want) {
414 t.Errorf("unexpected scale: %s", cmp.Diff(got, want))
415 }
416 }
417
418 func TestScaleGetWithoutSpecReplicas(t *testing.T) {
419 storage, server := newStorage(t)
420 defer server.Terminate(t)
421 defer storage.CustomResource.Store.DestroyFunc()
422
423 name := "foo"
424
425 var cr unstructured.Unstructured
426 ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
427 key := "/noxus/" + metav1.NamespaceDefault + "/" + name
428 withoutSpecReplicas := validCustomResource.DeepCopy()
429 unstructured.RemoveNestedField(withoutSpecReplicas.Object, "spec", "replicas")
430 if err := storage.CustomResource.Storage.Create(ctx, key, withoutSpecReplicas, &cr, 0, false); err != nil {
431 t.Fatalf("error setting new custom resource (key: %s) %v: %v", key, withoutSpecReplicas, err)
432 }
433
434 _, err := storage.Scale.Get(ctx, name, &metav1.GetOptions{})
435 if err == nil {
436 t.Fatalf("error expected for %s", name)
437 }
438 if expected := `the spec replicas field ".spec.replicas" does not exist`; !strings.Contains(err.Error(), expected) {
439 t.Fatalf("expected error string %q, got: %v", expected, err)
440 }
441 }
442
443 func TestScaleUpdate(t *testing.T) {
444 storage, server := newStorage(t)
445 defer server.Terminate(t)
446 defer storage.CustomResource.Store.DestroyFunc()
447
448 name := "foo"
449
450 var cr unstructured.Unstructured
451 ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
452 key := "/noxus/" + metav1.NamespaceDefault + "/" + name
453 if err := storage.CustomResource.Storage.Create(ctx, key, &validCustomResource, &cr, 0, false); err != nil {
454 t.Fatalf("error setting new custom resource (key: %s) %v: %v", key, validCustomResource, err)
455 }
456
457 obj, err := storage.Scale.Get(ctx, name, &metav1.GetOptions{})
458 if err != nil {
459 t.Fatalf("error fetching scale for %s: %v", name, err)
460 }
461 scale, ok := obj.(*autoscalingv1.Scale)
462 if !ok {
463 t.Fatalf("%v is not of the type autoscalingv1.Scale", scale)
464 }
465
466 replicas := 12
467 update := autoscalingv1.Scale{
468 ObjectMeta: scale.ObjectMeta,
469 Spec: autoscalingv1.ScaleSpec{
470 Replicas: int32(replicas),
471 },
472 }
473
474 if _, _, err := storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}); err != nil {
475 t.Fatalf("error updating scale %v: %v", update, err)
476 }
477
478 obj, err = storage.Scale.Get(ctx, name, &metav1.GetOptions{})
479 if err != nil {
480 t.Fatalf("error fetching scale for %s: %v", name, err)
481 }
482 scale = obj.(*autoscalingv1.Scale)
483 if scale.Spec.Replicas != int32(replicas) {
484 t.Errorf("wrong replicas count: expected: %d got: %d", replicas, scale.Spec.Replicas)
485 }
486
487 update.ResourceVersion = scale.ResourceVersion
488 update.Spec.Replicas = 15
489
490 if _, _, err = storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}); err != nil && !errors.IsConflict(err) {
491 t.Fatalf("unexpected error, expecting an update conflict but got %v", err)
492 }
493 }
494
495 func TestScaleUpdateWithoutSpecReplicas(t *testing.T) {
496 storage, server := newStorage(t)
497 defer server.Terminate(t)
498 defer storage.CustomResource.Store.DestroyFunc()
499
500 name := "foo"
501
502 var cr unstructured.Unstructured
503 ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
504 key := "/noxus/" + metav1.NamespaceDefault + "/" + name
505 withoutSpecReplicas := validCustomResource.DeepCopy()
506 unstructured.RemoveNestedField(withoutSpecReplicas.Object, "spec", "replicas")
507 if err := storage.CustomResource.Storage.Create(ctx, key, withoutSpecReplicas, &cr, 0, false); err != nil {
508 t.Fatalf("error setting new custom resource (key: %s) %v: %v", key, withoutSpecReplicas, err)
509 }
510
511 replicas := 12
512 update := autoscalingv1.Scale{
513 ObjectMeta: metav1.ObjectMeta{
514 Name: name,
515 ResourceVersion: cr.GetResourceVersion(),
516 },
517 Spec: autoscalingv1.ScaleSpec{
518 Replicas: int32(replicas),
519 },
520 }
521
522 if _, _, err := storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}); err != nil {
523 t.Fatalf("error updating scale %v: %v", update, err)
524 }
525
526 obj, err := storage.Scale.Get(ctx, name, &metav1.GetOptions{})
527 if err != nil {
528 t.Fatalf("error fetching scale for %s: %v", name, err)
529 }
530 scale := obj.(*autoscalingv1.Scale)
531 if scale.Spec.Replicas != int32(replicas) {
532 t.Errorf("wrong replicas count: expected: %d got: %d", replicas, scale.Spec.Replicas)
533 }
534 }
535
536 func TestScaleUpdateWithoutResourceVersion(t *testing.T) {
537 storage, server := newStorage(t)
538 defer server.Terminate(t)
539 defer storage.CustomResource.Store.DestroyFunc()
540
541 name := "foo"
542
543 var cr unstructured.Unstructured
544 ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
545 key := "/noxus/" + metav1.NamespaceDefault + "/" + name
546 if err := storage.CustomResource.Storage.Create(ctx, key, &validCustomResource, &cr, 0, false); err != nil {
547 t.Fatalf("error setting new custom resource (key: %s) %v: %v", key, validCustomResource, err)
548 }
549
550 replicas := int32(8)
551 update := autoscalingv1.Scale{
552 ObjectMeta: metav1.ObjectMeta{
553 Name: name,
554 },
555 Spec: autoscalingv1.ScaleSpec{
556 Replicas: replicas,
557 },
558 }
559
560 if _, _, err := storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}); err != nil {
561 t.Fatalf("error updating scale %v: %v", update, err)
562 }
563
564 obj, err := storage.Scale.Get(ctx, name, &metav1.GetOptions{})
565 if err != nil {
566 t.Fatalf("error fetching scale for %s: %v", name, err)
567 }
568 scale := obj.(*autoscalingv1.Scale)
569 if scale.Spec.Replicas != replicas {
570 t.Errorf("wrong replicas count: expected: %d got: %d", replicas, scale.Spec.Replicas)
571 }
572 }
573
574 func TestScaleUpdateWithoutResourceVersionWithConflicts(t *testing.T) {
575 storage, server := newStorage(t)
576 defer server.Terminate(t)
577 defer storage.CustomResource.Store.DestroyFunc()
578
579 name := "foo"
580
581 var cr unstructured.Unstructured
582 ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
583 key := "/noxus/" + metav1.NamespaceDefault + "/" + name
584 if err := storage.CustomResource.Storage.Create(ctx, key, &validCustomResource, &cr, 0, false); err != nil {
585 t.Fatalf("error setting new custom resource (key: %s) %v: %v", key, validCustomResource, err)
586 }
587
588 fetchObject := func(name string) (*unstructured.Unstructured, error) {
589 gotObj, err := storage.CustomResource.Get(ctx, name, &metav1.GetOptions{})
590 if err != nil {
591 return nil, fmt.Errorf("error fetching custom resource %s: %v", name, err)
592 }
593 return gotObj.(*unstructured.Unstructured), nil
594 }
595
596 applyPatch := func(labelName, labelValue string) rest.TransformFunc {
597 return func(_ context.Context, _, currentObject runtime.Object) (objToUpdate runtime.Object, patchErr error) {
598 o := currentObject.(metav1.Object)
599 o.SetLabels(map[string]string{
600 labelName: labelValue,
601 })
602 return currentObject, nil
603 }
604 }
605
606 errs := make(chan error, 1)
607 rounds := 100
608 go func() {
609
610 labelName := "timestamp"
611 for i := 0; i < rounds; i++ {
612 expectedLabelValue := fmt.Sprint(i)
613 update, err := fetchObject(name)
614 if err != nil {
615 errs <- err
616 return
617 }
618 setNestedField(update, expectedLabelValue, "metadata", "labels", labelName)
619 if _, _, err := storage.CustomResource.Update(ctx, name, rest.DefaultUpdatedObjectInfo(nil, applyPatch(labelName, fmt.Sprint(i))), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}); err != nil {
620
621 errs <- fmt.Errorf("error updating custom resource label: %v", err)
622 return
623 }
624
625 gotObj, err := fetchObject(name)
626 if err != nil {
627 errs <- err
628 return
629 }
630 gotLabelValue, _, err := unstructured.NestedString(gotObj.Object, "metadata", "labels", labelName)
631 if err != nil {
632 errs <- fmt.Errorf("error getting label %s of custom resource %s: %v", labelName, name, err)
633 return
634 }
635 if gotLabelValue != expectedLabelValue {
636 errs <- fmt.Errorf("wrong label value: expected: %s, got: %s", expectedLabelValue, gotLabelValue)
637 return
638 }
639 }
640 }()
641
642 replicas := int32(0)
643 update := autoscalingv1.Scale{
644 ObjectMeta: metav1.ObjectMeta{
645 Name: name,
646 },
647 }
648
649
650 for i := 0; i < rounds; i++ {
651 select {
652 case err := <-errs:
653 t.Fatal(err)
654 default:
655 replicas++
656 update.Spec.Replicas = replicas
657 if _, _, err := storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}); err != nil {
658 t.Fatalf("error updating scale %v: %v", update, err)
659 }
660
661 obj, err := storage.Scale.Get(ctx, name, &metav1.GetOptions{})
662 if err != nil {
663 t.Fatalf("error fetching scale for %s: %v", name, err)
664 }
665 scale := obj.(*autoscalingv1.Scale)
666 if scale.Spec.Replicas != replicas {
667 t.Errorf("wrong replicas count: expected: %d got: %d", replicas, scale.Spec.Replicas)
668 }
669 }
670 }
671 }
672
673 func TestScaleUpdateWithResourceVersionWithConflicts(t *testing.T) {
674 storage, server := newStorage(t)
675 defer server.Terminate(t)
676 defer storage.CustomResource.Store.DestroyFunc()
677
678 name := "foo"
679
680 var cr unstructured.Unstructured
681 ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
682 key := "/noxus/" + metav1.NamespaceDefault + "/" + name
683 if err := storage.CustomResource.Storage.Create(ctx, key, &validCustomResource, &cr, 0, false); err != nil {
684 t.Fatalf("error setting new custom resource (key: %s) %v: %v", key, validCustomResource, err)
685 }
686
687 obj, err := storage.Scale.Get(ctx, name, &metav1.GetOptions{})
688 if err != nil {
689 t.Fatalf("error fetching scale for %s: %v", name, err)
690 }
691 scale, ok := obj.(*autoscalingv1.Scale)
692 if !ok {
693 t.Fatalf("%v is not of the type autoscalingv1.Scale", scale)
694 }
695
696 replicas := int32(12)
697 update := autoscalingv1.Scale{
698 ObjectMeta: scale.ObjectMeta,
699 Spec: autoscalingv1.ScaleSpec{
700 Replicas: replicas,
701 },
702 }
703 update.ResourceVersion = "1"
704
705 _, _, err = storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{})
706 if err == nil {
707 t.Fatal("expecting an update conflict error")
708 }
709 if !errors.IsConflict(err) {
710 t.Fatalf("unexpected error, expecting an update conflict but got %v", err)
711 }
712 }
713
714 func setSpecReplicas(u *unstructured.Unstructured, replicas int64) {
715 setNestedField(u, replicas, "spec", "replicas")
716 }
717
718 func getSpecReplicas(u *unstructured.Unstructured) int64 {
719 val, found, err := unstructured.NestedInt64(u.Object, "spec", "replicas")
720 if !found || err != nil {
721 return 0
722 }
723 return val
724 }
725
726 func setStatusReplicas(u *unstructured.Unstructured, replicas int64) {
727 setNestedField(u, replicas, "status", "replicas")
728 }
729
730 func getStatusReplicas(u *unstructured.Unstructured) int64 {
731 val, found, err := unstructured.NestedInt64(u.Object, "status", "replicas")
732 if !found || err != nil {
733 return 0
734 }
735 return val
736 }
737
738 func setNestedField(u *unstructured.Unstructured, value interface{}, fields ...string) {
739 if u.Object == nil {
740 u.Object = make(map[string]interface{})
741 }
742 unstructured.SetNestedField(u.Object, value, fields...)
743 }
744
View as plain text