k8s.io/apiserver@v0.29.3/pkg/storage/etcd3/store_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 etcd3 18 19 import ( 20 "context" 21 "crypto/rand" 22 "fmt" 23 "io/ioutil" 24 "os" 25 "reflect" 26 "strings" 27 "sync/atomic" 28 "testing" 29 30 clientv3 "go.etcd.io/etcd/client/v3" 31 "go.etcd.io/etcd/server/v3/embed" 32 "google.golang.org/grpc/grpclog" 33 34 "k8s.io/apimachinery/pkg/api/apitesting" 35 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 36 "k8s.io/apimachinery/pkg/fields" 37 "k8s.io/apimachinery/pkg/labels" 38 "k8s.io/apimachinery/pkg/runtime" 39 "k8s.io/apimachinery/pkg/runtime/schema" 40 "k8s.io/apimachinery/pkg/runtime/serializer" 41 utilruntime "k8s.io/apimachinery/pkg/util/runtime" 42 "k8s.io/apiserver/pkg/apis/example" 43 examplev1 "k8s.io/apiserver/pkg/apis/example/v1" 44 "k8s.io/apiserver/pkg/storage" 45 "k8s.io/apiserver/pkg/storage/etcd3/testserver" 46 storagetesting "k8s.io/apiserver/pkg/storage/testing" 47 "k8s.io/apiserver/pkg/storage/value" 48 ) 49 50 var scheme = runtime.NewScheme() 51 var codecs = serializer.NewCodecFactory(scheme) 52 53 const defaultTestPrefix = "test!" 54 55 func init() { 56 metav1.AddToGroupVersion(scheme, metav1.SchemeGroupVersion) 57 utilruntime.Must(example.AddToScheme(scheme)) 58 utilruntime.Must(examplev1.AddToScheme(scheme)) 59 60 grpclog.SetLoggerV2(grpclog.NewLoggerV2(ioutil.Discard, ioutil.Discard, os.Stderr)) 61 } 62 63 func newPod() runtime.Object { 64 return &example.Pod{} 65 } 66 67 func newPodList() runtime.Object { 68 return &example.PodList{} 69 } 70 71 func checkStorageInvariants(etcdClient *clientv3.Client, codec runtime.Codec) storagetesting.KeyValidation { 72 return func(ctx context.Context, t *testing.T, key string) { 73 getResp, err := etcdClient.KV.Get(ctx, key) 74 if err != nil { 75 t.Fatalf("etcdClient.KV.Get failed: %v", err) 76 } 77 if len(getResp.Kvs) == 0 { 78 t.Fatalf("expecting non empty result on key: %s", key) 79 } 80 decoded, err := runtime.Decode(codec, getResp.Kvs[0].Value[len(defaultTestPrefix):]) 81 if err != nil { 82 t.Fatalf("expecting successful decode of object from %v\n%v", err, string(getResp.Kvs[0].Value)) 83 } 84 obj := decoded.(*example.Pod) 85 if obj.ResourceVersion != "" { 86 t.Errorf("stored object should have empty resource version") 87 } 88 if obj.SelfLink != "" { 89 t.Errorf("stored output should have empty selfLink") 90 } 91 } 92 } 93 94 func TestCreate(t *testing.T) { 95 ctx, store, etcdClient := testSetup(t) 96 storagetesting.RunTestCreate(ctx, t, store, checkStorageInvariants(etcdClient, store.codec)) 97 } 98 99 func TestCreateWithTTL(t *testing.T) { 100 ctx, store, _ := testSetup(t) 101 storagetesting.RunTestCreateWithTTL(ctx, t, store) 102 } 103 104 func TestCreateWithKeyExist(t *testing.T) { 105 ctx, store, _ := testSetup(t) 106 storagetesting.RunTestCreateWithKeyExist(ctx, t, store) 107 } 108 109 func TestGet(t *testing.T) { 110 ctx, store, _ := testSetup(t) 111 storagetesting.RunTestGet(ctx, t, store) 112 } 113 114 func TestUnconditionalDelete(t *testing.T) { 115 ctx, store, _ := testSetup(t) 116 storagetesting.RunTestUnconditionalDelete(ctx, t, store) 117 } 118 119 func TestConditionalDelete(t *testing.T) { 120 ctx, store, _ := testSetup(t) 121 storagetesting.RunTestConditionalDelete(ctx, t, store) 122 } 123 124 func TestDeleteWithSuggestion(t *testing.T) { 125 ctx, store, _ := testSetup(t) 126 storagetesting.RunTestDeleteWithSuggestion(ctx, t, store) 127 } 128 129 func TestDeleteWithSuggestionAndConflict(t *testing.T) { 130 ctx, store, _ := testSetup(t) 131 storagetesting.RunTestDeleteWithSuggestionAndConflict(ctx, t, store) 132 } 133 134 func TestDeleteWithSuggestionOfDeletedObject(t *testing.T) { 135 ctx, store, _ := testSetup(t) 136 storagetesting.RunTestDeleteWithSuggestionOfDeletedObject(ctx, t, store) 137 } 138 139 func TestValidateDeletionWithSuggestion(t *testing.T) { 140 ctx, store, _ := testSetup(t) 141 storagetesting.RunTestValidateDeletionWithSuggestion(ctx, t, store) 142 } 143 144 func TestValidateDeletionWithOnlySuggestionValid(t *testing.T) { 145 ctx, store, _ := testSetup(t) 146 storagetesting.RunTestValidateDeletionWithOnlySuggestionValid(ctx, t, store) 147 } 148 149 func TestDeleteWithConflict(t *testing.T) { 150 ctx, store, _ := testSetup(t) 151 storagetesting.RunTestDeleteWithConflict(ctx, t, store) 152 } 153 154 func TestPreconditionalDeleteWithSuggestion(t *testing.T) { 155 ctx, store, _ := testSetup(t) 156 storagetesting.RunTestPreconditionalDeleteWithSuggestion(ctx, t, store) 157 } 158 159 func TestPreconditionalDeleteWithSuggestionPass(t *testing.T) { 160 ctx, store, _ := testSetup(t) 161 storagetesting.RunTestPreconditionalDeleteWithOnlySuggestionPass(ctx, t, store) 162 } 163 164 func TestGetListNonRecursive(t *testing.T) { 165 ctx, store, _ := testSetup(t) 166 storagetesting.RunTestGetListNonRecursive(ctx, t, store) 167 } 168 169 type storeWithPrefixTransformer struct { 170 *store 171 } 172 173 func (s *storeWithPrefixTransformer) UpdatePrefixTransformer(modifier storagetesting.PrefixTransformerModifier) func() { 174 originalTransformer := s.transformer.(*storagetesting.PrefixTransformer) 175 transformer := *originalTransformer 176 s.transformer = modifier(&transformer) 177 s.watcher.transformer = modifier(&transformer) 178 return func() { 179 s.transformer = originalTransformer 180 s.watcher.transformer = originalTransformer 181 } 182 } 183 184 func TestGuaranteedUpdate(t *testing.T) { 185 ctx, store, etcdClient := testSetup(t) 186 storagetesting.RunTestGuaranteedUpdate(ctx, t, &storeWithPrefixTransformer{store}, checkStorageInvariants(etcdClient, store.codec)) 187 } 188 189 func TestGuaranteedUpdateWithTTL(t *testing.T) { 190 ctx, store, _ := testSetup(t) 191 storagetesting.RunTestGuaranteedUpdateWithTTL(ctx, t, store) 192 } 193 194 func TestGuaranteedUpdateChecksStoredData(t *testing.T) { 195 ctx, store, _ := testSetup(t) 196 storagetesting.RunTestGuaranteedUpdateChecksStoredData(ctx, t, &storeWithPrefixTransformer{store}) 197 } 198 199 func TestGuaranteedUpdateWithConflict(t *testing.T) { 200 ctx, store, _ := testSetup(t) 201 storagetesting.RunTestGuaranteedUpdateWithConflict(ctx, t, store) 202 } 203 204 func TestGuaranteedUpdateWithSuggestionAndConflict(t *testing.T) { 205 ctx, store, _ := testSetup(t) 206 storagetesting.RunTestGuaranteedUpdateWithSuggestionAndConflict(ctx, t, store) 207 } 208 209 func TestTransformationFailure(t *testing.T) { 210 ctx, store, _ := testSetup(t) 211 storagetesting.RunTestTransformationFailure(ctx, t, &storeWithPrefixTransformer{store}) 212 } 213 214 func TestList(t *testing.T) { 215 ctx, store, client := testSetup(t) 216 storagetesting.RunTestList(ctx, t, store, compactStorage(client), false) 217 } 218 219 func checkStorageCallsInvariants(transformer *storagetesting.PrefixTransformer, recorder *clientRecorder) storagetesting.CallsValidation { 220 return func(t *testing.T, pageSize, estimatedProcessedObjects uint64) { 221 if reads := transformer.GetReadsAndReset(); reads != estimatedProcessedObjects { 222 t.Errorf("unexpected reads: %d, expected: %d", reads, estimatedProcessedObjects) 223 } 224 estimatedGetCalls := uint64(1) 225 if pageSize != 0 { 226 // We expect that kube-apiserver will be increasing page sizes 227 // if not full pages are received, so we should see significantly less 228 // than 1000 pages (which would be result of talking to etcd with page size 229 // copied from pred.Limit). 230 // The expected number of calls is n+1 where n is the smallest n so that: 231 // pageSize + pageSize * 2 + pageSize * 4 + ... + pageSize * 2^n >= podCount. 232 // For pageSize = 1, podCount = 1000, we get n+1 = 10, 2 ^ 10 = 1024. 233 currLimit := pageSize 234 for sum := uint64(1); sum < estimatedProcessedObjects; { 235 currLimit *= 2 236 if currLimit > maxLimit { 237 currLimit = maxLimit 238 } 239 sum += currLimit 240 estimatedGetCalls++ 241 } 242 } 243 if reads := recorder.GetReadsAndReset(); reads != estimatedGetCalls { 244 t.Errorf("unexpected reads: %d", reads) 245 } 246 } 247 } 248 249 func TestListContinuation(t *testing.T) { 250 ctx, store, etcdClient := testSetup(t, withRecorder()) 251 validation := checkStorageCallsInvariants( 252 store.transformer.(*storagetesting.PrefixTransformer), etcdClient.KV.(*clientRecorder)) 253 storagetesting.RunTestListContinuation(ctx, t, store, validation) 254 } 255 256 func TestListPaginationRareObject(t *testing.T) { 257 ctx, store, etcdClient := testSetup(t, withRecorder()) 258 validation := checkStorageCallsInvariants( 259 store.transformer.(*storagetesting.PrefixTransformer), etcdClient.KV.(*clientRecorder)) 260 storagetesting.RunTestListPaginationRareObject(ctx, t, store, validation) 261 } 262 263 func TestListContinuationWithFilter(t *testing.T) { 264 ctx, store, etcdClient := testSetup(t, withRecorder()) 265 validation := checkStorageCallsInvariants( 266 store.transformer.(*storagetesting.PrefixTransformer), etcdClient.KV.(*clientRecorder)) 267 storagetesting.RunTestListContinuationWithFilter(ctx, t, store, validation) 268 } 269 270 func compactStorage(etcdClient *clientv3.Client) storagetesting.Compaction { 271 return func(ctx context.Context, t *testing.T, resourceVersion string) { 272 versioner := storage.APIObjectVersioner{} 273 rv, err := versioner.ParseResourceVersion(resourceVersion) 274 if err != nil { 275 t.Fatal(err) 276 } 277 if _, _, err = compact(ctx, etcdClient, 0, int64(rv)); err != nil { 278 t.Fatalf("Unable to compact, %v", err) 279 } 280 } 281 } 282 283 func TestListInconsistentContinuation(t *testing.T) { 284 ctx, store, client := testSetup(t) 285 storagetesting.RunTestListInconsistentContinuation(ctx, t, store, compactStorage(client)) 286 } 287 288 func TestConsistentList(t *testing.T) { 289 ctx, store, _ := testSetup(t) 290 storagetesting.RunTestConsistentList(ctx, t, &storeWithPrefixTransformer{store}) 291 } 292 293 func TestCount(t *testing.T) { 294 ctx, store, _ := testSetup(t) 295 storagetesting.RunTestCount(ctx, t, store) 296 } 297 298 // ======================================================================= 299 // Implementation-specific tests are following. 300 // The following tests are exercising the details of the implementation 301 // not the actual user-facing contract of storage interface. 302 // As such, they may focus e.g. on non-functional aspects like performance 303 // impact. 304 // ======================================================================= 305 306 func TestPrefix(t *testing.T) { 307 testcases := map[string]string{ 308 "custom/prefix": "/custom/prefix/", 309 "/custom//prefix//": "/custom/prefix/", 310 "/registry": "/registry/", 311 } 312 for configuredPrefix, effectivePrefix := range testcases { 313 _, store, _ := testSetup(t, withPrefix(configuredPrefix)) 314 if store.pathPrefix != effectivePrefix { 315 t.Errorf("configured prefix of %s, expected effective prefix of %s, got %s", configuredPrefix, effectivePrefix, store.pathPrefix) 316 } 317 } 318 } 319 320 func Test_growSlice(t *testing.T) { 321 type args struct { 322 initialCapacity int 323 initialLen int 324 v reflect.Value 325 maxCapacity int 326 sizes []int 327 } 328 tests := []struct { 329 name string 330 args args 331 cap int 332 len int 333 }{ 334 { 335 name: "empty", 336 args: args{v: reflect.ValueOf([]example.Pod{})}, 337 cap: 0, 338 }, 339 { 340 name: "no sizes", 341 args: args{v: reflect.ValueOf([]example.Pod{}), maxCapacity: 10}, 342 cap: 10, 343 }, 344 { 345 name: "above maxCapacity", 346 args: args{v: reflect.ValueOf([]example.Pod{}), maxCapacity: 10, sizes: []int{1, 12}}, 347 cap: 10, 348 }, 349 { 350 name: "takes max", 351 args: args{v: reflect.ValueOf([]example.Pod{}), maxCapacity: 10, sizes: []int{8, 4}}, 352 cap: 8, 353 }, 354 { 355 name: "with existing capacity above max", 356 args: args{initialCapacity: 12, maxCapacity: 10, sizes: []int{8, 4}}, 357 cap: 12, 358 }, 359 { 360 name: "with existing capacity below max", 361 args: args{initialCapacity: 5, maxCapacity: 10, sizes: []int{8, 4}}, 362 cap: 8, 363 }, 364 { 365 name: "with existing capacity and length above max", 366 args: args{initialCapacity: 12, initialLen: 5, maxCapacity: 10, sizes: []int{8, 4}}, 367 cap: 12, 368 len: 5, 369 }, 370 { 371 name: "with existing capacity and length below max", 372 args: args{initialCapacity: 5, initialLen: 3, maxCapacity: 10, sizes: []int{8, 4}}, 373 cap: 8, 374 len: 3, 375 }, 376 } 377 for _, tt := range tests { 378 t.Run(tt.name, func(t *testing.T) { 379 if tt.args.initialCapacity > 0 { 380 val := make([]example.Pod, tt.args.initialLen, tt.args.initialCapacity) 381 for i := 0; i < tt.args.initialLen; i++ { 382 val[i].Name = fmt.Sprintf("test-%d", i) 383 } 384 tt.args.v = reflect.ValueOf(val) 385 } 386 // reflection requires that the value be addressable in order to call set, 387 // so we must ensure the value we created is available on the heap (not a problem 388 // for normal usage) 389 if !tt.args.v.CanAddr() { 390 x := reflect.New(tt.args.v.Type()) 391 x.Elem().Set(tt.args.v) 392 tt.args.v = x.Elem() 393 } 394 growSlice(tt.args.v, tt.args.maxCapacity, tt.args.sizes...) 395 if tt.cap != tt.args.v.Cap() { 396 t.Errorf("Unexpected capacity: got=%d want=%d", tt.args.v.Cap(), tt.cap) 397 } 398 if tt.len != tt.args.v.Len() { 399 t.Errorf("Unexpected length: got=%d want=%d", tt.args.v.Len(), tt.len) 400 } 401 for i := 0; i < tt.args.v.Len(); i++ { 402 nameWanted := fmt.Sprintf("test-%d", i) 403 val := tt.args.v.Index(i).Interface() 404 pod, ok := val.(example.Pod) 405 if !ok || pod.Name != nameWanted { 406 t.Errorf("Unexpected element value: got=%s, want=%s", pod.Name, nameWanted) 407 } 408 } 409 }) 410 } 411 } 412 413 func TestLeaseMaxObjectCount(t *testing.T) { 414 ctx, store, _ := testSetup(t, withLeaseConfig(LeaseManagerConfig{ 415 ReuseDurationSeconds: defaultLeaseReuseDurationSeconds, 416 MaxObjectCount: 2, 417 })) 418 419 obj := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}} 420 out := &example.Pod{} 421 422 testCases := []struct { 423 key string 424 expectAttachedCount int64 425 }{ 426 { 427 key: "testkey1", 428 expectAttachedCount: 1, 429 }, 430 { 431 key: "testkey2", 432 expectAttachedCount: 2, 433 }, 434 { 435 key: "testkey3", 436 // We assume each time has 1 object attached to the lease 437 // so after granting a new lease, the recorded count is set to 1 438 expectAttachedCount: 1, 439 }, 440 } 441 442 for _, tc := range testCases { 443 err := store.Create(ctx, tc.key, obj, out, 120) 444 if err != nil { 445 t.Fatalf("Set failed: %v", err) 446 } 447 if store.leaseManager.leaseAttachedObjectCount != tc.expectAttachedCount { 448 t.Errorf("Lease manager recorded count %v should be %v", store.leaseManager.leaseAttachedObjectCount, tc.expectAttachedCount) 449 } 450 } 451 } 452 453 // =================================================== 454 // Test-setup related function are following. 455 // =================================================== 456 457 func newTestLeaseManagerConfig() LeaseManagerConfig { 458 cfg := NewDefaultLeaseManagerConfig() 459 // As 30s is the default timeout for testing in global configuration, 460 // we cannot wait longer than that in a single time: change it to 1s 461 // for testing purposes. See wait.ForeverTestTimeout 462 cfg.ReuseDurationSeconds = 1 463 return cfg 464 } 465 466 func newTestTransformer() value.Transformer { 467 return storagetesting.NewPrefixTransformer([]byte(defaultTestPrefix), false) 468 } 469 470 type clientRecorder struct { 471 reads uint64 472 clientv3.KV 473 } 474 475 func (r *clientRecorder) Get(ctx context.Context, key string, opts ...clientv3.OpOption) (*clientv3.GetResponse, error) { 476 atomic.AddUint64(&r.reads, 1) 477 return r.KV.Get(ctx, key, opts...) 478 } 479 480 func (r *clientRecorder) GetReadsAndReset() uint64 { 481 return atomic.SwapUint64(&r.reads, 0) 482 } 483 484 type setupOptions struct { 485 client func(testing.TB) *clientv3.Client 486 codec runtime.Codec 487 newFunc func() runtime.Object 488 newListFunc func() runtime.Object 489 prefix string 490 resourcePrefix string 491 groupResource schema.GroupResource 492 transformer value.Transformer 493 leaseConfig LeaseManagerConfig 494 495 recorderEnabled bool 496 } 497 498 type setupOption func(*setupOptions) 499 500 func withClientConfig(config *embed.Config) setupOption { 501 return func(options *setupOptions) { 502 options.client = func(t testing.TB) *clientv3.Client { 503 return testserver.RunEtcd(t, config) 504 } 505 } 506 } 507 508 func withPrefix(prefix string) setupOption { 509 return func(options *setupOptions) { 510 options.prefix = prefix 511 } 512 } 513 514 func withLeaseConfig(leaseConfig LeaseManagerConfig) setupOption { 515 return func(options *setupOptions) { 516 options.leaseConfig = leaseConfig 517 } 518 } 519 520 func withRecorder() setupOption { 521 return func(options *setupOptions) { 522 options.recorderEnabled = true 523 } 524 } 525 526 func withDefaults(options *setupOptions) { 527 options.client = func(t testing.TB) *clientv3.Client { 528 return testserver.RunEtcd(t, nil) 529 } 530 options.codec = apitesting.TestCodec(codecs, examplev1.SchemeGroupVersion) 531 options.newFunc = newPod 532 options.newListFunc = newPodList 533 options.prefix = "" 534 options.resourcePrefix = "/pods" 535 options.groupResource = schema.GroupResource{Resource: "pods"} 536 options.transformer = newTestTransformer() 537 options.leaseConfig = newTestLeaseManagerConfig() 538 } 539 540 var _ setupOption = withDefaults 541 542 func testSetup(t testing.TB, opts ...setupOption) (context.Context, *store, *clientv3.Client) { 543 setupOpts := setupOptions{} 544 opts = append([]setupOption{withDefaults}, opts...) 545 for _, opt := range opts { 546 opt(&setupOpts) 547 } 548 client := setupOpts.client(t) 549 if setupOpts.recorderEnabled { 550 client.KV = &clientRecorder{KV: client.KV} 551 } 552 store := newStore( 553 client, 554 setupOpts.codec, 555 setupOpts.newFunc, 556 setupOpts.newListFunc, 557 setupOpts.prefix, 558 setupOpts.resourcePrefix, 559 setupOpts.groupResource, 560 setupOpts.transformer, 561 setupOpts.leaseConfig, 562 ) 563 ctx := context.Background() 564 return ctx, store, client 565 } 566 567 func TestValidateKey(t *testing.T) { 568 validKeys := []string{ 569 "/foo/bar/baz/a.b.c/", 570 "/foo", 571 "foo/bar/baz", 572 "/foo/bar..baz/", 573 "/foo/bar..", 574 "foo", 575 "foo/bar", 576 "/foo/bar/", 577 } 578 invalidKeys := []string{ 579 "/foo/bar/../a.b.c/", 580 "..", 581 "/..", 582 "../", 583 "/foo/bar/..", 584 "../foo/bar", 585 "/../foo", 586 "/foo/bar/../", 587 ".", 588 "/.", 589 "./", 590 "/./", 591 "/foo/.", 592 "./bar", 593 "/foo/./bar/", 594 } 595 const ( 596 pathPrefix = "/first/second" 597 expectPrefix = pathPrefix + "/" 598 ) 599 _, store, _ := testSetup(t, withPrefix(pathPrefix)) 600 601 for _, key := range validKeys { 602 k, err := store.prepareKey(key) 603 if err != nil { 604 t.Errorf("key %q should be valid; unexpected error: %v", key, err) 605 } else if !strings.HasPrefix(k, expectPrefix) { 606 t.Errorf("key %q should have prefix %q", k, expectPrefix) 607 } 608 } 609 610 for _, key := range invalidKeys { 611 _, err := store.prepareKey(key) 612 if err == nil { 613 t.Errorf("key %q should be invalid", key) 614 } 615 } 616 } 617 618 func TestInvalidKeys(t *testing.T) { 619 const invalidKey = "/foo/bar/../baz" 620 expectedError := fmt.Sprintf("invalid key: %q", invalidKey) 621 622 expectInvalidKey := func(methodName string, err error) { 623 if err == nil { 624 t.Errorf("[%s] expected invalid key error; got nil", methodName) 625 } else if err.Error() != expectedError { 626 t.Errorf("[%s] expected invalid key error; got %v", methodName, err) 627 } 628 } 629 630 ctx, store, _ := testSetup(t) 631 expectInvalidKey("Create", store.Create(ctx, invalidKey, nil, nil, 0)) 632 expectInvalidKey("Delete", store.Delete(ctx, invalidKey, nil, nil, nil, nil)) 633 _, watchErr := store.Watch(ctx, invalidKey, storage.ListOptions{}) 634 expectInvalidKey("Watch", watchErr) 635 expectInvalidKey("Get", store.Get(ctx, invalidKey, storage.GetOptions{}, nil)) 636 expectInvalidKey("GetList", store.GetList(ctx, invalidKey, storage.ListOptions{}, nil)) 637 expectInvalidKey("GuaranteedUpdate", store.GuaranteedUpdate(ctx, invalidKey, nil, true, nil, nil, nil)) 638 _, countErr := store.Count(invalidKey) 639 expectInvalidKey("Count", countErr) 640 } 641 642 func BenchmarkStore_GetList(b *testing.B) { 643 generateBigPod := func(index int, total int, expect int) runtime.Object { 644 l := map[string]string{} 645 if index%(total/expect) == 0 { 646 l["foo"] = "bar" 647 } 648 terminationGracePeriodSeconds := int64(42) 649 activeDeadlineSeconds := int64(42) 650 pod := &examplev1.Pod{ 651 ObjectMeta: metav1.ObjectMeta{ 652 Labels: l, 653 }, 654 Spec: examplev1.PodSpec{ 655 RestartPolicy: examplev1.RestartPolicy("Always"), 656 TerminationGracePeriodSeconds: &terminationGracePeriodSeconds, 657 ActiveDeadlineSeconds: &activeDeadlineSeconds, 658 NodeSelector: map[string]string{}, 659 ServiceAccountName: "demo-sa", 660 }, 661 } 662 pod.Name = fmt.Sprintf("object-%d", index) 663 data := make([]byte, 1024*2, 1024*2) // 2k labels 664 rand.Read(data) 665 pod.Spec.NodeSelector["key"] = string(data) 666 return pod 667 } 668 testCases := []struct { 669 name string 670 objectNum int 671 expectNum int 672 selector labels.Selector 673 newObjectFunc func(index int, total int, expect int) runtime.Object 674 newListObjectFunc func() runtime.Object 675 }{ 676 { 677 name: "pick 50 pods out of 5000 pod", 678 objectNum: 5000, 679 expectNum: 50, 680 selector: labels.SelectorFromSet(map[string]string{"foo": "bar"}), 681 newObjectFunc: generateBigPod, 682 newListObjectFunc: func() runtime.Object { return &examplev1.PodList{} }, 683 }, 684 { 685 name: "pick 500 pods out of 5000 pod", 686 objectNum: 5000, 687 expectNum: 500, 688 selector: labels.SelectorFromSet(map[string]string{"foo": "bar"}), 689 newObjectFunc: generateBigPod, 690 newListObjectFunc: func() runtime.Object { return &examplev1.PodList{} }, 691 }, 692 { 693 name: "pick 1000 pods out of 5000 pod", 694 objectNum: 5000, 695 expectNum: 1000, 696 selector: labels.SelectorFromSet(map[string]string{"foo": "bar"}), 697 newObjectFunc: generateBigPod, 698 newListObjectFunc: func() runtime.Object { return &examplev1.PodList{} }, 699 }, 700 { 701 name: "pick 2500 pods out of 5000 pod", 702 objectNum: 5000, 703 expectNum: 2500, 704 selector: labels.SelectorFromSet(map[string]string{"foo": "bar"}), 705 newObjectFunc: generateBigPod, 706 newListObjectFunc: func() runtime.Object { return &examplev1.PodList{} }, 707 }, 708 { 709 name: "pick 5000 pods out of 5000 pod", 710 objectNum: 5000, 711 expectNum: 5000, 712 selector: labels.SelectorFromSet(map[string]string{"foo": "bar"}), 713 newObjectFunc: generateBigPod, 714 newListObjectFunc: func() runtime.Object { return &examplev1.PodList{} }, 715 }, 716 } 717 for _, tc := range testCases { 718 b.Run(tc.name, func(b *testing.B) { 719 // booting etcd instance 720 ctx, store, etcdClient := testSetup(b) 721 defer etcdClient.Close() 722 723 // make fake objects.. 724 dir := "/testing" 725 originalRevision := "" 726 for i := 0; i < tc.objectNum; i++ { 727 obj := tc.newObjectFunc(i, tc.objectNum, tc.expectNum) 728 o := obj.(metav1.Object) 729 key := fmt.Sprintf("/testing/testkey/%s", o.GetName()) 730 out := tc.newObjectFunc(i, tc.objectNum, tc.expectNum) 731 if err := store.Create(ctx, key, obj, out, 0); err != nil { 732 b.Fatalf("Set failed: %v", err) 733 } 734 originalRevision = out.(metav1.Object).GetResourceVersion() 735 } 736 737 // prepare result and pred 738 pred := storage.SelectionPredicate{ 739 Label: tc.selector, 740 Field: fields.Everything(), 741 GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { 742 pod, ok := obj.(*examplev1.Pod) 743 if !ok { 744 return nil, nil, fmt.Errorf("not a pod") 745 } 746 return pod.ObjectMeta.Labels, fields.Set{ 747 "metadata.name": pod.Name, 748 }, nil 749 }, 750 } 751 752 // now we start benchmarking 753 b.ResetTimer() 754 for i := 0; i < b.N; i++ { 755 list := tc.newListObjectFunc() 756 if err := store.GetList(ctx, dir, storage.ListOptions{Predicate: pred, Recursive: true}, list); err != nil { 757 b.Errorf("Unexpected List error: %v", err) 758 } 759 listObject := list.(*examplev1.PodList) 760 if originalRevision != listObject.GetResourceVersion() { 761 b.Fatalf("original revision (%s) did not match final revision after linearized reads (%s)", originalRevision, listObject.GetResourceVersion()) 762 } 763 if len(listObject.Items) != tc.expectNum { 764 b.Fatalf("expect (%d) items but got (%d)", tc.expectNum, len(listObject.Items)) 765 } 766 } 767 }) 768 } 769 }