...

Source file src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta/coerce_test.go

Documentation: k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package objectmeta
    18  
    19  import (
    20  	"math/rand"
    21  	"reflect"
    22  	"testing"
    23  
    24  	"github.com/google/go-cmp/cmp"
    25  	corev1 "k8s.io/api/core/v1"
    26  	"k8s.io/apimachinery/pkg/api/apitesting/fuzzer"
    27  	"k8s.io/apimachinery/pkg/api/equality"
    28  	metafuzzer "k8s.io/apimachinery/pkg/apis/meta/fuzzer"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    31  	"k8s.io/apimachinery/pkg/runtime"
    32  	"k8s.io/apimachinery/pkg/runtime/serializer"
    33  	"k8s.io/apimachinery/pkg/runtime/serializer/json"
    34  	utiljson "k8s.io/apimachinery/pkg/util/json"
    35  	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
    36  )
    37  
    38  func TestRoundtripObjectMeta(t *testing.T) {
    39  	scheme := runtime.NewScheme()
    40  	codecs := serializer.NewCodecFactory(scheme)
    41  	codec := json.NewSerializer(json.DefaultMetaFactory, scheme, scheme, false)
    42  	seed := rand.Int63()
    43  	fuzzer := fuzzer.FuzzerFor(metafuzzer.Funcs, rand.NewSource(seed), codecs)
    44  
    45  	N := 1000
    46  	for i := 0; i < N; i++ {
    47  		u := &unstructured.Unstructured{Object: map[string]interface{}{}}
    48  		original := &metav1.ObjectMeta{}
    49  		fuzzer.Fuzz(original)
    50  		if err := SetObjectMeta(u.Object, original); err != nil {
    51  			t.Fatalf("unexpected error setting ObjectMeta: %v", err)
    52  		}
    53  		o, _, err := GetObjectMeta(u.Object, false)
    54  		if err != nil {
    55  			t.Fatalf("unexpected error getting the Objectmeta: %v", err)
    56  		}
    57  
    58  		if !equality.Semantic.DeepEqual(original, o) {
    59  			t.Errorf("diff: %v\nCodec: %#v", cmp.Diff(original, o), codec)
    60  		}
    61  	}
    62  }
    63  
    64  // TestMalformedObjectMetaFields sets a number of different random values and types for all
    65  // metadata fields. If json.Unmarshal accepts them, compare that getObjectMeta
    66  // gives the same result. Otherwise, drop malformed fields.
    67  func TestMalformedObjectMetaFields(t *testing.T) {
    68  	fuzzer := fuzzer.FuzzerFor(metafuzzer.Funcs, rand.NewSource(rand.Int63()), serializer.NewCodecFactory(runtime.NewScheme()))
    69  	spuriousValues := func() []interface{} {
    70  		return []interface{}{
    71  			// primitives
    72  			nil,
    73  			int64(1),
    74  			float64(1.5),
    75  			true,
    76  			"a",
    77  			// well-formed complex values
    78  			[]interface{}{"a", "b"},
    79  			map[string]interface{}{"a": "1", "b": "2"},
    80  			[]interface{}{int64(1), int64(2)},
    81  			[]interface{}{float64(1.5), float64(2.5)},
    82  			// known things json decoding tolerates
    83  			map[string]interface{}{"a": "1", "b": nil},
    84  			// malformed things
    85  			map[string]interface{}{"a": "1", "b": []interface{}{"nested"}},
    86  			[]interface{}{"a", int64(1), float64(1.5), true, []interface{}{"nested"}},
    87  		}
    88  	}
    89  	N := 100
    90  	for i := 0; i < N; i++ {
    91  		fuzzedObjectMeta := &metav1.ObjectMeta{}
    92  		fuzzer.Fuzz(fuzzedObjectMeta)
    93  		goodMetaMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(fuzzedObjectMeta.DeepCopy())
    94  		if err != nil {
    95  			t.Fatal(err)
    96  		}
    97  		for _, pth := range jsonPaths(nil, goodMetaMap) {
    98  			for _, v := range spuriousValues() {
    99  				// skip values of same type, because they can only cause decoding errors further insides
   100  				orig, err := JSONPathValue(goodMetaMap, pth, 0)
   101  				if err != nil {
   102  					t.Fatalf("unexpected to not find something at %v: %v", pth, err)
   103  				}
   104  				if reflect.TypeOf(v) == reflect.TypeOf(orig) {
   105  					continue
   106  				}
   107  
   108  				// make a spurious map
   109  				spuriousMetaMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(fuzzedObjectMeta.DeepCopy())
   110  				if err != nil {
   111  					t.Fatal(err)
   112  				}
   113  				if err := SetJSONPath(spuriousMetaMap, pth, 0, v); err != nil {
   114  					t.Fatal(err)
   115  				}
   116  
   117  				// See if it can unmarshal to object meta
   118  				spuriousJSON, err := utiljson.Marshal(spuriousMetaMap)
   119  				if err != nil {
   120  					t.Fatalf("error on %v=%#v: %v", pth, v, err)
   121  				}
   122  				expectedObjectMeta := &metav1.ObjectMeta{}
   123  				if err := utiljson.Unmarshal(spuriousJSON, expectedObjectMeta); err != nil {
   124  					// if standard json unmarshal would fail decoding this field, drop the field entirely
   125  					truncatedMetaMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(fuzzedObjectMeta.DeepCopy())
   126  					if err != nil {
   127  						t.Fatal(err)
   128  					}
   129  
   130  					// we expect this logic for the different fields:
   131  					switch {
   132  					default:
   133  						// delete complete top-level field by default
   134  						DeleteJSONPath(truncatedMetaMap, pth[:1], 0)
   135  					}
   136  
   137  					truncatedJSON, err := utiljson.Marshal(truncatedMetaMap)
   138  					if err != nil {
   139  						t.Fatalf("error on %v=%#v: %v", pth, v, err)
   140  					}
   141  					expectedObjectMeta = &metav1.ObjectMeta{}
   142  					if err := utiljson.Unmarshal(truncatedJSON, expectedObjectMeta); err != nil {
   143  						t.Fatalf("error on %v=%#v: %v", pth, v, err)
   144  					}
   145  				}
   146  
   147  				// make sure dropInvalidTypedFields+getObjectMeta matches what we expect
   148  				u := &unstructured.Unstructured{Object: map[string]interface{}{"metadata": spuriousMetaMap}}
   149  				actualObjectMeta, _, err := GetObjectMeta(u.Object, true)
   150  				if err != nil {
   151  					t.Errorf("got unexpected error after dropping invalid typed fields on %v=%#v: %v", pth, v, err)
   152  					continue
   153  				}
   154  
   155  				if !equality.Semantic.DeepEqual(expectedObjectMeta, actualObjectMeta) {
   156  					t.Errorf("%v=%#v, diff: %v\n", pth, v, cmp.Diff(expectedObjectMeta, actualObjectMeta))
   157  					t.Errorf("expectedObjectMeta %#v", expectedObjectMeta)
   158  				}
   159  			}
   160  		}
   161  	}
   162  }
   163  
   164  func TestGetObjectMetaWithOptions(t *testing.T) {
   165  	unknownAndMalformed := map[string]interface{}{
   166  		"kind":       "Pod",
   167  		"apiVersion": "v1",
   168  		"metadata": map[string]interface{}{
   169  			"name":         "my-meta",
   170  			"unknownField": "foo",
   171  			"generateName": nil,
   172  			"generation":   nil,
   173  			"labels": map[string]string{
   174  				"foo": "bar",
   175  			},
   176  			"annotations": 11,
   177  		},
   178  	}
   179  
   180  	unknownOnly := map[string]interface{}{
   181  		"kind":       "Pod",
   182  		"apiVersion": "v1",
   183  		"metadata": map[string]interface{}{
   184  			"name":         "my-meta",
   185  			"unknownField": "foo",
   186  			"generateName": nil,
   187  			"generation":   nil,
   188  			"labels": map[string]string{
   189  				"foo": "bar",
   190  			},
   191  		},
   192  	}
   193  
   194  	malformedOnly := map[string]interface{}{
   195  		"kind":       "Pod",
   196  		"apiVersion": "v1",
   197  		"metadata": map[string]interface{}{
   198  			"name":         "my-meta",
   199  			"generateName": nil,
   200  			"generation":   nil,
   201  			"labels": map[string]string{
   202  				"foo": "bar",
   203  			},
   204  			"annotations": 11,
   205  		},
   206  	}
   207  
   208  	var testcases = []struct {
   209  		obj                     map[string]interface{}
   210  		dropMalformedFields     bool
   211  		returnUnknownFieldPaths bool
   212  		expectedObject          *metav1.ObjectMeta
   213  		expectedUnknownPaths    []string
   214  		expectedErr             string
   215  	}{
   216  		{
   217  			obj:                     unknownAndMalformed,
   218  			dropMalformedFields:     false,
   219  			returnUnknownFieldPaths: false,
   220  			expectedErr:             "json: cannot unmarshal number into Go struct field ObjectMeta.annotations of type map[string]string",
   221  		},
   222  		{
   223  			obj:                     unknownAndMalformed,
   224  			dropMalformedFields:     true,
   225  			returnUnknownFieldPaths: false,
   226  			expectedObject: &metav1.ObjectMeta{
   227  				Name: "my-meta",
   228  				Labels: map[string]string{
   229  					"foo": "bar",
   230  				},
   231  			},
   232  		},
   233  		{
   234  			obj:                     unknownAndMalformed,
   235  			dropMalformedFields:     false,
   236  			returnUnknownFieldPaths: true,
   237  			expectedErr:             "json: cannot unmarshal number into Go struct field ObjectMeta.annotations of type map[string]string",
   238  		},
   239  		{
   240  			obj:                     unknownAndMalformed,
   241  			dropMalformedFields:     true,
   242  			returnUnknownFieldPaths: true,
   243  			expectedObject: &metav1.ObjectMeta{
   244  				Name: "my-meta",
   245  				Labels: map[string]string{
   246  					"foo": "bar",
   247  				},
   248  			},
   249  			expectedUnknownPaths: []string{"metadata.unknownField"},
   250  		},
   251  
   252  		{
   253  			obj:                     unknownOnly,
   254  			dropMalformedFields:     false,
   255  			returnUnknownFieldPaths: false,
   256  			expectedObject: &metav1.ObjectMeta{
   257  				Name: "my-meta",
   258  				Labels: map[string]string{
   259  					"foo": "bar",
   260  				},
   261  			},
   262  		},
   263  		{
   264  			obj:                     unknownOnly,
   265  			dropMalformedFields:     true,
   266  			returnUnknownFieldPaths: false,
   267  			expectedObject: &metav1.ObjectMeta{
   268  				Name: "my-meta",
   269  				Labels: map[string]string{
   270  					"foo": "bar",
   271  				},
   272  			},
   273  		},
   274  		{
   275  			obj:                     unknownOnly,
   276  			dropMalformedFields:     false,
   277  			returnUnknownFieldPaths: true,
   278  			expectedObject: &metav1.ObjectMeta{
   279  				Name: "my-meta",
   280  				Labels: map[string]string{
   281  					"foo": "bar",
   282  				},
   283  			},
   284  			expectedUnknownPaths: []string{"metadata.unknownField"},
   285  		},
   286  		{
   287  			obj:                     unknownOnly,
   288  			dropMalformedFields:     true,
   289  			returnUnknownFieldPaths: true,
   290  			expectedObject: &metav1.ObjectMeta{
   291  				Name: "my-meta",
   292  				Labels: map[string]string{
   293  					"foo": "bar",
   294  				},
   295  			},
   296  			expectedUnknownPaths: []string{"metadata.unknownField"},
   297  		},
   298  
   299  		{
   300  			obj:                     malformedOnly,
   301  			dropMalformedFields:     false,
   302  			returnUnknownFieldPaths: false,
   303  			expectedErr:             "json: cannot unmarshal number into Go struct field ObjectMeta.annotations of type map[string]string",
   304  		},
   305  		{
   306  			obj:                     malformedOnly,
   307  			dropMalformedFields:     true,
   308  			returnUnknownFieldPaths: false,
   309  			expectedObject: &metav1.ObjectMeta{
   310  				Name: "my-meta",
   311  				Labels: map[string]string{
   312  					"foo": "bar",
   313  				},
   314  			},
   315  		},
   316  		{
   317  			obj:                     malformedOnly,
   318  			dropMalformedFields:     false,
   319  			returnUnknownFieldPaths: true,
   320  			expectedErr:             "json: cannot unmarshal number into Go struct field ObjectMeta.annotations of type map[string]string",
   321  		},
   322  		{
   323  			obj:                     malformedOnly,
   324  			dropMalformedFields:     true,
   325  			returnUnknownFieldPaths: true,
   326  			expectedObject: &metav1.ObjectMeta{
   327  				Name: "my-meta",
   328  				Labels: map[string]string{
   329  					"foo": "bar",
   330  				},
   331  			},
   332  		},
   333  	}
   334  	for _, tc := range testcases {
   335  		opts := ObjectMetaOptions{
   336  			ReturnUnknownFieldPaths: tc.returnUnknownFieldPaths,
   337  			DropMalformedFields:     tc.dropMalformedFields,
   338  		}
   339  		obj, _, unknownPaths, err := GetObjectMetaWithOptions(tc.obj, opts)
   340  		if !reflect.DeepEqual(tc.expectedObject, obj) {
   341  			t.Errorf("expected: %v, got: %v", tc.expectedObject, obj)
   342  		}
   343  		if (err == nil && tc.expectedErr != "") || err != nil && (err.Error() != tc.expectedErr) {
   344  			t.Errorf("expected: %v, got: %v", tc.expectedErr, err)
   345  		}
   346  		if !reflect.DeepEqual(tc.expectedUnknownPaths, unknownPaths) {
   347  			t.Errorf("expected: %v, got: %v", tc.expectedUnknownPaths, unknownPaths)
   348  		}
   349  	}
   350  }
   351  
   352  func TestGetObjectMetaNils(t *testing.T) {
   353  	u := &unstructured.Unstructured{
   354  		Object: map[string]interface{}{
   355  			"kind":       "Pod",
   356  			"apiVersion": "v1",
   357  			"metadata": map[string]interface{}{
   358  				"generateName": nil,
   359  				"generation":   nil,
   360  				"labels": map[string]interface{}{
   361  					"foo": nil,
   362  				},
   363  			},
   364  		},
   365  	}
   366  
   367  	o, _, err := GetObjectMeta(u.Object, true)
   368  	if err != nil {
   369  		t.Fatal(err)
   370  	}
   371  	if o.GenerateName != "" {
   372  		t.Errorf("expected null json generateName value to be read as \"\" string, but got: %q", o.GenerateName)
   373  	}
   374  	if o.Generation != 0 {
   375  		t.Errorf("expected null json generation value to be read as zero, but got: %q", o.Generation)
   376  	}
   377  	if got, expected := o.Labels, map[string]string{"foo": ""}; !reflect.DeepEqual(got, expected) {
   378  		t.Errorf("unexpected labels, expected=%#v, got=%#v", expected, got)
   379  	}
   380  
   381  	// double check this what the kube JSON decode is doing
   382  	bs, _ := utiljson.Marshal(u.UnstructuredContent())
   383  	kubeObj, _, err := clientgoscheme.Codecs.UniversalDecoder(corev1.SchemeGroupVersion).Decode(bs, nil, nil)
   384  	if err != nil {
   385  		t.Fatal(err)
   386  	}
   387  	pod, ok := kubeObj.(*corev1.Pod)
   388  	if !ok {
   389  		t.Fatalf("expected v1 Pod, got: %T", pod)
   390  	}
   391  	if got, expected := o.GenerateName, pod.ObjectMeta.GenerateName; got != expected {
   392  		t.Errorf("expected generatedName to be %q, got %q", expected, got)
   393  	}
   394  	if got, expected := o.Generation, pod.ObjectMeta.Generation; got != expected {
   395  		t.Errorf("expected generation to be %q, got %q", expected, got)
   396  	}
   397  	if got, expected := o.Labels, pod.ObjectMeta.Labels; !reflect.DeepEqual(got, expected) {
   398  		t.Errorf("expected labels to be %v, got %v", expected, got)
   399  	}
   400  }
   401  
   402  func TestGetObjectMeta(t *testing.T) {
   403  	for i := 0; i < 100; i++ {
   404  		u := &unstructured.Unstructured{Object: map[string]interface{}{
   405  			"metadata": map[string]interface{}{
   406  				"name": "good",
   407  				"Name": "bad1",
   408  				"nAme": "bad2",
   409  				"naMe": "bad3",
   410  				"namE": "bad4",
   411  
   412  				"namespace": "good",
   413  				"Namespace": "bad1",
   414  				"nAmespace": "bad2",
   415  				"naMespace": "bad3",
   416  				"namEspace": "bad4",
   417  
   418  				"creationTimestamp": "a",
   419  			},
   420  		}}
   421  
   422  		meta, _, err := GetObjectMeta(u.Object, true)
   423  		if err != nil {
   424  			t.Fatal(err)
   425  		}
   426  		if meta.Name != "good" || meta.Namespace != "good" {
   427  			t.Fatalf("got %#v", meta)
   428  		}
   429  	}
   430  }
   431  

View as plain text