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