k8s.io/apiserver@v0.31.1/pkg/server/storage/storage_factory_test.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package storage 18 19 import ( 20 "mime" 21 "reflect" 22 "strings" 23 "testing" 24 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 "k8s.io/apimachinery/pkg/runtime" 27 "k8s.io/apimachinery/pkg/runtime/schema" 28 "k8s.io/apimachinery/pkg/runtime/serializer" 29 apimachineryversion "k8s.io/apimachinery/pkg/util/version" 30 "k8s.io/apiserver/pkg/apis/example" 31 exampleinstall "k8s.io/apiserver/pkg/apis/example/install" 32 examplev1 "k8s.io/apiserver/pkg/apis/example/v1" 33 "k8s.io/apiserver/pkg/storage/storagebackend" 34 "k8s.io/apiserver/pkg/util/version" 35 ) 36 37 var ( 38 v1GroupVersion = schema.GroupVersion{Group: "", Version: "v1"} 39 40 scheme = runtime.NewScheme() 41 codecs = serializer.NewCodecFactory(scheme) 42 ) 43 44 func init() { 45 metav1.AddToGroupVersion(scheme, metav1.SchemeGroupVersion) 46 scheme.AddUnversionedTypes(v1GroupVersion, 47 &metav1.Status{}, 48 &metav1.APIVersions{}, 49 &metav1.APIGroupList{}, 50 &metav1.APIGroup{}, 51 &metav1.APIResourceList{}, 52 ) 53 54 exampleinstall.Install(scheme) 55 } 56 57 type fakeNegotiater struct { 58 serializer, streamSerializer runtime.Serializer 59 framer runtime.Framer 60 types, streamTypes []string 61 } 62 63 func (n *fakeNegotiater) SupportedMediaTypes() []runtime.SerializerInfo { 64 var out []runtime.SerializerInfo 65 for _, s := range n.types { 66 mediaType, _, err := mime.ParseMediaType(s) 67 if err != nil { 68 panic(err) 69 } 70 parts := strings.SplitN(mediaType, "/", 2) 71 if len(parts) == 1 { 72 // this is an error on the server side 73 parts = append(parts, "") 74 } 75 76 info := runtime.SerializerInfo{ 77 Serializer: n.serializer, 78 MediaType: s, 79 MediaTypeType: parts[0], 80 MediaTypeSubType: parts[1], 81 EncodesAsText: true, 82 } 83 84 for _, t := range n.streamTypes { 85 if t == s { 86 info.StreamSerializer = &runtime.StreamSerializerInfo{ 87 EncodesAsText: true, 88 Framer: n.framer, 89 Serializer: n.streamSerializer, 90 } 91 } 92 } 93 out = append(out, info) 94 } 95 return out 96 } 97 98 func (n *fakeNegotiater) UniversalDeserializer() runtime.Decoder { 99 return n.serializer 100 } 101 102 func (n *fakeNegotiater) EncoderForVersion(serializer runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder { 103 return n.serializer 104 } 105 106 func (n *fakeNegotiater) DecoderToVersion(serializer runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder { 107 return n.serializer 108 } 109 110 func TestConfigurableStorageFactory(t *testing.T) { 111 ns := &fakeNegotiater{types: []string{"test/test"}} 112 f := NewDefaultStorageFactory(storagebackend.Config{}, "test/test", ns, NewDefaultResourceEncodingConfig(scheme), NewResourceConfig(), nil) 113 f.AddCohabitatingResources(example.Resource("test"), schema.GroupResource{Resource: "test2", Group: "2"}) 114 called := false 115 testEncoderChain := func(e runtime.Encoder) runtime.Encoder { 116 called = true 117 return e 118 } 119 f.AddSerializationChains(testEncoderChain, nil, example.Resource("test")) 120 f.SetEtcdLocation(example.Resource("*"), []string{"/server2"}) 121 f.SetEtcdPrefix(example.Resource("test"), "/prefix_for_test") 122 123 config, err := f.NewConfig(example.Resource("test"), nil) 124 if err != nil { 125 t.Fatal(err) 126 } 127 if config.Prefix != "/prefix_for_test" || !reflect.DeepEqual(config.Transport.ServerList, []string{"/server2"}) { 128 t.Errorf("unexpected config %#v", config) 129 } 130 if !called { 131 t.Errorf("expected encoder chain to be called") 132 } 133 } 134 135 func TestUpdateEtcdOverrides(t *testing.T) { 136 exampleinstall.Install(scheme) 137 138 testCases := []struct { 139 resource schema.GroupResource 140 servers []string 141 }{ 142 { 143 resource: schema.GroupResource{Group: example.GroupName, Resource: "resource"}, 144 servers: []string{"http://127.0.0.1:10000"}, 145 }, 146 { 147 resource: schema.GroupResource{Group: example.GroupName, Resource: "resource"}, 148 servers: []string{"http://127.0.0.1:10000", "http://127.0.0.1:20000"}, 149 }, 150 { 151 resource: schema.GroupResource{Group: example.GroupName, Resource: "resource"}, 152 servers: []string{"http://127.0.0.1:10000"}, 153 }, 154 } 155 156 defaultEtcdLocation := []string{"http://127.0.0.1"} 157 for i, test := range testCases { 158 defaultConfig := storagebackend.Config{ 159 Prefix: "/registry", 160 Transport: storagebackend.TransportConfig{ 161 ServerList: defaultEtcdLocation, 162 }, 163 } 164 storageFactory := NewDefaultStorageFactory(defaultConfig, "", codecs, NewDefaultResourceEncodingConfig(scheme), NewResourceConfig(), nil) 165 storageFactory.SetEtcdLocation(test.resource, test.servers) 166 167 var err error 168 config, err := storageFactory.NewConfig(test.resource, nil) 169 if err != nil { 170 t.Errorf("%d: unexpected error %v", i, err) 171 continue 172 } 173 if !reflect.DeepEqual(config.Transport.ServerList, test.servers) { 174 t.Errorf("%d: expected %v, got %v", i, test.servers, config.Transport.ServerList) 175 continue 176 } 177 178 config, err = storageFactory.NewConfig(schema.GroupResource{Group: examplev1.GroupName, Resource: "unlikely"}, nil) 179 if err != nil { 180 t.Errorf("%d: unexpected error %v", i, err) 181 continue 182 } 183 if !reflect.DeepEqual(config.Transport.ServerList, defaultEtcdLocation) { 184 t.Errorf("%d: expected %v, got %v", i, defaultEtcdLocation, config.Transport.ServerList) 185 continue 186 } 187 188 } 189 } 190 191 func TestConfigs(t *testing.T) { 192 exampleinstall.Install(scheme) 193 defaultEtcdLocations := []string{"http://127.0.0.1", "http://127.0.0.2"} 194 195 testCases := []struct { 196 resource *schema.GroupResource 197 servers []string 198 wantConfigs []storagebackend.Config 199 }{ 200 { 201 wantConfigs: []storagebackend.Config{ 202 {Transport: storagebackend.TransportConfig{ServerList: defaultEtcdLocations}, Prefix: "/registry"}, 203 }, 204 }, 205 { 206 resource: &schema.GroupResource{Group: example.GroupName, Resource: "resource"}, 207 servers: []string{}, 208 wantConfigs: []storagebackend.Config{ 209 {Transport: storagebackend.TransportConfig{ServerList: defaultEtcdLocations}, Prefix: "/registry"}, 210 }, 211 }, 212 { 213 resource: &schema.GroupResource{Group: example.GroupName, Resource: "resource"}, 214 servers: []string{"http://127.0.0.1:10000"}, 215 wantConfigs: []storagebackend.Config{ 216 {Transport: storagebackend.TransportConfig{ServerList: defaultEtcdLocations}, Prefix: "/registry"}, 217 {Transport: storagebackend.TransportConfig{ServerList: []string{"http://127.0.0.1:10000"}}, Prefix: "/registry"}, 218 }, 219 }, 220 { 221 resource: &schema.GroupResource{Group: example.GroupName, Resource: "resource"}, 222 servers: []string{"http://127.0.0.1:10000", "https://127.0.0.1", "http://127.0.0.2"}, 223 wantConfigs: []storagebackend.Config{ 224 {Transport: storagebackend.TransportConfig{ServerList: defaultEtcdLocations}, Prefix: "/registry"}, 225 {Transport: storagebackend.TransportConfig{ServerList: []string{"http://127.0.0.1:10000", "https://127.0.0.1", "http://127.0.0.2"}}, Prefix: "/registry"}, 226 }, 227 }, 228 } 229 230 for i, test := range testCases { 231 defaultConfig := storagebackend.Config{ 232 Prefix: "/registry", 233 Transport: storagebackend.TransportConfig{ 234 ServerList: defaultEtcdLocations, 235 }, 236 } 237 storageFactory := NewDefaultStorageFactory(defaultConfig, "", codecs, NewDefaultResourceEncodingConfig(scheme), NewResourceConfig(), nil) 238 if test.resource != nil { 239 storageFactory.SetEtcdLocation(*test.resource, test.servers) 240 } 241 242 got := storageFactory.Configs() 243 if !reflect.DeepEqual(test.wantConfigs, got) { 244 t.Errorf("%d: expected %v, got %v", i, test.wantConfigs, got) 245 continue 246 } 247 } 248 } 249 250 var introducedLifecycles = map[reflect.Type]*apimachineryversion.Version{} 251 var removedLifecycles = map[reflect.Type]*apimachineryversion.Version{} 252 253 type fakeLifecycler[T, V any] struct { 254 metav1.TypeMeta 255 metav1.ObjectMeta 256 } 257 258 type removedLifecycler[T, V any] struct { 259 fakeLifecycler[T, V] 260 } 261 262 func (f *fakeLifecycler[T, V]) GetObjectKind() schema.ObjectKind { return f } 263 func (f *fakeLifecycler[T, V]) DeepCopyObject() runtime.Object { return f } 264 func (f *fakeLifecycler[T, V]) APILifecycleIntroduced() (major, minor int) { 265 if introduced, ok := introducedLifecycles[reflect.TypeOf(f)]; ok { 266 return int(introduced.Major()), int(introduced.Minor()) 267 } 268 panic("no lifecycle version set") 269 } 270 func (f *removedLifecycler[T, V]) APILifecycleRemoved() (major, minor int) { 271 if removed, ok := removedLifecycles[reflect.TypeOf(f)]; ok { 272 return int(removed.Major()), int(removed.Minor()) 273 } 274 panic("no lifecycle version set") 275 } 276 277 func registerFakeLifecycle[T, V any](sch *runtime.Scheme, group, introduced, removed string) { 278 f := fakeLifecycler[T, V]{} 279 280 introducedLifecycles[reflect.TypeOf(&f)] = apimachineryversion.MustParseSemantic(introduced) 281 282 var res runtime.Object 283 if removed != "" { 284 removedLifecycles[reflect.TypeOf(&f)] = apimachineryversion.MustParseSemantic(removed) 285 res = &removedLifecycler[T, V]{fakeLifecycler: f} 286 } else { 287 res = &f 288 } 289 290 var v V 291 var t T 292 sch.AddKnownTypeWithName( 293 schema.GroupVersionKind{ 294 Group: group, 295 Version: strings.ToLower(reflect.TypeOf(v).Name()), 296 Kind: reflect.TypeOf(t).Name(), 297 }, 298 res, 299 ) 300 301 // Also ensure internal version is registered 302 // If it is registertd multiple times, it will ignore subsequent registrations 303 internalInstance := &fakeLifecycler[T, struct{}]{} 304 sch.AddKnownTypeWithName( 305 schema.GroupVersionKind{ 306 Group: group, 307 Version: runtime.APIVersionInternal, 308 Kind: reflect.TypeOf(t).Name(), 309 }, 310 internalInstance, 311 ) 312 } 313 314 func TestStorageFactoryCompatibilityVersion(t *testing.T) { 315 // Creates a scheme with stub types for unit test 316 sch := runtime.NewScheme() 317 codecs := serializer.NewCodecFactory(sch) 318 319 type Internal = struct{} 320 type V1beta1 struct{} 321 type V1beta2 struct{} 322 type V1beta3 struct{} 323 type V1 struct{} 324 325 type Pod struct{} 326 type FlowSchema struct{} 327 type ValidatingAdmisisonPolicy struct{} 328 type CronJob struct{} 329 330 // Order dictates priority order 331 registerFakeLifecycle[FlowSchema, V1](sch, "flowcontrol.apiserver.k8s.io", "1.29.0", "") 332 registerFakeLifecycle[FlowSchema, V1beta3](sch, "flowcontrol.apiserver.k8s.io", "1.26.0", "1.32.0") 333 registerFakeLifecycle[FlowSchema, V1beta2](sch, "flowcontrol.apiserver.k8s.io", "1.23.0", "1.29.0") 334 registerFakeLifecycle[FlowSchema, V1beta1](sch, "flowcontrol.apiserver.k8s.io", "1.20.0", "1.26.0") 335 registerFakeLifecycle[CronJob, V1](sch, "batch", "1.21.0", "") 336 registerFakeLifecycle[CronJob, V1beta1](sch, "batch", "1.8.0", "1.21.0") 337 registerFakeLifecycle[ValidatingAdmisisonPolicy, V1](sch, "admissionregistration.k8s.io", "1.30.0", "") 338 registerFakeLifecycle[ValidatingAdmisisonPolicy, V1beta1](sch, "admissionregistration.k8s.io", "1.28.0", "1.34.0") 339 registerFakeLifecycle[Pod, V1](sch, "", "1.31.0", "") 340 341 // FlowSchema 342 // - v1beta1: 1.20.0 - 1.23.0 343 // - v1beta2: 1.23.0 - 1.26.0 344 // - v1beta3: 1.26.0 - 1.30.0 345 // - v1: 1.29.0+ 346 // CronJob 347 // - v1beta1: 1.8.0 - 1.21.0 348 // - v1: 1.21.0+ 349 // ValidatingAdmissionPolicy 350 // - v1beta1: 1.28.0 - 1.31.0 351 // - v1: 1.30.0+ 352 353 testcases := []struct { 354 effectiveVersion string 355 example runtime.Object 356 expectedVersion schema.GroupVersion 357 }{ 358 { 359 // Basic case. Beta version for long time 360 effectiveVersion: "1.14.0", 361 example: &fakeLifecycler[CronJob, Internal]{}, 362 expectedVersion: schema.GroupVersion{Group: "batch", Version: "v1beta1"}, 363 }, 364 { 365 // Basic case. Beta version for long time 366 effectiveVersion: "1.20.0", 367 example: &fakeLifecycler[CronJob, Internal]{}, 368 expectedVersion: schema.GroupVersion{Group: "batch", Version: "v1beta1"}, 369 }, 370 { 371 // Basic case. GA version for long time 372 effectiveVersion: "1.28.0", 373 example: &fakeLifecycler[CronJob, Internal]{}, 374 expectedVersion: schema.GroupVersion{Group: "batch", Version: "v1"}, 375 }, 376 { 377 // Basic core/v1 378 effectiveVersion: "1.31.0", 379 example: &fakeLifecycler[Pod, Internal]{}, 380 expectedVersion: schema.GroupVersion{Group: "", Version: "v1"}, 381 }, 382 { 383 // Corner case: 1.1.0 has no flowcontrol. Options are to error 384 // out or to use the latest version. This test assumes the latter. 385 effectiveVersion: "1.1.0", 386 example: &fakeLifecycler[FlowSchema, Internal]{}, 387 expectedVersion: schema.GroupVersion{Group: "flowcontrol.apiserver.k8s.io", Version: "v1"}, 388 }, 389 { 390 effectiveVersion: "1.21.0", 391 example: &fakeLifecycler[FlowSchema, Internal]{}, 392 expectedVersion: schema.GroupVersion{Group: "flowcontrol.apiserver.k8s.io", Version: "v1beta1"}, 393 }, 394 { 395 // v2Beta1 introduced this version, but minCompatibility should 396 // force v1beta1 397 effectiveVersion: "1.23.0", 398 example: &fakeLifecycler[FlowSchema, Internal]{}, 399 expectedVersion: schema.GroupVersion{Group: "flowcontrol.apiserver.k8s.io", Version: "v1beta1"}, 400 }, 401 { 402 effectiveVersion: "1.24.0", 403 example: &fakeLifecycler[FlowSchema, Internal]{}, 404 expectedVersion: schema.GroupVersion{Group: "flowcontrol.apiserver.k8s.io", Version: "v1beta2"}, 405 }, 406 { 407 effectiveVersion: "1.26.0", 408 example: &fakeLifecycler[FlowSchema, Internal]{}, 409 expectedVersion: schema.GroupVersion{Group: "flowcontrol.apiserver.k8s.io", Version: "v1beta2"}, 410 }, 411 { 412 effectiveVersion: "1.27.0", 413 example: &fakeLifecycler[FlowSchema, Internal]{}, 414 expectedVersion: schema.GroupVersion{Group: "flowcontrol.apiserver.k8s.io", Version: "v1beta3"}, 415 }, 416 { 417 // GA API introduced 1.29 but must keep storing in v1beta3 for downgrades 418 effectiveVersion: "1.29.0", 419 example: &fakeLifecycler[FlowSchema, Internal]{}, 420 expectedVersion: schema.GroupVersion{Group: "flowcontrol.apiserver.k8s.io", Version: "v1beta3"}, 421 }, 422 { 423 // Version after GA api is introduced 424 effectiveVersion: "1.30.0", 425 example: &fakeLifecycler[FlowSchema, Internal]{}, 426 expectedVersion: schema.GroupVersion{Group: "flowcontrol.apiserver.k8s.io", Version: "v1"}, 427 }, 428 { 429 effectiveVersion: "1.30.0", 430 example: &fakeLifecycler[ValidatingAdmisisonPolicy, Internal]{}, 431 expectedVersion: schema.GroupVersion{Group: "admissionregistration.k8s.io", Version: "v1beta1"}, 432 }, 433 { 434 effectiveVersion: "1.31.0", 435 example: &fakeLifecycler[ValidatingAdmisisonPolicy, Internal]{}, 436 expectedVersion: schema.GroupVersion{Group: "admissionregistration.k8s.io", Version: "v1"}, 437 }, 438 { 439 effectiveVersion: "1.29.0", 440 example: &fakeLifecycler[ValidatingAdmisisonPolicy, Internal]{}, 441 expectedVersion: schema.GroupVersion{Group: "admissionregistration.k8s.io", Version: "v1beta1"}, 442 }, 443 } 444 445 for _, tc := range testcases { 446 gvks, _, err := sch.ObjectKinds(tc.example) 447 if err != nil { 448 t.Fatalf("unexpected error: %v", err) 449 } 450 451 gvk := gvks[0] 452 t.Run(gvk.GroupKind().String()+"@"+tc.effectiveVersion, func(t *testing.T) { 453 config := NewDefaultResourceEncodingConfig(sch) 454 config.SetEffectiveVersion(version.NewEffectiveVersion(tc.effectiveVersion)) 455 f := NewDefaultStorageFactory( 456 storagebackend.Config{}, 457 "", 458 codecs, 459 config, 460 NewResourceConfig(), 461 nil) 462 463 cfg, err := f.NewConfig(schema.GroupResource{ 464 Group: gvk.Group, 465 Resource: gvk.Kind, // doesnt really matter here 466 }, tc.example) 467 if err != nil { 468 t.Fatalf("unexpected error: %v", err) 469 } 470 471 gvks, _, err := sch.ObjectKinds(tc.example) 472 if err != nil { 473 t.Fatalf("unexpected error: %v", err) 474 } 475 expectEncodeVersioner := runtime.NewMultiGroupVersioner(tc.expectedVersion, 476 schema.GroupKind{ 477 Group: gvks[0].Group, 478 }, schema.GroupKind{ 479 Group: gvks[0].Group, 480 }) 481 if cfg.EncodeVersioner.Identifier() != expectEncodeVersioner.Identifier() { 482 t.Errorf("expected %v, got %v", expectEncodeVersioner, cfg.EncodeVersioner) 483 } 484 }) 485 } 486 }