1/*
2Copyright 2015 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package test
18
19import (
20	"fmt"
21	"reflect"
22	"strconv"
23	"strings"
24	"testing"
25	"time"
26
27	apitesting "k8s.io/apimachinery/pkg/api/apitesting"
28	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
30	"k8s.io/apimachinery/pkg/apis/testapigroup"
31	"k8s.io/apimachinery/pkg/runtime"
32	"k8s.io/apimachinery/pkg/runtime/schema"
33	"k8s.io/apimachinery/pkg/types"
34)
35
36func TestDecodeUnstructured(t *testing.T) {
37	groupVersionString := "v1"
38	rawJson := fmt.Sprintf(`{"kind":"Pod","apiVersion":"%s","metadata":{"name":"test"}}`, groupVersionString)
39	pl := &List{
40		Items: []runtime.Object{
41			&testapigroup.Carp{ObjectMeta: metav1.ObjectMeta{Name: "1"}},
42			&runtime.Unknown{
43				TypeMeta:    runtime.TypeMeta{Kind: "Pod", APIVersion: groupVersionString},
44				Raw:         []byte(rawJson),
45				ContentType: runtime.ContentTypeJSON,
46			},
47			&runtime.Unknown{
48				TypeMeta:    runtime.TypeMeta{Kind: "", APIVersion: groupVersionString},
49				Raw:         []byte(rawJson),
50				ContentType: runtime.ContentTypeJSON,
51			},
52			&unstructured.Unstructured{
53				Object: map[string]interface{}{
54					"kind":       "Foo",
55					"apiVersion": "Bar",
56					"test":       "value",
57				},
58			},
59		},
60	}
61	if errs := runtime.DecodeList(pl.Items, unstructured.UnstructuredJSONScheme); len(errs) == 1 {
62		t.Fatalf("unexpected error %v", errs)
63	}
64	if pod, ok := pl.Items[1].(*unstructured.Unstructured); !ok || pod.Object["kind"] != "Pod" || pod.Object["metadata"].(map[string]interface{})["name"] != "test" {
65		t.Errorf("object not converted: %#v", pl.Items[1])
66	}
67	if pod, ok := pl.Items[2].(*unstructured.Unstructured); !ok || pod.Object["kind"] != "Pod" || pod.Object["metadata"].(map[string]interface{})["name"] != "test" {
68		t.Errorf("object not converted: %#v", pl.Items[2])
69	}
70}
71
72func TestDecode(t *testing.T) {
73	tcs := []struct {
74		json []byte
75		want runtime.Object
76	}{
77		{
78			json: []byte(`{"apiVersion": "test", "kind": "test_kind"}`),
79			want: &unstructured.Unstructured{
80				Object: map[string]interface{}{"apiVersion": "test", "kind": "test_kind"},
81			},
82		},
83		{
84			json: []byte(`{"apiVersion": "test", "kind": "test_list", "items": []}`),
85			want: &unstructured.UnstructuredList{
86				Object: map[string]interface{}{"apiVersion": "test", "kind": "test_list"},
87				Items:  []unstructured.Unstructured{},
88			},
89		},
90		{
91			json: []byte(`{"items": [{"metadata": {"name": "object1", "deletionGracePeriodSeconds": 10}, "apiVersion": "test", "kind": "test_kind"}, {"metadata": {"name": "object2"}, "apiVersion": "test", "kind": "test_kind"}], "apiVersion": "test", "kind": "test_list"}`),
92			want: &unstructured.UnstructuredList{
93				Object: map[string]interface{}{"apiVersion": "test", "kind": "test_list"},
94				Items: []unstructured.Unstructured{
95					{
96						Object: map[string]interface{}{
97							"metadata":   map[string]interface{}{"name": "object1", "deletionGracePeriodSeconds": int64(10)},
98							"apiVersion": "test",
99							"kind":       "test_kind",
100						},
101					},
102					{
103						Object: map[string]interface{}{
104							"metadata":   map[string]interface{}{"name": "object2"},
105							"apiVersion": "test",
106							"kind":       "test_kind",
107						},
108					},
109				},
110			},
111		},
112	}
113
114	for _, tc := range tcs {
115		got, _, err := unstructured.UnstructuredJSONScheme.Decode(tc.json, nil, nil)
116		if err != nil {
117			t.Errorf("Unexpected error for %q: %v", string(tc.json), err)
118			continue
119		}
120
121		if !reflect.DeepEqual(got, tc.want) {
122			t.Errorf("Decode(%q) want: %v\ngot: %v", string(tc.json), tc.want, got)
123		}
124	}
125}
126
127func TestUnstructuredGetters(t *testing.T) {
128	trueVar := true
129	ten := int64(10)
130	unstruct := unstructured.Unstructured{
131		Object: map[string]interface{}{
132			"kind":       "test_kind",
133			"apiVersion": "test_version",
134			"metadata": map[string]interface{}{
135				"name":                       "test_name",
136				"namespace":                  "test_namespace",
137				"generateName":               "test_generateName",
138				"uid":                        "test_uid",
139				"resourceVersion":            "test_resourceVersion",
140				"generation":                 ten,
141				"deletionGracePeriodSeconds": ten,
142				"selfLink":                   "test_selfLink",
143				"creationTimestamp":          "2009-11-10T23:00:00Z",
144				"deletionTimestamp":          "2010-11-10T23:00:00Z",
145				"labels": map[string]interface{}{
146					"test_label": "test_value",
147				},
148				"annotations": map[string]interface{}{
149					"test_annotation": "test_value",
150				},
151				"ownerReferences": []interface{}{
152					map[string]interface{}{
153						"kind":       "Pod",
154						"name":       "poda",
155						"apiVersion": "v1",
156						"uid":        "1",
157					},
158					map[string]interface{}{
159						"kind":       "Pod",
160						"name":       "podb",
161						"apiVersion": "v1",
162						"uid":        "2",
163						// though these fields are of type *bool, but when
164						// decoded from JSON, they are unmarshalled as bool.
165						"controller":         true,
166						"blockOwnerDeletion": true,
167					},
168				},
169				"finalizers": []interface{}{
170					"finalizer.1",
171					"finalizer.2",
172				},
173				"clusterName": "cluster123",
174			},
175		},
176	}
177
178	if got, want := unstruct.GetAPIVersion(), "test_version"; got != want {
179		t.Errorf("GetAPIVersions() = %s, want %s", got, want)
180	}
181
182	if got, want := unstruct.GetKind(), "test_kind"; got != want {
183		t.Errorf("GetKind() = %s, want %s", got, want)
184	}
185
186	if got, want := unstruct.GetNamespace(), "test_namespace"; got != want {
187		t.Errorf("GetNamespace() = %s, want %s", got, want)
188	}
189
190	if got, want := unstruct.GetName(), "test_name"; got != want {
191		t.Errorf("GetName() = %s, want %s", got, want)
192	}
193
194	if got, want := unstruct.GetGenerateName(), "test_generateName"; got != want {
195		t.Errorf("GetGenerateName() = %s, want %s", got, want)
196	}
197
198	if got, want := unstruct.GetUID(), types.UID("test_uid"); got != want {
199		t.Errorf("GetUID() = %s, want %s", got, want)
200	}
201
202	if got, want := unstruct.GetResourceVersion(), "test_resourceVersion"; got != want {
203		t.Errorf("GetResourceVersion() = %s, want %s", got, want)
204	}
205
206	if got, want := unstruct.GetSelfLink(), "test_selfLink"; got != want {
207		t.Errorf("GetSelfLink() = %s, want %s", got, want)
208	}
209
210	if got, want := unstruct.GetCreationTimestamp(), metav1.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC); !got.Equal(&want) {
211		t.Errorf("GetCreationTimestamp() = %s, want %s", got, want)
212	}
213
214	if got, want := unstruct.GetDeletionTimestamp(), metav1.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC); got == nil || !got.Equal(&want) {
215		t.Errorf("GetDeletionTimestamp() = %s, want %s", got, want)
216	}
217
218	if got, want := unstruct.GetLabels(), map[string]string{"test_label": "test_value"}; !reflect.DeepEqual(got, want) {
219		t.Errorf("GetLabels() = %s, want %s", got, want)
220	}
221
222	if got, want := unstruct.GetAnnotations(), map[string]string{"test_annotation": "test_value"}; !reflect.DeepEqual(got, want) {
223		t.Errorf("GetAnnotations() = %s, want %s", got, want)
224	}
225	refs := unstruct.GetOwnerReferences()
226	expectedOwnerReferences := []metav1.OwnerReference{
227		{
228			Kind:       "Pod",
229			Name:       "poda",
230			APIVersion: "v1",
231			UID:        "1",
232		},
233		{
234			Kind:               "Pod",
235			Name:               "podb",
236			APIVersion:         "v1",
237			UID:                "2",
238			Controller:         &trueVar,
239			BlockOwnerDeletion: &trueVar,
240		},
241	}
242	if got, want := refs, expectedOwnerReferences; !reflect.DeepEqual(got, want) {
243		t.Errorf("GetOwnerReferences()=%v, want %v", got, want)
244	}
245	if got, want := unstruct.GetFinalizers(), []string{"finalizer.1", "finalizer.2"}; !reflect.DeepEqual(got, want) {
246		t.Errorf("GetFinalizers()=%v, want %v", got, want)
247	}
248	if got, want := unstruct.GetClusterName(), "cluster123"; got != want {
249		t.Errorf("GetClusterName()=%v, want %v", got, want)
250	}
251	if got, want := unstruct.GetDeletionGracePeriodSeconds(), &ten; !reflect.DeepEqual(got, want) {
252		t.Errorf("GetDeletionGracePeriodSeconds()=%v, want %v", got, want)
253	}
254	if got, want := unstruct.GetGeneration(), ten; !reflect.DeepEqual(got, want) {
255		t.Errorf("GetGeneration()=%v, want %v", got, want)
256	}
257}
258
259func TestUnstructuredSetters(t *testing.T) {
260	unstruct := unstructured.Unstructured{}
261	trueVar := true
262	ten := int64(10)
263
264	want := unstructured.Unstructured{
265		Object: map[string]interface{}{
266			"kind":       "test_kind",
267			"apiVersion": "test_version",
268			"metadata": map[string]interface{}{
269				"name":                       "test_name",
270				"namespace":                  "test_namespace",
271				"generateName":               "test_generateName",
272				"uid":                        "test_uid",
273				"resourceVersion":            "test_resourceVersion",
274				"selfLink":                   "test_selfLink",
275				"creationTimestamp":          "2009-11-10T23:00:00Z",
276				"deletionTimestamp":          "2010-11-10T23:00:00Z",
277				"deletionGracePeriodSeconds": ten,
278				"generation":                 ten,
279				"labels": map[string]interface{}{
280					"test_label": "test_value",
281				},
282				"annotations": map[string]interface{}{
283					"test_annotation": "test_value",
284				},
285				"ownerReferences": []interface{}{
286					map[string]interface{}{
287						"kind":       "Pod",
288						"name":       "poda",
289						"apiVersion": "v1",
290						"uid":        "1",
291					},
292					map[string]interface{}{
293						"kind":               "Pod",
294						"name":               "podb",
295						"apiVersion":         "v1",
296						"uid":                "2",
297						"controller":         true,
298						"blockOwnerDeletion": true,
299					},
300				},
301				"finalizers": []interface{}{
302					"finalizer.1",
303					"finalizer.2",
304				},
305				"clusterName": "cluster123",
306			},
307		},
308	}
309
310	unstruct.SetAPIVersion("test_version")
311	unstruct.SetKind("test_kind")
312	unstruct.SetNamespace("test_namespace")
313	unstruct.SetName("test_name")
314	unstruct.SetGenerateName("test_generateName")
315	unstruct.SetUID(types.UID("test_uid"))
316	unstruct.SetResourceVersion("test_resourceVersion")
317	unstruct.SetSelfLink("test_selfLink")
318	unstruct.SetCreationTimestamp(metav1.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC))
319	date := metav1.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC)
320	unstruct.SetDeletionTimestamp(&date)
321	unstruct.SetLabels(map[string]string{"test_label": "test_value"})
322	unstruct.SetAnnotations(map[string]string{"test_annotation": "test_value"})
323	newOwnerReferences := []metav1.OwnerReference{
324		{
325			Kind:       "Pod",
326			Name:       "poda",
327			APIVersion: "v1",
328			UID:        "1",
329		},
330		{
331			Kind:               "Pod",
332			Name:               "podb",
333			APIVersion:         "v1",
334			UID:                "2",
335			Controller:         &trueVar,
336			BlockOwnerDeletion: &trueVar,
337		},
338	}
339	unstruct.SetOwnerReferences(newOwnerReferences)
340	unstruct.SetFinalizers([]string{"finalizer.1", "finalizer.2"})
341	unstruct.SetClusterName("cluster123")
342	unstruct.SetDeletionGracePeriodSeconds(&ten)
343	unstruct.SetGeneration(ten)
344
345	if !reflect.DeepEqual(unstruct, want) {
346		t.Errorf("Wanted: \n%s\n Got:\n%s", want, unstruct)
347	}
348}
349
350func TestOwnerReferences(t *testing.T) {
351	t.Parallel()
352	trueVar := true
353	falseVar := false
354	refs := []metav1.OwnerReference{
355		{
356			APIVersion: "v2",
357			Kind:       "K2",
358			Name:       "n2",
359			UID:        types.UID("abc1"),
360		},
361		{
362			APIVersion:         "v1",
363			Kind:               "K1",
364			Name:               "n1",
365			UID:                types.UID("abc2"),
366			Controller:         &trueVar,
367			BlockOwnerDeletion: &falseVar,
368		},
369		{
370			APIVersion:         "v3",
371			Kind:               "K3",
372			Name:               "n3",
373			UID:                types.UID("abc3"),
374			Controller:         &falseVar,
375			BlockOwnerDeletion: &trueVar,
376		},
377	}
378	for i, ref := range refs {
379		ref := ref
380		t.Run(strconv.Itoa(i), func(t *testing.T) {
381			t.Parallel()
382			u1 := unstructured.Unstructured{
383				Object: make(map[string]interface{}),
384			}
385			refsX := []metav1.OwnerReference{ref}
386			u1.SetOwnerReferences(refsX)
387
388			have := u1.GetOwnerReferences()
389			if !reflect.DeepEqual(have, refsX) {
390				t.Errorf("Object references are not the same: %#v != %#v", have, refsX)
391			}
392		})
393	}
394}
395
396func TestUnstructuredListGetters(t *testing.T) {
397	unstruct := unstructured.UnstructuredList{
398		Object: map[string]interface{}{
399			"kind":       "test_kind",
400			"apiVersion": "test_version",
401			"metadata": map[string]interface{}{
402				"resourceVersion": "test_resourceVersion",
403				"selfLink":        "test_selfLink",
404			},
405		},
406	}
407
408	if got, want := unstruct.GetAPIVersion(), "test_version"; got != want {
409		t.Errorf("GetAPIVersions() = %s, want %s", got, want)
410	}
411
412	if got, want := unstruct.GetKind(), "test_kind"; got != want {
413		t.Errorf("GetKind() = %s, want %s", got, want)
414	}
415
416	if got, want := unstruct.GetResourceVersion(), "test_resourceVersion"; got != want {
417		t.Errorf("GetResourceVersion() = %s, want %s", got, want)
418	}
419
420	if got, want := unstruct.GetSelfLink(), "test_selfLink"; got != want {
421		t.Errorf("GetSelfLink() = %s, want %s", got, want)
422	}
423}
424
425func TestUnstructuredListSetters(t *testing.T) {
426	unstruct := unstructured.UnstructuredList{}
427
428	want := unstructured.UnstructuredList{
429		Object: map[string]interface{}{
430			"kind":       "test_kind",
431			"apiVersion": "test_version",
432			"metadata": map[string]interface{}{
433				"resourceVersion": "test_resourceVersion",
434				"selfLink":        "test_selfLink",
435			},
436		},
437	}
438
439	unstruct.SetAPIVersion("test_version")
440	unstruct.SetKind("test_kind")
441	unstruct.SetResourceVersion("test_resourceVersion")
442	unstruct.SetSelfLink("test_selfLink")
443
444	if !reflect.DeepEqual(unstruct, want) {
445		t.Errorf("Wanted: \n%s\n Got:\n%s", unstruct, want)
446	}
447}
448
449func TestDecodeNumbers(t *testing.T) {
450
451	// Start with a valid pod
452	originalJSON := []byte(`{
453		"kind":"Carp",
454		"apiVersion":"v1",
455		"metadata":{"name":"pod","namespace":"foo"},
456		"spec":{
457			"containers":[{"name":"container","image":"container"}],
458			"activeDeadlineSeconds":1000030003
459		}
460	}`)
461
462	pod := &testapigroup.Carp{}
463
464	_, codecs := TestScheme()
465	codec := apitesting.TestCodec(codecs, schema.GroupVersion{Group: "", Version: runtime.APIVersionInternal})
466
467	err := runtime.DecodeInto(codec, originalJSON, pod)
468	if err != nil {
469		t.Fatalf("unexpected error: %v", err)
470	}
471
472	// Round-trip with unstructured codec
473	unstructuredObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, originalJSON)
474	if err != nil {
475		t.Fatalf("unexpected error: %v", err)
476	}
477	roundtripJSON, err := runtime.Encode(unstructured.UnstructuredJSONScheme, unstructuredObj)
478	if err != nil {
479		t.Fatalf("unexpected error: %v", err)
480	}
481
482	// Make sure we serialize back out in int form
483	if !strings.Contains(string(roundtripJSON), `"activeDeadlineSeconds":1000030003`) {
484		t.Errorf("Expected %s, got %s", `"activeDeadlineSeconds":1000030003`, string(roundtripJSON))
485	}
486
487	// Decode with structured codec again
488	obj2, err := runtime.Decode(codec, roundtripJSON)
489	if err != nil {
490		t.Fatalf("unexpected error: %v", err)
491	}
492	// ensure pod is still valid
493	pod2, ok := obj2.(*testapigroup.Carp)
494	if !ok {
495		t.Fatalf("expected an *api.Pod, got %#v", obj2)
496	}
497
498	// ensure round-trip preserved large integers
499	if !reflect.DeepEqual(pod, pod2) {
500		t.Fatalf("Expected\n\t%#v, got \n\t%#v", pod, pod2)
501	}
502}
503
504// TestAccessorMethods does opaque roundtrip testing against an Unstructured
505// instance's Object methods to ensure that what is "Set" matches what you
506// subsequently "Get" without any assertions against internal state.
507func TestAccessorMethods(t *testing.T) {
508	int64p := func(i int) *int64 {
509		v := int64(i)
510		return &v
511	}
512	tests := []struct {
513		accessor string
514		val      interface{}
515		nilVal   reflect.Value
516	}{
517		{accessor: "Namespace", val: "foo"},
518		{accessor: "Name", val: "bar"},
519		{accessor: "GenerateName", val: "baz"},
520		{accessor: "UID", val: types.UID("uid")},
521		{accessor: "ResourceVersion", val: "1"},
522		{accessor: "Generation", val: int64(5)},
523		{accessor: "SelfLink", val: "/foo"},
524		// TODO: Handle timestamps, which are being marshalled as UTC and
525		// unmarshalled as Local.
526		// https://github.com/kubernetes/kubernetes/issues/21402
527		// {accessor: "CreationTimestamp", val: someTime},
528		// {accessor: "DeletionTimestamp", val: someTimeP},
529		{accessor: "DeletionTimestamp", nilVal: reflect.ValueOf((*metav1.Time)(nil))},
530		{accessor: "DeletionGracePeriodSeconds", val: int64p(10)},
531		{accessor: "DeletionGracePeriodSeconds", val: int64p(0)},
532		{accessor: "DeletionGracePeriodSeconds", nilVal: reflect.ValueOf((*int64)(nil))},
533		{accessor: "Labels", val: map[string]string{"foo": "bar"}},
534		{accessor: "Annotations", val: map[string]string{"foo": "bar"}},
535		{accessor: "Finalizers", val: []string{"foo"}},
536		{accessor: "OwnerReferences", val: []metav1.OwnerReference{{Name: "foo"}}},
537		{accessor: "ClusterName", val: "foo"},
538	}
539	for i, test := range tests {
540		t.Logf("evaluating test %d (%s)", i, test.accessor)
541
542		u := &unstructured.Unstructured{}
543		setter := reflect.ValueOf(u).MethodByName("Set" + test.accessor)
544		getter := reflect.ValueOf(u).MethodByName("Get" + test.accessor)
545
546		args := []reflect.Value{}
547		if test.val != nil {
548			args = append(args, reflect.ValueOf(test.val))
549		} else {
550			args = append(args, test.nilVal)
551		}
552		setter.Call(args)
553
554		ret := getter.Call([]reflect.Value{})
555		actual := ret[0].Interface()
556
557		var expected interface{}
558		if test.val != nil {
559			expected = test.val
560		} else {
561			expected = test.nilVal.Interface()
562		}
563
564		if e, a := expected, actual; !reflect.DeepEqual(e, a) {
565			t.Fatalf("%s: expected %v (%T), got %v (%T)", test.accessor, e, e, a, a)
566		}
567	}
568}
569