1
16
17 package unstructured_test
18
19 import (
20 "fmt"
21 "reflect"
22 "strings"
23 "testing"
24 "time"
25
26 "github.com/stretchr/testify/assert"
27
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 testapigroupv1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1"
32 "k8s.io/apimachinery/pkg/runtime"
33 "k8s.io/apimachinery/pkg/runtime/schema"
34 "k8s.io/apimachinery/pkg/test"
35 )
36
37 func TestObjectToUnstructuredConversion(t *testing.T) {
38 scheme, _ := test.TestScheme()
39 testCases := []struct {
40 name string
41 objectToConvert runtime.Object
42 expectedErr error
43 expectedConvertedUnstructured *unstructured.Unstructured
44 }{
45 {
46 name: "convert nil object to unstructured should fail",
47 objectToConvert: nil,
48 expectedErr: fmt.Errorf("unable to convert object type <nil> to Unstructured, must be a runtime.Object"),
49 expectedConvertedUnstructured: &unstructured.Unstructured{},
50 },
51 {
52 name: "convert versioned empty object to unstructured should work",
53 objectToConvert: &testapigroupv1.Carp{},
54 expectedConvertedUnstructured: &unstructured.Unstructured{
55 Object: map[string]interface{}{
56 "apiVersion": "v1",
57 "kind": "Carp",
58 "metadata": map[string]interface{}{
59 "creationTimestamp": nil,
60 },
61 "spec": map[string]interface{}{},
62 "status": map[string]interface{}{},
63 },
64 },
65 },
66 {
67 name: "convert valid versioned object to unstructured should work",
68 objectToConvert: &testapigroupv1.Carp{
69 ObjectMeta: metav1.ObjectMeta{
70 Name: "noxu",
71 },
72 Spec: testapigroupv1.CarpSpec{
73 Hostname: "example.com",
74 },
75 },
76 expectedConvertedUnstructured: &unstructured.Unstructured{
77 Object: map[string]interface{}{
78 "apiVersion": "v1",
79 "kind": "Carp",
80 "metadata": map[string]interface{}{
81 "creationTimestamp": nil,
82 "name": "noxu",
83 },
84 "spec": map[string]interface{}{
85 "hostname": "example.com",
86 },
87 "status": map[string]interface{}{},
88 },
89 },
90 },
91 {
92 name: "convert hub-versioned object to unstructured should fail",
93 objectToConvert: &testapigroup.Carp{},
94 expectedErr: fmt.Errorf("unable to convert the internal object type *testapigroup.Carp to Unstructured without providing a preferred version to convert to"),
95 },
96 }
97 for _, testCase := range testCases {
98 t.Run(testCase.name, func(t *testing.T) {
99 outUnstructured := &unstructured.Unstructured{}
100 err := scheme.Convert(testCase.objectToConvert, outUnstructured, nil)
101 if err != nil {
102 assert.Equal(t, testCase.expectedErr, err)
103 return
104 }
105 assert.Equal(t, testCase.expectedConvertedUnstructured, outUnstructured)
106 })
107 }
108 }
109
110 func TestUnstructuredToObjectConversion(t *testing.T) {
111 scheme, _ := test.TestScheme()
112 testCases := []struct {
113 name string
114 unstructuredToConvert *unstructured.Unstructured
115 convertingObject runtime.Object
116 expectPanic bool
117 expectedErrFunc func(err error) bool
118 expectedConvertedObject runtime.Object
119 }{
120 {
121 name: "convert empty unstructured w/o gvk to versioned object should fail",
122 unstructuredToConvert: &unstructured.Unstructured{
123 Object: map[string]interface{}{},
124 },
125 convertingObject: &testapigroupv1.Carp{},
126 expectedErrFunc: func(err error) bool {
127 return reflect.DeepEqual(err, runtime.NewMissingKindErr("unstructured object has no kind"))
128 },
129 },
130 {
131 name: "convert empty versioned unstructured to versioned object should work",
132 unstructuredToConvert: &unstructured.Unstructured{
133 Object: map[string]interface{}{
134 "apiVersion": "v1",
135 "kind": "Carp",
136 },
137 },
138 convertingObject: &testapigroupv1.Carp{},
139 expectedConvertedObject: &testapigroupv1.Carp{},
140 },
141 {
142 name: "convert empty unstructured w/o gvk to versioned object should fail",
143 unstructuredToConvert: &unstructured.Unstructured{
144 Object: map[string]interface{}{},
145 },
146 convertingObject: &testapigroupv1.Carp{},
147 expectedErrFunc: func(err error) bool {
148 return reflect.DeepEqual(err, runtime.NewMissingKindErr("unstructured object has no kind"))
149 },
150 },
151 {
152 name: "convert valid versioned unstructured to versioned object should work",
153 unstructuredToConvert: &unstructured.Unstructured{
154 Object: map[string]interface{}{
155 "apiVersion": "v1",
156 "kind": "Carp",
157 "metadata": map[string]interface{}{
158 "creationTimestamp": nil,
159 "name": "noxu",
160 },
161 "spec": map[string]interface{}{
162 "hostname": "example.com",
163 },
164 "status": map[string]interface{}{},
165 },
166 },
167 convertingObject: &testapigroupv1.Carp{},
168 expectedConvertedObject: &testapigroupv1.Carp{
169 ObjectMeta: metav1.ObjectMeta{
170 Name: "noxu",
171 },
172 Spec: testapigroupv1.CarpSpec{
173 Hostname: "example.com",
174 },
175 },
176 },
177 {
178 name: "convert valid versioned unstructured to hub-versioned object should work",
179 unstructuredToConvert: &unstructured.Unstructured{
180 Object: map[string]interface{}{
181 "apiVersion": "v1",
182 "kind": "Carp",
183 "metadata": map[string]interface{}{
184 "creationTimestamp": nil,
185 "name": "noxu",
186 },
187 "spec": map[string]interface{}{
188 "hostname": "example.com",
189 },
190 "status": map[string]interface{}{},
191 },
192 },
193 convertingObject: &testapigroup.Carp{},
194 expectedConvertedObject: &testapigroup.Carp{
195 ObjectMeta: metav1.ObjectMeta{
196 Name: "noxu",
197 },
198 Spec: testapigroup.CarpSpec{
199 Hostname: "example.com",
200 },
201 },
202 },
203 {
204 name: "convert unexisting-versioned unstructured to hub-versioned object should fail",
205 unstructuredToConvert: &unstructured.Unstructured{
206 Object: map[string]interface{}{
207 "apiVersion": "v9",
208 "kind": "Carp",
209 "metadata": map[string]interface{}{
210 "creationTimestamp": nil,
211 "name": "noxu",
212 },
213 "spec": map[string]interface{}{
214 "hostname": "example.com",
215 },
216 "status": map[string]interface{}{},
217 },
218 },
219 convertingObject: &testapigroup.Carp{},
220 expectedErrFunc: func(err error) bool {
221 return reflect.DeepEqual(err, runtime.NewNotRegisteredGVKErrForTarget(
222 scheme.Name(),
223 schema.GroupVersionKind{Group: "", Version: "v9", Kind: "Carp"},
224 nil,
225 ))
226 },
227 },
228 {
229 name: "convert valid versioned unstructured to object w/ a mismatching kind should fail",
230 unstructuredToConvert: &unstructured.Unstructured{
231 Object: map[string]interface{}{
232 "apiVersion": "v1",
233 "kind": "Carp",
234 "metadata": map[string]interface{}{
235 "creationTimestamp": nil,
236 "name": "noxu",
237 },
238 "spec": map[string]interface{}{
239 "hostname": "example.com",
240 },
241 "status": map[string]interface{}{},
242 },
243 },
244 convertingObject: &metav1.CreateOptions{},
245 expectedErrFunc: func(err error) bool {
246 return strings.HasPrefix(err.Error(), "converting (v1.Carp) to (v1.CreateOptions):")
247 },
248 },
249 }
250 for _, testCase := range testCases {
251 t.Run(testCase.name, func(t *testing.T) {
252 defer func() {
253 v := recover()
254 assert.Equal(t, testCase.expectPanic, v != nil, "unexpected panic")
255 }()
256 outObject := testCase.convertingObject.DeepCopyObject()
257
258 err := scheme.Convert(testCase.unstructuredToConvert, outObject, nil)
259 if err != nil {
260 if testCase.expectedErrFunc != nil {
261 if !testCase.expectedErrFunc(err) {
262 t.Errorf("error mismatched: %v", err)
263 }
264 }
265 return
266 }
267 assert.Equal(t, testCase.expectedConvertedObject, outObject)
268 })
269 }
270 }
271
272 func TestUnstructuredToGVConversion(t *testing.T) {
273 scheme, _ := test.TestScheme()
274
275 scheme.AddKnownTypes(schema.GroupVersion{Group: "foo", Version: "v1beta1"}, &testapigroup.Carp{})
276 scheme.AddKnownTypes(schema.GroupVersion{Group: "foo", Version: "__internal"}, &testapigroup.Carp{})
277
278 testCases := []struct {
279 name string
280 unstructuredToConvert *unstructured.Unstructured
281 targetGV schema.GroupVersion
282 expectPanic bool
283 expectedErrFunc func(err error) bool
284 expectedConvertedObject runtime.Object
285 }{
286 {
287 name: "convert versioned unstructured to valid external version should work",
288 unstructuredToConvert: &unstructured.Unstructured{
289 Object: map[string]interface{}{
290 "apiVersion": "v1",
291 "kind": "Carp",
292 },
293 },
294 targetGV: schema.GroupVersion{Group: "", Version: "v1"},
295 expectedConvertedObject: &testapigroupv1.Carp{
296 TypeMeta: metav1.TypeMeta{
297 APIVersion: "v1",
298 Kind: "Carp",
299 },
300 },
301 },
302 {
303 name: "convert hub-versioned unstructured to hub version should work",
304 unstructuredToConvert: &unstructured.Unstructured{
305 Object: map[string]interface{}{
306 "apiVersion": "__internal",
307 "kind": "Carp",
308 },
309 },
310 targetGV: schema.GroupVersion{Group: "", Version: "__internal"},
311 expectedConvertedObject: &testapigroup.Carp{},
312 },
313 {
314 name: "convert empty unstructured w/o gvk to versioned should fail",
315 unstructuredToConvert: &unstructured.Unstructured{
316 Object: map[string]interface{}{},
317 },
318 targetGV: schema.GroupVersion{Group: "", Version: "v1"},
319 expectedErrFunc: func(err error) bool {
320 return reflect.DeepEqual(err, runtime.NewMissingKindErr("unstructured object has no kind"))
321 },
322 expectedConvertedObject: nil,
323 },
324 {
325 name: "convert versioned unstructured to mismatching external version should fail",
326 unstructuredToConvert: &unstructured.Unstructured{
327 Object: map[string]interface{}{
328 "apiVersion": "v1",
329 "kind": "Carp",
330 },
331 },
332 targetGV: schema.GroupVersion{Group: "foo", Version: "v1beta1"},
333 expectedErrFunc: func(err error) bool {
334 return reflect.DeepEqual(err, runtime.NewNotRegisteredErrForTarget(
335 scheme.Name(), reflect.TypeOf(testapigroupv1.Carp{}), schema.GroupVersion{Group: "foo", Version: "v1beta1"}))
336 },
337 expectedConvertedObject: nil,
338 },
339 {
340 name: "convert versioned unstructured to mismatching internal version should fail",
341 unstructuredToConvert: &unstructured.Unstructured{
342 Object: map[string]interface{}{
343 "apiVersion": "v1",
344 "kind": "Carp",
345 },
346 },
347 targetGV: schema.GroupVersion{Group: "foo", Version: "__internal"},
348 expectedErrFunc: func(err error) bool {
349 return reflect.DeepEqual(err, runtime.NewNotRegisteredErrForTarget(
350 scheme.Name(), reflect.TypeOf(testapigroupv1.Carp{}), schema.GroupVersion{Group: "foo", Version: "__internal"}))
351 },
352 expectedConvertedObject: nil,
353 },
354 {
355 name: "convert valid versioned unstructured to its own version should work",
356 unstructuredToConvert: &unstructured.Unstructured{
357 Object: map[string]interface{}{
358 "apiVersion": "v1",
359 "kind": "Carp",
360 "metadata": map[string]interface{}{
361 "creationTimestamp": nil,
362 "name": "noxu",
363 },
364 "spec": map[string]interface{}{
365 "hostname": "example.com",
366 },
367 "status": map[string]interface{}{},
368 },
369 },
370 targetGV: schema.GroupVersion{Group: "", Version: "v1"},
371 expectedConvertedObject: &testapigroupv1.Carp{
372 TypeMeta: metav1.TypeMeta{
373 APIVersion: "v1",
374 Kind: "Carp",
375 },
376 ObjectMeta: metav1.ObjectMeta{
377 Name: "noxu",
378 },
379 Spec: testapigroupv1.CarpSpec{
380 Hostname: "example.com",
381 },
382 },
383 },
384 {
385 name: "convert valid versioned unstructured to hub-version should work ignoring type meta",
386 unstructuredToConvert: &unstructured.Unstructured{
387 Object: map[string]interface{}{
388 "apiVersion": "v1",
389 "kind": "Carp",
390 "metadata": map[string]interface{}{
391 "creationTimestamp": nil,
392 "name": "noxu",
393 },
394 "spec": map[string]interface{}{
395 "hostname": "example.com",
396 },
397 "status": map[string]interface{}{},
398 },
399 },
400 targetGV: schema.GroupVersion{Group: "", Version: "__internal"},
401 expectedConvertedObject: &testapigroup.Carp{
402 ObjectMeta: metav1.ObjectMeta{
403 Name: "noxu",
404 },
405 Spec: testapigroup.CarpSpec{
406 Hostname: "example.com",
407 },
408 },
409 },
410 {
411 name: "convert valid versioned unstructured to unexisting version should fail",
412 unstructuredToConvert: &unstructured.Unstructured{
413 Object: map[string]interface{}{
414 "apiVersion": "v1",
415 "kind": "Carp",
416 "metadata": map[string]interface{}{
417 "creationTimestamp": nil,
418 "name": "noxu",
419 },
420 "spec": map[string]interface{}{
421 "hostname": "example.com",
422 },
423 "status": map[string]interface{}{},
424 },
425 },
426 targetGV: schema.GroupVersion{Group: "", Version: "v9"},
427 expectedErrFunc: func(err error) bool {
428 return reflect.DeepEqual(err, runtime.NewNotRegisteredGVKErrForTarget(
429 scheme.Name(),
430 schema.GroupVersionKind{Group: "", Version: "v9", Kind: "Carp"},
431 nil,
432 ))
433 },
434 expectedConvertedObject: nil,
435 },
436 }
437 for _, testCase := range testCases {
438 t.Run(testCase.name, func(t *testing.T) {
439 defer func() {
440 v := recover()
441 assert.Equal(t, testCase.expectPanic, v != nil, "unexpected panic")
442 }()
443
444 outObject, err := scheme.ConvertToVersion(testCase.unstructuredToConvert, testCase.targetGV)
445 if testCase.expectedErrFunc != nil {
446 if !testCase.expectedErrFunc(err) {
447 t.Errorf("error mismatched: %v", err)
448 }
449 }
450 assert.Equal(t, testCase.expectedConvertedObject, outObject)
451 })
452 }
453 }
454
455 func TestUnstructuredToUnstructuredConversion(t *testing.T) {
456
457
458 scheme, _ := test.TestScheme()
459 inUnstructured := &unstructured.Unstructured{
460 Object: map[string]interface{}{
461 "apiVersion": "v1",
462 "kind": "Carp",
463 },
464 }
465 outUnstructured := &unstructured.Unstructured{}
466 err := scheme.Convert(inUnstructured, outUnstructured, nil)
467 assert.NoError(t, err)
468 assert.Equal(t, inUnstructured, outUnstructured)
469 }
470
471 func benchmarkCarp() *testapigroupv1.Carp {
472 t := metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC)
473 return &testapigroupv1.Carp{
474 ObjectMeta: metav1.ObjectMeta{
475 Name: "name",
476 Namespace: "namespace",
477 },
478 Spec: testapigroupv1.CarpSpec{
479 RestartPolicy: "restart",
480 NodeSelector: map[string]string{
481 "label1": "value1",
482 "label2": "value2",
483 },
484 ServiceAccountName: "service-account",
485 HostNetwork: false,
486 HostPID: true,
487 Subdomain: "hostname.subdomain.namespace.svc.domain",
488 },
489 Status: testapigroupv1.CarpStatus{
490 Phase: "phase",
491 Conditions: []testapigroupv1.CarpCondition{
492 {
493 Type: "condition1",
494 Status: "true",
495 LastProbeTime: t,
496 LastTransitionTime: t,
497 Reason: "reason",
498 Message: "message",
499 },
500 },
501 Message: "message",
502 Reason: "reason",
503 HostIP: "1.2.3.4",
504 },
505 }
506 }
507
508 func BenchmarkToUnstructured(b *testing.B) {
509 carp := benchmarkCarp()
510 converter := runtime.DefaultUnstructuredConverter
511 b.ResetTimer()
512
513 for i := 0; i < b.N; i++ {
514 result, err := converter.ToUnstructured(carp)
515 if err != nil {
516 b.Fatalf("Unexpected conversion error: %v", err)
517 }
518 if len(result) != 3 {
519 b.Errorf("Unexpected conversion result: %#v", result)
520 }
521 }
522 }
523
524 func BenchmarkFromUnstructured(b *testing.B) {
525 carp := benchmarkCarp()
526 converter := runtime.DefaultUnstructuredConverter
527 unstr, err := converter.ToUnstructured(carp)
528 if err != nil {
529 b.Fatalf("Unexpected conversion error: %v", err)
530 }
531 b.ResetTimer()
532
533 for i := 0; i < b.N; i++ {
534 result := testapigroupv1.Carp{}
535 if err := converter.FromUnstructured(unstr, &result); err != nil {
536 b.Fatalf("Unexpected conversion error: %v", err)
537 }
538 if result.Status.Phase != "phase" {
539 b.Errorf("Unexpected conversion result: %#v", result)
540 }
541 }
542 }
543
View as plain text