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 TestDecode(t *testing.T) { 106 gvk1 := &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"} 107 decodable1 := &testDecodable{} 108 decodable2 := &testDecodable{} 109 decodable3 := &testDecodable{} 110 versionedDecodable1 := &runtime.VersionedObjects{Objects: []runtime.Object{decodable1}} 111 112 testCases := []struct { 113 serializer runtime.Serializer 114 convertor runtime.ObjectConvertor 115 creater runtime.ObjectCreater 116 typer runtime.ObjectTyper 117 defaulter runtime.ObjectDefaulter 118 yaml bool 119 pretty bool 120 121 encodes, decodes runtime.GroupVersioner 122 123 defaultGVK *schema.GroupVersionKind 124 into runtime.Object 125 126 errFn func(error) bool 127 expectedObject runtime.Object 128 sameObject runtime.Object 129 expectedGVK *schema.GroupVersionKind 130 }{ 131 { 132 serializer: &mockSerializer{actual: gvk1}, 133 convertor: &checkConvertor{groupVersion: schema.GroupVersion{Group: "other", Version: runtime.APIVersionInternal}}, 134 expectedGVK: gvk1, 135 decodes: schema.GroupVersion{Group: "other", Version: runtime.APIVersionInternal}, 136 }, 137 { 138 serializer: &mockSerializer{actual: gvk1, obj: decodable1}, 139 convertor: &checkConvertor{in: decodable1, obj: decodable2, groupVersion: schema.GroupVersion{Group: "other", Version: runtime.APIVersionInternal}}, 140 expectedGVK: gvk1, 141 sameObject: decodable2, 142 decodes: schema.GroupVersion{Group: "other", Version: runtime.APIVersionInternal}, 143 }, 144 // defaultGVK.Group is allowed to force a conversion to the destination group 145 { 146 serializer: &mockSerializer{actual: gvk1, obj: decodable1}, 147 defaultGVK: &schema.GroupVersionKind{Group: "force"}, 148 convertor: &checkConvertor{in: decodable1, obj: decodable2, groupVersion: schema.GroupVersion{Group: "force", Version: runtime.APIVersionInternal}}, 149 expectedGVK: gvk1, 150 sameObject: decodable2, 151 decodes: schema.GroupVersion{Group: "force", Version: runtime.APIVersionInternal}, 152 }, 153 // uses direct conversion for into when objects differ 154 { 155 into: decodable3, 156 serializer: &mockSerializer{actual: gvk1, obj: decodable1}, 157 convertor: &checkConvertor{in: decodable1, obj: decodable3, directConvert: true}, 158 expectedGVK: gvk1, 159 sameObject: decodable3, 160 }, 161 { 162 into: versionedDecodable1, 163 serializer: &mockSerializer{actual: gvk1, obj: decodable3}, 164 convertor: &checkConvertor{in: decodable3, obj: decodable1, directConvert: true}, 165 expectedGVK: gvk1, 166 sameObject: versionedDecodable1, 167 }, 168 // returns directly when serializer returns into 169 { 170 into: decodable3, 171 serializer: &mockSerializer{actual: gvk1, obj: decodable3}, 172 expectedGVK: gvk1, 173 sameObject: decodable3, 174 }, 175 // returns directly when serializer returns into 176 { 177 into: versionedDecodable1, 178 serializer: &mockSerializer{actual: gvk1, obj: decodable1}, 179 expectedGVK: gvk1, 180 sameObject: versionedDecodable1, 181 }, 182 183 // runtime.VersionedObjects are decoded 184 { 185 into: &runtime.VersionedObjects{Objects: []runtime.Object{}}, 186 187 serializer: &mockSerializer{actual: gvk1, obj: decodable1}, 188 convertor: &checkConvertor{in: decodable1, obj: decodable2, groupVersion: schema.GroupVersion{Group: "other", Version: runtime.APIVersionInternal}}, 189 expectedGVK: gvk1, 190 expectedObject: &runtime.VersionedObjects{Objects: []runtime.Object{decodable1, decodable2}}, 191 decodes: schema.GroupVersion{Group: "other", Version: runtime.APIVersionInternal}, 192 }, 193 194 // decode into the same version as the serialized object 195 { 196 decodes: schema.GroupVersions{gvk1.GroupVersion()}, 197 198 serializer: &mockSerializer{actual: gvk1, obj: decodable1}, 199 convertor: &checkConvertor{in: decodable1, obj: decodable1, groupVersion: schema.GroupVersions{{Group: "other", Version: "blah"}}}, 200 expectedGVK: gvk1, 201 expectedObject: decodable1, 202 }, 203 { 204 into: &runtime.VersionedObjects{Objects: []runtime.Object{}}, 205 decodes: schema.GroupVersions{gvk1.GroupVersion()}, 206 207 serializer: &mockSerializer{actual: gvk1, obj: decodable1}, 208 convertor: &checkConvertor{in: decodable1, obj: decodable1, groupVersion: schema.GroupVersions{{Group: "other", Version: "blah"}}}, 209 expectedGVK: gvk1, 210 expectedObject: &runtime.VersionedObjects{Objects: []runtime.Object{decodable1}}, 211 }, 212 213 // codec with non matching version skips conversion altogether 214 { 215 decodes: schema.GroupVersions{{Group: "something", Version: "else"}}, 216 217 serializer: &mockSerializer{actual: gvk1, obj: decodable1}, 218 convertor: &checkConvertor{in: decodable1, obj: decodable1, groupVersion: schema.GroupVersions{{Group: "something", Version: "else"}}}, 219 expectedGVK: gvk1, 220 expectedObject: decodable1, 221 }, 222 { 223 into: &runtime.VersionedObjects{Objects: []runtime.Object{}}, 224 decodes: schema.GroupVersions{{Group: "something", Version: "else"}}, 225 226 serializer: &mockSerializer{actual: gvk1, obj: decodable1}, 227 convertor: &checkConvertor{in: decodable1, obj: decodable1, groupVersion: schema.GroupVersions{{Group: "something", Version: "else"}}}, 228 expectedGVK: gvk1, 229 expectedObject: &runtime.VersionedObjects{Objects: []runtime.Object{decodable1}}, 230 }, 231 } 232 233 for i, test := range testCases { 234 t.Logf("%d", i) 235 s := NewCodec(test.serializer, test.serializer, test.convertor, test.creater, test.typer, test.defaulter, test.encodes, test.decodes, fmt.Sprintf("mock-%d", i)) 236 obj, gvk, err := s.Decode([]byte(`{}`), test.defaultGVK, test.into) 237 238 if !reflect.DeepEqual(test.expectedGVK, gvk) { 239 t.Errorf("%d: unexpected GVK: %v", i, gvk) 240 } 241 242 switch { 243 case err == nil && test.errFn != nil: 244 t.Errorf("%d: failed: %v", i, err) 245 continue 246 case err != nil && test.errFn == nil: 247 t.Errorf("%d: failed: %v", i, err) 248 continue 249 case err != nil: 250 if !test.errFn(err) { 251 t.Errorf("%d: failed: %v", i, err) 252 } 253 if obj != nil { 254 t.Errorf("%d: should have returned nil object", i) 255 } 256 continue 257 } 258 259 if test.into != nil && test.into != obj { 260 t.Errorf("%d: expected into to be returned: %v", i, obj) 261 continue 262 } 263 264 switch { 265 case test.expectedObject != nil: 266 if !reflect.DeepEqual(test.expectedObject, obj) { 267 t.Errorf("%d: unexpected object:\n%s", i, diff.ObjectGoPrintSideBySide(test.expectedObject, obj)) 268 } 269 case test.sameObject != nil: 270 if test.sameObject != obj { 271 t.Errorf("%d: unexpected object:\n%s", i, diff.ObjectGoPrintSideBySide(test.sameObject, obj)) 272 } 273 case obj != nil: 274 t.Errorf("%d: unexpected object: %#v", i, obj) 275 } 276 } 277} 278 279type checkConvertor struct { 280 err error 281 in, obj runtime.Object 282 groupVersion runtime.GroupVersioner 283 directConvert bool 284} 285 286func (c *checkConvertor) Convert(in, out, context interface{}) error { 287 if !c.directConvert { 288 return fmt.Errorf("unexpected call to Convert") 289 } 290 if c.in != nil && c.in != in { 291 return fmt.Errorf("unexpected in: %s", in) 292 } 293 if c.obj != nil && c.obj != out { 294 return fmt.Errorf("unexpected out: %s", out) 295 } 296 return c.err 297} 298func (c *checkConvertor) ConvertToVersion(in runtime.Object, outVersion runtime.GroupVersioner) (out runtime.Object, err error) { 299 if c.directConvert { 300 return nil, fmt.Errorf("unexpected call to ConvertToVersion") 301 } 302 if c.in != nil && c.in != in { 303 return nil, fmt.Errorf("unexpected in: %s", in) 304 } 305 if !reflect.DeepEqual(c.groupVersion, outVersion) { 306 return nil, fmt.Errorf("unexpected outversion: %s (%s)", outVersion, c.groupVersion) 307 } 308 return c.obj, c.err 309} 310func (c *checkConvertor) ConvertFieldLabel(gvk schema.GroupVersionKind, label, value string) (string, string, error) { 311 return "", "", fmt.Errorf("unexpected call to ConvertFieldLabel") 312} 313 314type mockSerializer struct { 315 err error 316 obj runtime.Object 317 encodingObjGVK schema.GroupVersionKind 318 319 defaults, actual *schema.GroupVersionKind 320 into runtime.Object 321} 322 323func (s *mockSerializer) Decode(data []byte, defaults *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { 324 s.defaults = defaults 325 s.into = into 326 return s.obj, s.actual, s.err 327} 328 329func (s *mockSerializer) Encode(obj runtime.Object, w io.Writer) error { 330 s.obj = obj 331 s.encodingObjGVK = obj.GetObjectKind().GroupVersionKind() 332 return s.err 333} 334 335type mockCreater struct { 336 err error 337 obj runtime.Object 338} 339 340func (c *mockCreater) New(kind schema.GroupVersionKind) (runtime.Object, error) { 341 return c.obj, c.err 342} 343 344type mockTyper struct { 345 gvks []schema.GroupVersionKind 346 unversioned bool 347 err error 348} 349 350func (t *mockTyper) ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) { 351 return t.gvks, t.unversioned, t.err 352} 353 354func (t *mockTyper) Recognizes(_ schema.GroupVersionKind) bool { 355 return true 356} 357 358func TestDirectCodecEncode(t *testing.T) { 359 serializer := mockSerializer{} 360 typer := mockTyper{ 361 gvks: []schema.GroupVersionKind{ 362 { 363 Group: "wrong_group", 364 Kind: "some_kind", 365 }, 366 { 367 Group: "expected_group", 368 Kind: "some_kind", 369 }, 370 }, 371 } 372 373 c := DirectEncoder{ 374 Version: schema.GroupVersion{Group: "expected_group"}, 375 Encoder: &serializer, 376 ObjectTyper: &typer, 377 } 378 c.Encode(&testDecodable{}, ioutil.Discard) 379 if e, a := "expected_group", serializer.encodingObjGVK.Group; e != a { 380 t.Errorf("expected group to be %v, got %v", e, a) 381 } 382} 383