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 versioning 18 19import ( 20 "fmt" 21 "io" 22 "io/ioutil" 23 "reflect" 24 "testing" 25 26 "k8s.io/apimachinery/pkg/runtime" 27 "k8s.io/apimachinery/pkg/runtime/schema" 28 "k8s.io/apimachinery/pkg/util/diff" 29) 30 31type testDecodable struct { 32 Other string 33 Value int `json:"value"` 34 gvk schema.GroupVersionKind 35} 36 37func (d *testDecodable) GetObjectKind() schema.ObjectKind { return d } 38func (d *testDecodable) SetGroupVersionKind(gvk schema.GroupVersionKind) { d.gvk = gvk } 39func (d *testDecodable) GroupVersionKind() schema.GroupVersionKind { return d.gvk } 40func (d *testDecodable) DeepCopyObject() runtime.Object { 41 // no real deepcopy because these tests check for pointer equality 42 return d 43} 44 45type testNestedDecodable struct { 46 Other string 47 Value int `json:"value"` 48 49 gvk schema.GroupVersionKind 50 nestedCalled bool 51 nestedErr error 52} 53 54func (d *testNestedDecodable) GetObjectKind() schema.ObjectKind { return d } 55func (d *testNestedDecodable) SetGroupVersionKind(gvk schema.GroupVersionKind) { d.gvk = gvk } 56func (d *testNestedDecodable) GroupVersionKind() schema.GroupVersionKind { return d.gvk } 57func (d *testNestedDecodable) DeepCopyObject() runtime.Object { 58 // no real deepcopy because these tests check for pointer equality 59 return d 60} 61 62func (d *testNestedDecodable) EncodeNestedObjects(e runtime.Encoder) error { 63 d.nestedCalled = true 64 return d.nestedErr 65} 66 67func (d *testNestedDecodable) DecodeNestedObjects(_ runtime.Decoder) error { 68 d.nestedCalled = true 69 return d.nestedErr 70} 71 72func TestNestedDecode(t *testing.T) { 73 n := &testNestedDecodable{nestedErr: fmt.Errorf("unable to decode")} 74 decoder := &mockSerializer{obj: n} 75 codec := NewCodec(nil, decoder, nil, nil, nil, nil, nil, nil, "TestNestedDecode") 76 if _, _, err := codec.Decode([]byte(`{}`), nil, n); err != n.nestedErr { 77 t.Errorf("unexpected error: %v", err) 78 } 79 if !n.nestedCalled { 80 t.Errorf("did not invoke nested decoder") 81 } 82} 83 84func TestNestedEncode(t *testing.T) { 85 n := &testNestedDecodable{nestedErr: fmt.Errorf("unable to decode")} 86 n2 := &testNestedDecodable{nestedErr: fmt.Errorf("unable to decode 2")} 87 encoder := &mockSerializer{obj: n} 88 codec := NewCodec( 89 encoder, nil, 90 &checkConvertor{obj: n2, groupVersion: schema.GroupVersion{Group: "other"}}, 91 nil, 92 &mockTyper{gvks: []schema.GroupVersionKind{{Kind: "test"}}}, 93 nil, 94 schema.GroupVersion{Group: "other"}, nil, 95 "TestNestedEncode", 96 ) 97 if err := codec.Encode(n, ioutil.Discard); err != n2.nestedErr { 98 t.Errorf("unexpected error: %v", err) 99 } 100 if n.nestedCalled || !n2.nestedCalled { 101 t.Errorf("did not invoke correct nested decoder") 102 } 103} 104 105func TestNestedEncodeError(t *testing.T) { 106 n := &testNestedDecodable{nestedErr: fmt.Errorf("unable to encode")} 107 gvk1 := schema.GroupVersionKind{Kind: "test", Group: "other", Version: "v1"} 108 gvk2 := schema.GroupVersionKind{Kind: "test", Group: "other", Version: "v2"} 109 n.SetGroupVersionKind(gvk1) 110 codec := NewCodec( 111 nil, nil, 112 &mockConvertor{}, 113 nil, 114 &mockTyper{gvks: []schema.GroupVersionKind{gvk1, gvk2}}, 115 nil, 116 schema.GroupVersion{Group: "other", Version: "v2"}, nil, 117 "TestNestedEncodeError", 118 ) 119 if err := codec.Encode(n, ioutil.Discard); err != n.nestedErr { 120 t.Errorf("unexpected error: %v", err) 121 } 122 if n.GroupVersionKind() != gvk1 { 123 t.Errorf("unexpected gvk of input object: %v", n.GroupVersionKind()) 124 } 125} 126 127func TestDecode(t *testing.T) { 128 gvk1 := &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"} 129 decodable1 := &testDecodable{} 130 decodable2 := &testDecodable{} 131 decodable3 := &testDecodable{} 132 versionedDecodable1 := &runtime.VersionedObjects{Objects: []runtime.Object{decodable1}} 133 134 testCases := []struct { 135 serializer runtime.Serializer 136 convertor runtime.ObjectConvertor 137 creater runtime.ObjectCreater 138 typer runtime.ObjectTyper 139 defaulter runtime.ObjectDefaulter 140 yaml bool 141 pretty bool 142 143 encodes, decodes runtime.GroupVersioner 144 145 defaultGVK *schema.GroupVersionKind 146 into runtime.Object 147 148 errFn func(error) bool 149 expectedObject runtime.Object 150 sameObject runtime.Object 151 expectedGVK *schema.GroupVersionKind 152 }{ 153 { 154 serializer: &mockSerializer{actual: gvk1}, 155 convertor: &checkConvertor{groupVersion: schema.GroupVersion{Group: "other", Version: runtime.APIVersionInternal}}, 156 expectedGVK: gvk1, 157 decodes: schema.GroupVersion{Group: "other", Version: runtime.APIVersionInternal}, 158 }, 159 { 160 serializer: &mockSerializer{actual: gvk1, obj: decodable1}, 161 convertor: &checkConvertor{in: decodable1, obj: decodable2, groupVersion: schema.GroupVersion{Group: "other", Version: runtime.APIVersionInternal}}, 162 expectedGVK: gvk1, 163 sameObject: decodable2, 164 decodes: schema.GroupVersion{Group: "other", Version: runtime.APIVersionInternal}, 165 }, 166 // defaultGVK.Group is allowed to force a conversion to the destination group 167 { 168 serializer: &mockSerializer{actual: gvk1, obj: decodable1}, 169 defaultGVK: &schema.GroupVersionKind{Group: "force"}, 170 convertor: &checkConvertor{in: decodable1, obj: decodable2, groupVersion: schema.GroupVersion{Group: "force", Version: runtime.APIVersionInternal}}, 171 expectedGVK: gvk1, 172 sameObject: decodable2, 173 decodes: schema.GroupVersion{Group: "force", Version: runtime.APIVersionInternal}, 174 }, 175 // uses direct conversion for into when objects differ 176 { 177 into: decodable3, 178 serializer: &mockSerializer{actual: gvk1, obj: decodable1}, 179 convertor: &checkConvertor{in: decodable1, obj: decodable3, directConvert: true}, 180 expectedGVK: gvk1, 181 sameObject: decodable3, 182 }, 183 { 184 into: versionedDecodable1, 185 serializer: &mockSerializer{actual: gvk1, obj: decodable3}, 186 convertor: &checkConvertor{in: decodable3, obj: decodable1, directConvert: true}, 187 expectedGVK: gvk1, 188 sameObject: versionedDecodable1, 189 }, 190 // returns directly when serializer returns into 191 { 192 into: decodable3, 193 serializer: &mockSerializer{actual: gvk1, obj: decodable3}, 194 expectedGVK: gvk1, 195 sameObject: decodable3, 196 }, 197 // returns directly when serializer returns into 198 { 199 into: versionedDecodable1, 200 serializer: &mockSerializer{actual: gvk1, obj: decodable1}, 201 expectedGVK: gvk1, 202 sameObject: versionedDecodable1, 203 }, 204 205 // runtime.VersionedObjects are decoded 206 { 207 into: &runtime.VersionedObjects{Objects: []runtime.Object{}}, 208 209 serializer: &mockSerializer{actual: gvk1, obj: decodable1}, 210 convertor: &checkConvertor{in: decodable1, obj: decodable2, groupVersion: schema.GroupVersion{Group: "other", Version: runtime.APIVersionInternal}}, 211 expectedGVK: gvk1, 212 expectedObject: &runtime.VersionedObjects{Objects: []runtime.Object{decodable1, decodable2}}, 213 decodes: schema.GroupVersion{Group: "other", Version: runtime.APIVersionInternal}, 214 }, 215 216 // decode into the same version as the serialized object 217 { 218 decodes: schema.GroupVersions{gvk1.GroupVersion()}, 219 220 serializer: &mockSerializer{actual: gvk1, obj: decodable1}, 221 convertor: &checkConvertor{in: decodable1, obj: decodable1, groupVersion: schema.GroupVersions{{Group: "other", Version: "blah"}}}, 222 expectedGVK: gvk1, 223 expectedObject: decodable1, 224 }, 225 { 226 into: &runtime.VersionedObjects{Objects: []runtime.Object{}}, 227 decodes: schema.GroupVersions{gvk1.GroupVersion()}, 228 229 serializer: &mockSerializer{actual: gvk1, obj: decodable1}, 230 convertor: &checkConvertor{in: decodable1, obj: decodable1, groupVersion: schema.GroupVersions{{Group: "other", Version: "blah"}}}, 231 expectedGVK: gvk1, 232 expectedObject: &runtime.VersionedObjects{Objects: []runtime.Object{decodable1}}, 233 }, 234 235 // codec with non matching version skips conversion altogether 236 { 237 decodes: schema.GroupVersions{{Group: "something", Version: "else"}}, 238 239 serializer: &mockSerializer{actual: gvk1, obj: decodable1}, 240 convertor: &checkConvertor{in: decodable1, obj: decodable1, groupVersion: schema.GroupVersions{{Group: "something", Version: "else"}}}, 241 expectedGVK: gvk1, 242 expectedObject: decodable1, 243 }, 244 { 245 into: &runtime.VersionedObjects{Objects: []runtime.Object{}}, 246 decodes: schema.GroupVersions{{Group: "something", Version: "else"}}, 247 248 serializer: &mockSerializer{actual: gvk1, obj: decodable1}, 249 convertor: &checkConvertor{in: decodable1, obj: decodable1, groupVersion: schema.GroupVersions{{Group: "something", Version: "else"}}}, 250 expectedGVK: gvk1, 251 expectedObject: &runtime.VersionedObjects{Objects: []runtime.Object{decodable1}}, 252 }, 253 } 254 255 for i, test := range testCases { 256 t.Logf("%d", i) 257 s := NewCodec(test.serializer, test.serializer, test.convertor, test.creater, test.typer, test.defaulter, test.encodes, test.decodes, fmt.Sprintf("mock-%d", i)) 258 obj, gvk, err := s.Decode([]byte(`{}`), test.defaultGVK, test.into) 259 260 if !reflect.DeepEqual(test.expectedGVK, gvk) { 261 t.Errorf("%d: unexpected GVK: %v", i, gvk) 262 } 263 264 switch { 265 case err == nil && test.errFn != nil: 266 t.Errorf("%d: failed: %v", i, err) 267 continue 268 case err != nil && test.errFn == nil: 269 t.Errorf("%d: failed: %v", i, err) 270 continue 271 case err != nil: 272 if !test.errFn(err) { 273 t.Errorf("%d: failed: %v", i, err) 274 } 275 if obj != nil { 276 t.Errorf("%d: should have returned nil object", i) 277 } 278 continue 279 } 280 281 if test.into != nil && test.into != obj { 282 t.Errorf("%d: expected into to be returned: %v", i, obj) 283 continue 284 } 285 286 switch { 287 case test.expectedObject != nil: 288 if !reflect.DeepEqual(test.expectedObject, obj) { 289 t.Errorf("%d: unexpected object:\n%s", i, diff.ObjectGoPrintSideBySide(test.expectedObject, obj)) 290 } 291 case test.sameObject != nil: 292 if test.sameObject != obj { 293 t.Errorf("%d: unexpected object:\n%s", i, diff.ObjectGoPrintSideBySide(test.sameObject, obj)) 294 } 295 case obj != nil: 296 t.Errorf("%d: unexpected object: %#v", i, obj) 297 } 298 } 299} 300 301type checkConvertor struct { 302 err error 303 in, obj runtime.Object 304 groupVersion runtime.GroupVersioner 305 directConvert bool 306} 307 308func (c *checkConvertor) Convert(in, out, context interface{}) error { 309 if !c.directConvert { 310 return fmt.Errorf("unexpected call to Convert") 311 } 312 if c.in != nil && c.in != in { 313 return fmt.Errorf("unexpected in: %s", in) 314 } 315 if c.obj != nil && c.obj != out { 316 return fmt.Errorf("unexpected out: %s", out) 317 } 318 return c.err 319} 320func (c *checkConvertor) ConvertToVersion(in runtime.Object, outVersion runtime.GroupVersioner) (out runtime.Object, err error) { 321 if c.directConvert { 322 return nil, fmt.Errorf("unexpected call to ConvertToVersion") 323 } 324 if c.in != nil && c.in != in { 325 return nil, fmt.Errorf("unexpected in: %s", in) 326 } 327 if !reflect.DeepEqual(c.groupVersion, outVersion) { 328 return nil, fmt.Errorf("unexpected outversion: %s (%s)", outVersion, c.groupVersion) 329 } 330 return c.obj, c.err 331} 332func (c *checkConvertor) ConvertFieldLabel(gvk schema.GroupVersionKind, label, value string) (string, string, error) { 333 return "", "", fmt.Errorf("unexpected call to ConvertFieldLabel") 334} 335 336type mockConvertor struct { 337} 338 339func (c *mockConvertor) Convert(in, out, context interface{}) error { 340 return fmt.Errorf("unexpect call to Convert") 341} 342 343func (c *mockConvertor) ConvertToVersion(in runtime.Object, outVersion runtime.GroupVersioner) (out runtime.Object, err error) { 344 objectKind := in.GetObjectKind() 345 inGVK := objectKind.GroupVersionKind() 346 if out, ok := outVersion.KindForGroupVersionKinds([]schema.GroupVersionKind{inGVK}); ok { 347 objectKind.SetGroupVersionKind(out) 348 } else { 349 return nil, fmt.Errorf("unexpected conversion") 350 } 351 return in, nil 352} 353 354func (c *mockConvertor) ConvertFieldLabel(gvk schema.GroupVersionKind, label, value string) (string, string, error) { 355 return "", "", fmt.Errorf("unexpected call to ConvertFieldLabel") 356} 357 358type mockSerializer struct { 359 err error 360 obj runtime.Object 361 encodingObjGVK schema.GroupVersionKind 362 363 defaults, actual *schema.GroupVersionKind 364 into runtime.Object 365} 366 367func (s *mockSerializer) Decode(data []byte, defaults *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { 368 s.defaults = defaults 369 s.into = into 370 return s.obj, s.actual, s.err 371} 372 373func (s *mockSerializer) Encode(obj runtime.Object, w io.Writer) error { 374 s.obj = obj 375 s.encodingObjGVK = obj.GetObjectKind().GroupVersionKind() 376 return s.err 377} 378 379type mockCreater struct { 380 err error 381 obj runtime.Object 382} 383 384func (c *mockCreater) New(kind schema.GroupVersionKind) (runtime.Object, error) { 385 return c.obj, c.err 386} 387 388type mockTyper struct { 389 gvks []schema.GroupVersionKind 390 unversioned bool 391 err error 392} 393 394func (t *mockTyper) ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) { 395 return t.gvks, t.unversioned, t.err 396} 397 398func (t *mockTyper) Recognizes(_ schema.GroupVersionKind) bool { 399 return true 400} 401 402func TestDirectCodecEncode(t *testing.T) { 403 serializer := mockSerializer{} 404 typer := mockTyper{ 405 gvks: []schema.GroupVersionKind{ 406 { 407 Group: "wrong_group", 408 Kind: "some_kind", 409 }, 410 { 411 Group: "expected_group", 412 Kind: "some_kind", 413 }, 414 }, 415 } 416 417 c := runtime.WithVersionEncoder{ 418 Version: schema.GroupVersion{Group: "expected_group"}, 419 Encoder: &serializer, 420 ObjectTyper: &typer, 421 } 422 c.Encode(&testDecodable{}, ioutil.Discard) 423 if e, a := "expected_group", serializer.encodingObjGVK.Group; e != a { 424 t.Errorf("expected group to be %v, got %v", e, a) 425 } 426} 427