1/*
2Copyright 2018 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 unstructured_test
18
19import (
20	"math/rand"
21	"reflect"
22	"testing"
23
24	"github.com/stretchr/testify/assert"
25	"k8s.io/apimachinery/pkg/api/apitesting/fuzzer"
26	"k8s.io/apimachinery/pkg/api/equality"
27	metafuzzer "k8s.io/apimachinery/pkg/apis/meta/fuzzer"
28	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
30	"k8s.io/apimachinery/pkg/runtime"
31	"k8s.io/apimachinery/pkg/runtime/serializer"
32	"k8s.io/apimachinery/pkg/util/diff"
33)
34
35func TestNilUnstructuredContent(t *testing.T) {
36	var u unstructured.Unstructured
37	uCopy := u.DeepCopy()
38	content := u.UnstructuredContent()
39	expContent := make(map[string]interface{})
40	assert.EqualValues(t, expContent, content)
41	assert.Equal(t, uCopy, &u)
42}
43
44// TestUnstructuredMetadataRoundTrip checks that metadata accessors
45// correctly set the metadata for unstructured objects.
46// First, it fuzzes an empty ObjectMeta and sets this value as the metadata for an unstructured object.
47// Next, it uses metadata accessor methods to set these fuzzed values to another unstructured object.
48// Finally, it checks that both the unstructured objects are equal.
49func TestUnstructuredMetadataRoundTrip(t *testing.T) {
50	scheme := runtime.NewScheme()
51	codecs := serializer.NewCodecFactory(scheme)
52	seed := rand.Int63()
53	fuzzer := fuzzer.FuzzerFor(metafuzzer.Funcs, rand.NewSource(seed), codecs)
54
55	N := 1000
56	for i := 0; i < N; i++ {
57		u := &unstructured.Unstructured{Object: map[string]interface{}{}}
58		uCopy := u.DeepCopy()
59		metadata := &metav1.ObjectMeta{}
60		fuzzer.Fuzz(metadata)
61
62		if err := setObjectMeta(u, metadata); err != nil {
63			t.Fatalf("unexpected error setting fuzzed ObjectMeta: %v", err)
64		}
65		setObjectMetaUsingAccessors(u, uCopy)
66
67		// TODO: remove this special casing when creationTimestamp becomes a pointer.
68		// Right now, creationTimestamp is a struct (metav1.Time) so omitempty holds no meaning for it.
69		// However, the current behaviour is to remove the field if it holds an empty struct.
70		// This special casing exists here because custom marshallers for metav1.Time marshal
71		// an empty value to "null", which gets converted to nil when converting to an unstructured map by "ToUnstructured".
72		if err := unstructured.SetNestedField(uCopy.UnstructuredContent(), nil, "metadata", "creationTimestamp"); err != nil {
73			t.Fatalf("unexpected error setting creationTimestamp as nil: %v", err)
74		}
75
76		if !equality.Semantic.DeepEqual(u, uCopy) {
77			t.Errorf("diff: %v", diff.ObjectReflectDiff(u, uCopy))
78		}
79	}
80}
81
82// TestUnstructuredMetadataOmitempty checks that ObjectMeta omitempty
83// semantics are enforced for unstructured objects.
84// The fuzzing test above should catch these cases but this is here just to be safe.
85// Example: the metadata.clusterName field has the omitempty json tag
86// so if it is set to it's zero value (""), it should be removed from the metadata map.
87func TestUnstructuredMetadataOmitempty(t *testing.T) {
88	scheme := runtime.NewScheme()
89	codecs := serializer.NewCodecFactory(scheme)
90	seed := rand.Int63()
91	fuzzer := fuzzer.FuzzerFor(metafuzzer.Funcs, rand.NewSource(seed), codecs)
92
93	// fuzz to make sure we don't miss any function calls below
94	u := &unstructured.Unstructured{Object: map[string]interface{}{}}
95	metadata := &metav1.ObjectMeta{}
96	fuzzer.Fuzz(metadata)
97	if err := setObjectMeta(u, metadata); err != nil {
98		t.Fatalf("unexpected error setting fuzzed ObjectMeta: %v", err)
99	}
100
101	// set zero values for all fields in metadata explicitly
102	// to check that omitempty fields having zero values are never set
103	u.SetName("")
104	u.SetGenerateName("")
105	u.SetNamespace("")
106	u.SetSelfLink("")
107	u.SetUID("")
108	u.SetResourceVersion("")
109	u.SetGeneration(0)
110	u.SetCreationTimestamp(metav1.Time{})
111	u.SetDeletionTimestamp(nil)
112	u.SetDeletionGracePeriodSeconds(nil)
113	u.SetLabels(nil)
114	u.SetAnnotations(nil)
115	u.SetOwnerReferences(nil)
116	u.SetFinalizers(nil)
117	u.SetClusterName("")
118	u.SetManagedFields(nil)
119
120	gotMetadata, _, err := unstructured.NestedFieldNoCopy(u.UnstructuredContent(), "metadata")
121	if err != nil {
122		t.Error(err)
123	}
124	emptyMetadata := make(map[string]interface{})
125
126	if !reflect.DeepEqual(gotMetadata, emptyMetadata) {
127		t.Errorf("expected %v, got %v", emptyMetadata, gotMetadata)
128	}
129}
130
131func setObjectMeta(u *unstructured.Unstructured, objectMeta *metav1.ObjectMeta) error {
132	if objectMeta == nil {
133		unstructured.RemoveNestedField(u.UnstructuredContent(), "metadata")
134		return nil
135	}
136	metadata, err := runtime.DefaultUnstructuredConverter.ToUnstructured(objectMeta)
137	if err != nil {
138		return err
139	}
140	u.UnstructuredContent()["metadata"] = metadata
141	return nil
142}
143
144func setObjectMetaUsingAccessors(u, uCopy *unstructured.Unstructured) {
145	uCopy.SetName(u.GetName())
146	uCopy.SetGenerateName(u.GetGenerateName())
147	uCopy.SetNamespace(u.GetNamespace())
148	uCopy.SetSelfLink(u.GetSelfLink())
149	uCopy.SetUID(u.GetUID())
150	uCopy.SetResourceVersion(u.GetResourceVersion())
151	uCopy.SetGeneration(u.GetGeneration())
152	uCopy.SetCreationTimestamp(u.GetCreationTimestamp())
153	uCopy.SetDeletionTimestamp(u.GetDeletionTimestamp())
154	uCopy.SetDeletionGracePeriodSeconds(u.GetDeletionGracePeriodSeconds())
155	uCopy.SetLabels(u.GetLabels())
156	uCopy.SetAnnotations(u.GetAnnotations())
157	uCopy.SetOwnerReferences(u.GetOwnerReferences())
158	uCopy.SetFinalizers(u.GetFinalizers())
159	uCopy.SetClusterName(u.GetClusterName())
160	uCopy.SetManagedFields(u.GetManagedFields())
161}
162