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 runtimetesting "k8s.io/apimachinery/pkg/runtime/testing" 29 "k8s.io/apimachinery/pkg/util/diff" 30) 31 32type testDecodable struct { 33 Other string 34 Value int `json:"value"` 35 gvk schema.GroupVersionKind 36} 37 38func (d *testDecodable) GetObjectKind() schema.ObjectKind { return d } 39func (d *testDecodable) SetGroupVersionKind(gvk schema.GroupVersionKind) { d.gvk = gvk } 40func (d *testDecodable) GroupVersionKind() schema.GroupVersionKind { return d.gvk } 41func (d *testDecodable) DeepCopyObject() runtime.Object { 42 // no real deepcopy because these tests check for pointer equality 43 return d 44} 45 46type testNestedDecodable struct { 47 Other string 48 Value int `json:"value"` 49 50 gvk schema.GroupVersionKind 51 nestedCalled bool 52 nestedErr error 53} 54 55func (d *testNestedDecodable) GetObjectKind() schema.ObjectKind { return d } 56func (d *testNestedDecodable) SetGroupVersionKind(gvk schema.GroupVersionKind) { d.gvk = gvk } 57func (d *testNestedDecodable) GroupVersionKind() schema.GroupVersionKind { return d.gvk } 58func (d *testNestedDecodable) DeepCopyObject() runtime.Object { 59 // no real deepcopy because these tests check for pointer equality 60 return d 61} 62 63func (d *testNestedDecodable) EncodeNestedObjects(e runtime.Encoder) error { 64 d.nestedCalled = true 65 return d.nestedErr 66} 67 68func (d *testNestedDecodable) DecodeNestedObjects(_ runtime.Decoder) error { 69 d.nestedCalled = true 70 return d.nestedErr 71} 72 73func TestNestedDecode(t *testing.T) { 74 n := &testNestedDecodable{nestedErr: fmt.Errorf("unable to decode")} 75 decoder := &mockSerializer{obj: n} 76 codec := NewCodec(nil, decoder, nil, nil, nil, nil, nil, nil, "TestNestedDecode") 77 if _, _, err := codec.Decode([]byte(`{}`), nil, n); err != n.nestedErr { 78 t.Errorf("unexpected error: %v", err) 79 } 80 if !n.nestedCalled { 81 t.Errorf("did not invoke nested decoder") 82 } 83} 84 85func TestNestedEncode(t *testing.T) { 86 n := &testNestedDecodable{nestedErr: fmt.Errorf("unable to decode")} 87 n2 := &testNestedDecodable{nestedErr: fmt.Errorf("unable to decode 2")} 88 encoder := &mockSerializer{obj: n} 89 codec := NewCodec( 90 encoder, nil, 91 &checkConvertor{obj: n2, groupVersion: schema.GroupVersion{Group: "other"}}, 92 nil, 93 &mockTyper{gvks: []schema.GroupVersionKind{{Kind: "test"}}}, 94 nil, 95 schema.GroupVersion{Group: "other"}, nil, 96 "TestNestedEncode", 97 ) 98 if err := codec.Encode(n, ioutil.Discard); err != n2.nestedErr { 99 t.Errorf("unexpected error: %v", err) 100 } 101 if n.nestedCalled || !n2.nestedCalled { 102 t.Errorf("did not invoke correct nested decoder") 103 } 104} 105 106func TestNestedEncodeError(t *testing.T) { 107 n := &testNestedDecodable{nestedErr: fmt.Errorf("unable to encode")} 108 gvk1 := schema.GroupVersionKind{Kind: "test", Group: "other", Version: "v1"} 109 gvk2 := schema.GroupVersionKind{Kind: "test", Group: "other", Version: "v2"} 110 n.SetGroupVersionKind(gvk1) 111 codec := NewCodec( 112 nil, nil, 113 &mockConvertor{}, 114 nil, 115 &mockTyper{gvks: []schema.GroupVersionKind{gvk1, gvk2}}, 116 nil, 117 schema.GroupVersion{Group: "other", Version: "v2"}, nil, 118 "TestNestedEncodeError", 119 ) 120 if err := codec.Encode(n, ioutil.Discard); err != n.nestedErr { 121 t.Errorf("unexpected error: %v", err) 122 } 123 if n.GroupVersionKind() != gvk1 { 124 t.Errorf("unexpected gvk of input object: %v", n.GroupVersionKind()) 125 } 126} 127 128func TestDecode(t *testing.T) { 129 gvk1 := &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"} 130 decodable1 := &testDecodable{} 131 decodable2 := &testDecodable{} 132 decodable3 := &testDecodable{} 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 // decode into the same version as the serialized object 184 { 185 decodes: schema.GroupVersions{gvk1.GroupVersion()}, 186 187 serializer: &mockSerializer{actual: gvk1, obj: decodable1}, 188 convertor: &checkConvertor{in: decodable1, obj: decodable1, groupVersion: schema.GroupVersions{{Group: "other", Version: "blah"}}}, 189 expectedGVK: gvk1, 190 expectedObject: decodable1, 191 }, 192 } 193 194 for i, test := range testCases { 195 t.Logf("%d", i) 196 s := NewCodec(test.serializer, test.serializer, test.convertor, test.creater, test.typer, test.defaulter, test.encodes, test.decodes, fmt.Sprintf("mock-%d", i)) 197 obj, gvk, err := s.Decode([]byte(`{}`), test.defaultGVK, test.into) 198 199 if !reflect.DeepEqual(test.expectedGVK, gvk) { 200 t.Errorf("%d: unexpected GVK: %v", i, gvk) 201 } 202 203 switch { 204 case err == nil && test.errFn != nil: 205 t.Errorf("%d: failed: %v", i, err) 206 continue 207 case err != nil && test.errFn == nil: 208 t.Errorf("%d: failed: %v", i, err) 209 continue 210 case err != nil: 211 if !test.errFn(err) { 212 t.Errorf("%d: failed: %v", i, err) 213 } 214 if obj != nil { 215 t.Errorf("%d: should have returned nil object", i) 216 } 217 continue 218 } 219 220 if test.into != nil && test.into != obj { 221 t.Errorf("%d: expected into to be returned: %v", i, obj) 222 continue 223 } 224 225 switch { 226 case test.expectedObject != nil: 227 if !reflect.DeepEqual(test.expectedObject, obj) { 228 t.Errorf("%d: unexpected object:\n%s", i, diff.ObjectGoPrintSideBySide(test.expectedObject, obj)) 229 } 230 case test.sameObject != nil: 231 if test.sameObject != obj { 232 t.Errorf("%d: unexpected object:\n%s", i, diff.ObjectGoPrintSideBySide(test.sameObject, obj)) 233 } 234 case obj != nil: 235 t.Errorf("%d: unexpected object: %#v", i, obj) 236 } 237 } 238} 239 240type checkConvertor struct { 241 err error 242 in, obj runtime.Object 243 groupVersion runtime.GroupVersioner 244 directConvert bool 245} 246 247func (c *checkConvertor) Convert(in, out, context interface{}) error { 248 if !c.directConvert { 249 return fmt.Errorf("unexpected call to Convert") 250 } 251 if c.in != nil && c.in != in { 252 return fmt.Errorf("unexpected in: %s", in) 253 } 254 if c.obj != nil && c.obj != out { 255 return fmt.Errorf("unexpected out: %s", out) 256 } 257 return c.err 258} 259func (c *checkConvertor) ConvertToVersion(in runtime.Object, outVersion runtime.GroupVersioner) (out runtime.Object, err error) { 260 if c.directConvert { 261 return nil, fmt.Errorf("unexpected call to ConvertToVersion") 262 } 263 if c.in != nil && c.in != in { 264 return nil, fmt.Errorf("unexpected in: %s", in) 265 } 266 if !reflect.DeepEqual(c.groupVersion, outVersion) { 267 return nil, fmt.Errorf("unexpected outversion: %s (%s)", outVersion, c.groupVersion) 268 } 269 return c.obj, c.err 270} 271func (c *checkConvertor) ConvertFieldLabel(gvk schema.GroupVersionKind, label, value string) (string, string, error) { 272 return "", "", fmt.Errorf("unexpected call to ConvertFieldLabel") 273} 274 275type mockConvertor struct { 276} 277 278func (c *mockConvertor) Convert(in, out, context interface{}) error { 279 return fmt.Errorf("unexpect call to Convert") 280} 281 282func (c *mockConvertor) ConvertToVersion(in runtime.Object, outVersion runtime.GroupVersioner) (out runtime.Object, err error) { 283 objectKind := in.GetObjectKind() 284 inGVK := objectKind.GroupVersionKind() 285 if out, ok := outVersion.KindForGroupVersionKinds([]schema.GroupVersionKind{inGVK}); ok { 286 objectKind.SetGroupVersionKind(out) 287 } else { 288 return nil, fmt.Errorf("unexpected conversion") 289 } 290 return in, nil 291} 292 293func (c *mockConvertor) ConvertFieldLabel(gvk schema.GroupVersionKind, label, value string) (string, string, error) { 294 return "", "", fmt.Errorf("unexpected call to ConvertFieldLabel") 295} 296 297type mockSerializer struct { 298 err error 299 obj runtime.Object 300 encodingObjGVK schema.GroupVersionKind 301 302 defaults, actual *schema.GroupVersionKind 303 into runtime.Object 304} 305 306func (s *mockSerializer) Decode(data []byte, defaults *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { 307 s.defaults = defaults 308 s.into = into 309 return s.obj, s.actual, s.err 310} 311 312func (s *mockSerializer) Encode(obj runtime.Object, w io.Writer) error { 313 s.obj = obj 314 s.encodingObjGVK = obj.GetObjectKind().GroupVersionKind() 315 return s.err 316} 317 318func (s *mockSerializer) Identifier() runtime.Identifier { 319 return runtime.Identifier("mock") 320} 321 322type mockCreater struct { 323 err error 324 obj runtime.Object 325} 326 327func (c *mockCreater) New(kind schema.GroupVersionKind) (runtime.Object, error) { 328 return c.obj, c.err 329} 330 331type mockTyper struct { 332 gvks []schema.GroupVersionKind 333 unversioned bool 334 err error 335} 336 337func (t *mockTyper) ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) { 338 return t.gvks, t.unversioned, t.err 339} 340 341func (t *mockTyper) Recognizes(_ schema.GroupVersionKind) bool { 342 return true 343} 344 345func TestDirectCodecEncode(t *testing.T) { 346 serializer := mockSerializer{} 347 typer := mockTyper{ 348 gvks: []schema.GroupVersionKind{ 349 { 350 Group: "wrong_group", 351 Kind: "some_kind", 352 }, 353 { 354 Group: "expected_group", 355 Kind: "some_kind", 356 }, 357 }, 358 } 359 360 c := runtime.WithVersionEncoder{ 361 Version: schema.GroupVersion{Group: "expected_group"}, 362 Encoder: &serializer, 363 ObjectTyper: &typer, 364 } 365 c.Encode(&testDecodable{}, ioutil.Discard) 366 if e, a := "expected_group", serializer.encodingObjGVK.Group; e != a { 367 t.Errorf("expected group to be %v, got %v", e, a) 368 } 369} 370 371func TestCacheableObject(t *testing.T) { 372 gvk1 := schema.GroupVersionKind{Group: "group", Version: "version1", Kind: "MockCacheableObject"} 373 gvk2 := schema.GroupVersionKind{Group: "group", Version: "version2", Kind: "MockCacheableObject"} 374 375 encoder := NewCodec( 376 &mockSerializer{}, &mockSerializer{}, 377 &mockConvertor{}, nil, 378 &mockTyper{gvks: []schema.GroupVersionKind{gvk1, gvk2}}, nil, 379 gvk1.GroupVersion(), gvk2.GroupVersion(), 380 "TestCacheableObject") 381 382 runtimetesting.CacheableObjectTest(t, encoder) 383} 384 385func BenchmarkIdentifier(b *testing.B) { 386 encoder := &mockSerializer{} 387 gv := schema.GroupVersion{Group: "group", Version: "version"} 388 389 for i := 0; i < b.N; i++ { 390 id := identifier(gv, encoder) 391 // Avoid optimizing by compiler. 392 if id[0] != '{' { 393 b.Errorf("unexpected identifier: %s", id) 394 } 395 } 396} 397