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