k8s.io/apiserver@v0.31.1/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, client := testSetup(t) 166 storagetesting.RunTestGetListNonRecursive(ctx, t, compactStorage(client), store) 167 } 168 169 func TestGetListRecursivePrefix(t *testing.T) { 170 ctx, store, _ := testSetup(t) 171 storagetesting.RunTestGetListRecursivePrefix(ctx, t, store) 172 } 173 174 type storeWithPrefixTransformer struct { 175 *store 176 } 177 178 func (s *storeWithPrefixTransformer) UpdatePrefixTransformer(modifier storagetesting.PrefixTransformerModifier) func() { 179 originalTransformer := s.transformer.(*storagetesting.PrefixTransformer) 180 transformer := *originalTransformer 181 s.transformer = modifier(&transformer) 182 s.watcher.transformer = modifier(&transformer) 183 return func() { 184 s.transformer = originalTransformer 185 s.watcher.transformer = originalTransformer 186 } 187 } 188 189 func TestGuaranteedUpdate(t *testing.T) { 190 ctx, store, etcdClient := testSetup(t) 191 storagetesting.RunTestGuaranteedUpdate(ctx, t, &storeWithPrefixTransformer{store}, checkStorageInvariants(etcdClient, store.codec)) 192 } 193 194 func TestGuaranteedUpdateWithTTL(t *testing.T) { 195 ctx, store, _ := testSetup(t) 196 storagetesting.RunTestGuaranteedUpdateWithTTL(ctx, t, store) 197 } 198 199 func TestGuaranteedUpdateChecksStoredData(t *testing.T) { 200 ctx, store, _ := testSetup(t) 201 storagetesting.RunTestGuaranteedUpdateChecksStoredData(ctx, t, &storeWithPrefixTransformer{store}) 202 } 203 204 func TestGuaranteedUpdateWithConflict(t *testing.T) { 205 ctx, store, _ := testSetup(t) 206 storagetesting.RunTestGuaranteedUpdateWithConflict(ctx, t, store) 207 } 208 209 func TestGuaranteedUpdateWithSuggestionAndConflict(t *testing.T) { 210 ctx, store, _ := testSetup(t) 211 storagetesting.RunTestGuaranteedUpdateWithSuggestionAndConflict(ctx, t, store) 212 } 213 214 func TestTransformationFailure(t *testing.T) { 215 ctx, store, _ := testSetup(t) 216 storagetesting.RunTestTransformationFailure(ctx, t, &storeWithPrefixTransformer{store}) 217 } 218 219 func TestList(t *testing.T) { 220 ctx, store, client := testSetup(t) 221 storagetesting.RunTestList(ctx, t, store, compactStorage(client), false) 222 } 223 224 func TestConsistentList(t *testing.T) { 225 ctx, store, client := testSetup(t) 226 storagetesting.RunTestConsistentList(ctx, t, store, compactStorage(client), false, true) 227 } 228 229 func checkStorageCallsInvariants(transformer *storagetesting.PrefixTransformer, recorder *clientRecorder) storagetesting.CallsValidation { 230 return func(t *testing.T, pageSize, estimatedProcessedObjects uint64) { 231 if reads := transformer.GetReadsAndReset(); reads != estimatedProcessedObjects { 232 t.Errorf("unexpected reads: %d, expected: %d", reads, estimatedProcessedObjects) 233 } 234 estimatedGetCalls := uint64(1) 235 if pageSize != 0 { 236 // We expect that kube-apiserver will be increasing page sizes 237 // if not full pages are received, so we should see significantly less 238 // than 1000 pages (which would be result of talking to etcd with page size 239 // copied from pred.Limit). 240 // The expected number of calls is n+1 where n is the smallest n so that: 241 // pageSize + pageSize * 2 + pageSize * 4 + ... + pageSize * 2^n >= podCount. 242 // For pageSize = 1, podCount = 1000, we get n+1 = 10, 2 ^ 10 = 1024. 243 currLimit := pageSize 244 for sum := uint64(1); sum < estimatedProcessedObjects; { 245 currLimit *= 2 246 if currLimit > maxLimit { 247 currLimit = maxLimit 248 } 249 sum += currLimit 250 estimatedGetCalls++ 251 } 252 } 253 if reads := recorder.GetReadsAndReset(); reads != estimatedGetCalls { 254 t.Errorf("unexpected reads: %d", reads) 255 } 256 } 257 } 258 259 func TestListContinuation(t *testing.T) { 260 ctx, store, etcdClient := testSetup(t, withRecorder()) 261 validation := checkStorageCallsInvariants( 262 store.transformer.(*storagetesting.PrefixTransformer), etcdClient.KV.(*clientRecorder)) 263 storagetesting.RunTestListContinuation(ctx, t, store, validation) 264 } 265 266 func TestListPaginationRareObject(t *testing.T) { 267 ctx, store, etcdClient := testSetup(t, withRecorder()) 268 validation := checkStorageCallsInvariants( 269 store.transformer.(*storagetesting.PrefixTransformer), etcdClient.KV.(*clientRecorder)) 270 storagetesting.RunTestListPaginationRareObject(ctx, t, store, validation) 271 } 272 273 func TestListContinuationWithFilter(t *testing.T) { 274 ctx, store, etcdClient := testSetup(t, withRecorder()) 275 validation := checkStorageCallsInvariants( 276 store.transformer.(*storagetesting.PrefixTransformer), etcdClient.KV.(*clientRecorder)) 277 storagetesting.RunTestListContinuationWithFilter(ctx, t, store, validation) 278 } 279 280 func compactStorage(etcdClient *clientv3.Client) storagetesting.Compaction { 281 return func(ctx context.Context, t *testing.T, resourceVersion string) { 282 versioner := storage.APIObjectVersioner{} 283 rv, err := versioner.ParseResourceVersion(resourceVersion) 284 if err != nil { 285 t.Fatal(err) 286 } 287 if _, _, err = compact(ctx, etcdClient, 0, int64(rv)); err != nil { 288 t.Fatalf("Unable to compact, %v", err) 289 } 290 } 291 } 292 293 func TestListInconsistentContinuation(t *testing.T) { 294 ctx, store, client := testSetup(t) 295 storagetesting.RunTestListInconsistentContinuation(ctx, t, store, compactStorage(client)) 296 } 297 298 func TestListResourceVersionMatch(t *testing.T) { 299 ctx, store, _ := testSetup(t) 300 storagetesting.RunTestListResourceVersionMatch(ctx, t, &storeWithPrefixTransformer{store}) 301 } 302 303 func TestCount(t *testing.T) { 304 ctx, store, _ := testSetup(t) 305 storagetesting.RunTestCount(ctx, t, store) 306 } 307 308 // ======================================================================= 309 // Implementation-specific tests are following. 310 // The following tests are exercising the details of the implementation 311 // not the actual user-facing contract of storage interface. 312 // As such, they may focus e.g. on non-functional aspects like performance 313 // impact. 314 // ======================================================================= 315 316 func TestPrefix(t *testing.T) { 317 testcases := map[string]string{ 318 "custom/prefix": "/custom/prefix/", 319 "/custom//prefix//": "/custom/prefix/", 320 "/registry": "/registry/", 321 } 322 for configuredPrefix, effectivePrefix := range testcases { 323 _, store, _ := testSetup(t, withPrefix(configuredPrefix)) 324 if store.pathPrefix != effectivePrefix { 325 t.Errorf("configured prefix of %s, expected effective prefix of %s, got %s", configuredPrefix, effectivePrefix, store.pathPrefix) 326 } 327 } 328 } 329 330 func Test_growSlice(t *testing.T) { 331 type args struct { 332 initialCapacity int 333 initialLen int 334 v reflect.Value 335 maxCapacity int 336 sizes []int 337 } 338 tests := []struct { 339 name string 340 args args 341 cap int 342 len int 343 }{ 344 { 345 name: "empty", 346 args: args{v: reflect.ValueOf([]example.Pod{})}, 347 cap: 0, 348 }, 349 { 350 name: "no sizes", 351 args: args{v: reflect.ValueOf([]example.Pod{}), maxCapacity: 10}, 352 cap: 10, 353 }, 354 { 355 name: "above maxCapacity", 356 args: args{v: reflect.ValueOf([]example.Pod{}), maxCapacity: 10, sizes: []int{1, 12}}, 357 cap: 10, 358 }, 359 { 360 name: "takes max", 361 args: args{v: reflect.ValueOf([]example.Pod{}), maxCapacity: 10, sizes: []int{8, 4}}, 362 cap: 8, 363 }, 364 { 365 name: "with existing capacity above max", 366 args: args{initialCapacity: 12, maxCapacity: 10, sizes: []int{8, 4}}, 367 cap: 12, 368 }, 369 { 370 name: "with existing capacity below max", 371 args: args{initialCapacity: 5, maxCapacity: 10, sizes: []int{8, 4}}, 372 cap: 8, 373 }, 374 { 375 name: "with existing capacity and length above max", 376 args: args{initialCapacity: 12, initialLen: 5, maxCapacity: 10, sizes: []int{8, 4}}, 377 cap: 12, 378 len: 5, 379 }, 380 { 381 name: "with existing capacity and length below max", 382 args: args{initialCapacity: 5, initialLen: 3, maxCapacity: 10, sizes: []int{8, 4}}, 383 cap: 8, 384 len: 3, 385 }, 386 } 387 for _, tt := range tests { 388 t.Run(tt.name, func(t *testing.T) { 389 if tt.args.initialCapacity > 0 { 390 val := make([]example.Pod, tt.args.initialLen, tt.args.initialCapacity) 391 for i := 0; i < tt.args.initialLen; i++ { 392 val[i].Name = fmt.Sprintf("test-%d", i) 393 } 394 tt.args.v = reflect.ValueOf(val) 395 } 396 // reflection requires that the value be addressable in order to call set, 397 // so we must ensure the value we created is available on the heap (not a problem 398 // for normal usage) 399 if !tt.args.v.CanAddr() { 400 x := reflect.New(tt.args.v.Type()) 401 x.Elem().Set(tt.args.v) 402 tt.args.v = x.Elem() 403 } 404 growSlice(tt.args.v, tt.args.maxCapacity, tt.args.sizes...) 405 if tt.cap != tt.args.v.Cap() { 406 t.Errorf("Unexpected capacity: got=%d want=%d", tt.args.v.Cap(), tt.cap) 407 } 408 if tt.len != tt.args.v.Len() { 409 t.Errorf("Unexpected length: got=%d want=%d", tt.args.v.Len(), tt.len) 410 } 411 for i := 0; i < tt.args.v.Len(); i++ { 412 nameWanted := fmt.Sprintf("test-%d", i) 413 val := tt.args.v.Index(i).Interface() 414 pod, ok := val.(example.Pod) 415 if !ok || pod.Name != nameWanted { 416 t.Errorf("Unexpected element value: got=%s, want=%s", pod.Name, nameWanted) 417 } 418 } 419 }) 420 } 421 } 422 423 func TestLeaseMaxObjectCount(t *testing.T) { 424 ctx, store, _ := testSetup(t, withLeaseConfig(LeaseManagerConfig{ 425 ReuseDurationSeconds: defaultLeaseReuseDurationSeconds, 426 MaxObjectCount: 2, 427 })) 428 429 obj := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}} 430 out := &example.Pod{} 431 432 testCases := []struct { 433 key string 434 expectAttachedCount int64 435 }{ 436 { 437 key: "testkey1", 438 expectAttachedCount: 1, 439 }, 440 { 441 key: "testkey2", 442 expectAttachedCount: 2, 443 }, 444 { 445 key: "testkey3", 446 // We assume each time has 1 object attached to the lease 447 // so after granting a new lease, the recorded count is set to 1 448 expectAttachedCount: 1, 449 }, 450 } 451 452 for _, tc := range testCases { 453 err := store.Create(ctx, tc.key, obj, out, 120) 454 if err != nil { 455 t.Fatalf("Set failed: %v", err) 456 } 457 if store.leaseManager.leaseAttachedObjectCount != tc.expectAttachedCount { 458 t.Errorf("Lease manager recorded count %v should be %v", store.leaseManager.leaseAttachedObjectCount, tc.expectAttachedCount) 459 } 460 } 461 } 462 463 // =================================================== 464 // Test-setup related function are following. 465 // =================================================== 466 467 func newTestLeaseManagerConfig() LeaseManagerConfig { 468 cfg := NewDefaultLeaseManagerConfig() 469 // As 30s is the default timeout for testing in global configuration, 470 // we cannot wait longer than that in a single time: change it to 1s 471 // for testing purposes. See wait.ForeverTestTimeout 472 cfg.ReuseDurationSeconds = 1 473 return cfg 474 } 475 476 func newTestTransformer() value.Transformer { 477 return storagetesting.NewPrefixTransformer([]byte(defaultTestPrefix), false) 478 } 479 480 type clientRecorder struct { 481 reads uint64 482 clientv3.KV 483 } 484 485 func (r *clientRecorder) Get(ctx context.Context, key string, opts ...clientv3.OpOption) (*clientv3.GetResponse, error) { 486 atomic.AddUint64(&r.reads, 1) 487 return r.KV.Get(ctx, key, opts...) 488 } 489 490 func (r *clientRecorder) GetReadsAndReset() uint64 { 491 return atomic.SwapUint64(&r.reads, 0) 492 } 493 494 type setupOptions struct { 495 client func(testing.TB) *clientv3.Client 496 codec runtime.Codec 497 newFunc func() runtime.Object 498 newListFunc func() runtime.Object 499 prefix string 500 resourcePrefix string 501 groupResource schema.GroupResource 502 transformer value.Transformer 503 leaseConfig LeaseManagerConfig 504 505 recorderEnabled bool 506 } 507 508 type setupOption func(*setupOptions) 509 510 func withClientConfig(config *embed.Config) setupOption { 511 return func(options *setupOptions) { 512 options.client = func(t testing.TB) *clientv3.Client { 513 return testserver.RunEtcd(t, config) 514 } 515 } 516 } 517 518 func withPrefix(prefix string) setupOption { 519 return func(options *setupOptions) { 520 options.prefix = prefix 521 } 522 } 523 524 func withLeaseConfig(leaseConfig LeaseManagerConfig) setupOption { 525 return func(options *setupOptions) { 526 options.leaseConfig = leaseConfig 527 } 528 } 529 530 func withRecorder() setupOption { 531 return func(options *setupOptions) { 532 options.recorderEnabled = true 533 } 534 } 535 536 func withDefaults(options *setupOptions) { 537 options.client = func(t testing.TB) *clientv3.Client { 538 return testserver.RunEtcd(t, nil) 539 } 540 options.codec = apitesting.TestCodec(codecs, examplev1.SchemeGroupVersion) 541 options.newFunc = newPod 542 options.newListFunc = newPodList 543 options.prefix = "" 544 options.resourcePrefix = "/pods" 545 options.groupResource = schema.GroupResource{Resource: "pods"} 546 options.transformer = newTestTransformer() 547 options.leaseConfig = newTestLeaseManagerConfig() 548 } 549 550 var _ setupOption = withDefaults 551 552 func testSetup(t testing.TB, opts ...setupOption) (context.Context, *store, *clientv3.Client) { 553 setupOpts := setupOptions{} 554 opts = append([]setupOption{withDefaults}, opts...) 555 for _, opt := range opts { 556 opt(&setupOpts) 557 } 558 client := setupOpts.client(t) 559 if setupOpts.recorderEnabled { 560 client.KV = &clientRecorder{KV: client.KV} 561 } 562 store := newStore( 563 client, 564 setupOpts.codec, 565 setupOpts.newFunc, 566 setupOpts.newListFunc, 567 setupOpts.prefix, 568 setupOpts.resourcePrefix, 569 setupOpts.groupResource, 570 setupOpts.transformer, 571 setupOpts.leaseConfig, 572 ) 573 ctx := context.Background() 574 return ctx, store, client 575 } 576 577 func TestValidateKey(t *testing.T) { 578 validKeys := []string{ 579 "/foo/bar/baz/a.b.c/", 580 "/foo", 581 "foo/bar/baz", 582 "/foo/bar..baz/", 583 "/foo/bar..", 584 "foo", 585 "foo/bar", 586 "/foo/bar/", 587 } 588 invalidKeys := []string{ 589 "/foo/bar/../a.b.c/", 590 "..", 591 "/..", 592 "../", 593 "/foo/bar/..", 594 "../foo/bar", 595 "/../foo", 596 "/foo/bar/../", 597 ".", 598 "/.", 599 "./", 600 "/./", 601 "/foo/.", 602 "./bar", 603 "/foo/./bar/", 604 } 605 const ( 606 pathPrefix = "/first/second" 607 expectPrefix = pathPrefix + "/" 608 ) 609 _, store, _ := testSetup(t, withPrefix(pathPrefix)) 610 611 for _, key := range validKeys { 612 k, err := store.prepareKey(key) 613 if err != nil { 614 t.Errorf("key %q should be valid; unexpected error: %v", key, err) 615 } else if !strings.HasPrefix(k, expectPrefix) { 616 t.Errorf("key %q should have prefix %q", k, expectPrefix) 617 } 618 } 619 620 for _, key := range invalidKeys { 621 _, err := store.prepareKey(key) 622 if err == nil { 623 t.Errorf("key %q should be invalid", key) 624 } 625 } 626 } 627 628 func TestInvalidKeys(t *testing.T) { 629 const invalidKey = "/foo/bar/../baz" 630 expectedError := fmt.Sprintf("invalid key: %q", invalidKey) 631 632 expectInvalidKey := func(methodName string, err error) { 633 if err == nil { 634 t.Errorf("[%s] expected invalid key error; got nil", methodName) 635 } else if err.Error() != expectedError { 636 t.Errorf("[%s] expected invalid key error; got %v", methodName, err) 637 } 638 } 639 640 ctx, store, _ := testSetup(t) 641 expectInvalidKey("Create", store.Create(ctx, invalidKey, nil, nil, 0)) 642 expectInvalidKey("Delete", store.Delete(ctx, invalidKey, nil, nil, nil, nil)) 643 _, watchErr := store.Watch(ctx, invalidKey, storage.ListOptions{}) 644 expectInvalidKey("Watch", watchErr) 645 expectInvalidKey("Get", store.Get(ctx, invalidKey, storage.GetOptions{}, nil)) 646 expectInvalidKey("GetList", store.GetList(ctx, invalidKey, storage.ListOptions{}, nil)) 647 expectInvalidKey("GuaranteedUpdate", store.GuaranteedUpdate(ctx, invalidKey, nil, true, nil, nil, nil)) 648 _, countErr := store.Count(invalidKey) 649 expectInvalidKey("Count", countErr) 650 } 651 652 func TestResolveGetListRev(t *testing.T) { 653 _, store, _ := testSetup(t) 654 testCases := []struct { 655 name string 656 continueKey string 657 continueRV int64 658 rv string 659 rvMatch metav1.ResourceVersionMatch 660 recursive bool 661 expectedError string 662 limit int64 663 expectedRev int64 664 }{ 665 { 666 name: "specifying resource versionwhen using continue", 667 continueKey: "continue", 668 continueRV: 100, 669 rv: "200", 670 expectedError: "specifying resource version is not allowed when using continue", 671 }, 672 { 673 name: "invalid resource version", 674 rv: "invalid", 675 expectedError: "invalid resource version", 676 }, 677 { 678 name: "unknown ResourceVersionMatch value", 679 rv: "200", 680 rvMatch: "unknown", 681 expectedError: "unknown ResourceVersionMatch value", 682 }, 683 { 684 name: "use continueRV", 685 continueKey: "continue", 686 continueRV: 100, 687 rv: "0", 688 expectedRev: 100, 689 }, 690 { 691 name: "use continueRV with empty rv", 692 continueKey: "continue", 693 continueRV: 100, 694 rv: "", 695 expectedRev: 100, 696 }, 697 { 698 name: "continueRV = 0", 699 continueKey: "continue", 700 continueRV: 0, 701 rv: "", 702 expectedRev: 0, 703 }, 704 { 705 name: "continueRV < 0", 706 continueKey: "continue", 707 continueRV: -1, 708 rv: "", 709 expectedRev: 0, 710 }, 711 { 712 name: "default", 713 expectedRev: 0, 714 }, 715 { 716 name: "rev resolve to 0 if ResourceVersionMatchNotOlderThan", 717 rv: "200", 718 rvMatch: metav1.ResourceVersionMatchNotOlderThan, 719 expectedRev: 0, 720 }, 721 { 722 name: "specified rev if ResourceVersionMatchExact", 723 rv: "200", 724 rvMatch: metav1.ResourceVersionMatchExact, 725 expectedRev: 200, 726 }, 727 { 728 name: "rev resolve to 0 if not recursive", 729 rv: "200", 730 limit: 1, 731 expectedRev: 0, 732 }, 733 { 734 name: "rev resolve to 0 if limit unspecified", 735 rv: "200", 736 recursive: true, 737 expectedRev: 0, 738 }, 739 { 740 name: "specified rev if recursive with limit", 741 rv: "200", 742 recursive: true, 743 limit: 1, 744 expectedRev: 200, 745 }, 746 } 747 for _, tt := range testCases { 748 tt := tt 749 t.Run(tt.name, func(t *testing.T) { 750 storageOpts := storage.ListOptions{ 751 ResourceVersion: tt.rv, 752 ResourceVersionMatch: tt.rvMatch, 753 Predicate: storage.SelectionPredicate{ 754 Limit: tt.limit, 755 }, 756 Recursive: tt.recursive, 757 } 758 rev, err := store.resolveGetListRev(tt.continueKey, tt.continueRV, storageOpts) 759 if len(tt.expectedError) > 0 { 760 if err == nil || !strings.Contains(err.Error(), tt.expectedError) { 761 t.Fatalf("expected error: %s, but got: %v", tt.expectedError, err) 762 } 763 return 764 } 765 if err != nil { 766 t.Fatalf("resolveRevForGetList failed: %v", err) 767 } 768 if rev != tt.expectedRev { 769 t.Errorf("%s: expecting rev = %d, but get %d", tt.name, tt.expectedRev, rev) 770 } 771 }) 772 } 773 } 774 775 func BenchmarkStore_GetList(b *testing.B) { 776 generateBigPod := func(index int, total int, expect int) runtime.Object { 777 l := map[string]string{} 778 if index%(total/expect) == 0 { 779 l["foo"] = "bar" 780 } 781 terminationGracePeriodSeconds := int64(42) 782 activeDeadlineSeconds := int64(42) 783 pod := &examplev1.Pod{ 784 ObjectMeta: metav1.ObjectMeta{ 785 Labels: l, 786 }, 787 Spec: examplev1.PodSpec{ 788 RestartPolicy: examplev1.RestartPolicy("Always"), 789 TerminationGracePeriodSeconds: &terminationGracePeriodSeconds, 790 ActiveDeadlineSeconds: &activeDeadlineSeconds, 791 NodeSelector: map[string]string{}, 792 ServiceAccountName: "demo-sa", 793 }, 794 } 795 pod.Name = fmt.Sprintf("object-%d", index) 796 data := make([]byte, 1024*2, 1024*2) // 2k labels 797 rand.Read(data) 798 pod.Spec.NodeSelector["key"] = string(data) 799 return pod 800 } 801 testCases := []struct { 802 name string 803 objectNum int 804 expectNum int 805 selector labels.Selector 806 newObjectFunc func(index int, total int, expect int) runtime.Object 807 newListObjectFunc func() runtime.Object 808 }{ 809 { 810 name: "pick 50 pods out of 5000 pod", 811 objectNum: 5000, 812 expectNum: 50, 813 selector: labels.SelectorFromSet(map[string]string{"foo": "bar"}), 814 newObjectFunc: generateBigPod, 815 newListObjectFunc: func() runtime.Object { return &examplev1.PodList{} }, 816 }, 817 { 818 name: "pick 500 pods out of 5000 pod", 819 objectNum: 5000, 820 expectNum: 500, 821 selector: labels.SelectorFromSet(map[string]string{"foo": "bar"}), 822 newObjectFunc: generateBigPod, 823 newListObjectFunc: func() runtime.Object { return &examplev1.PodList{} }, 824 }, 825 { 826 name: "pick 1000 pods out of 5000 pod", 827 objectNum: 5000, 828 expectNum: 1000, 829 selector: labels.SelectorFromSet(map[string]string{"foo": "bar"}), 830 newObjectFunc: generateBigPod, 831 newListObjectFunc: func() runtime.Object { return &examplev1.PodList{} }, 832 }, 833 { 834 name: "pick 2500 pods out of 5000 pod", 835 objectNum: 5000, 836 expectNum: 2500, 837 selector: labels.SelectorFromSet(map[string]string{"foo": "bar"}), 838 newObjectFunc: generateBigPod, 839 newListObjectFunc: func() runtime.Object { return &examplev1.PodList{} }, 840 }, 841 { 842 name: "pick 5000 pods out of 5000 pod", 843 objectNum: 5000, 844 expectNum: 5000, 845 selector: labels.SelectorFromSet(map[string]string{"foo": "bar"}), 846 newObjectFunc: generateBigPod, 847 newListObjectFunc: func() runtime.Object { return &examplev1.PodList{} }, 848 }, 849 } 850 for _, tc := range testCases { 851 b.Run(tc.name, func(b *testing.B) { 852 // booting etcd instance 853 ctx, store, etcdClient := testSetup(b) 854 defer etcdClient.Close() 855 856 // make fake objects.. 857 dir := "/testing" 858 originalRevision := "" 859 for i := 0; i < tc.objectNum; i++ { 860 obj := tc.newObjectFunc(i, tc.objectNum, tc.expectNum) 861 o := obj.(metav1.Object) 862 key := fmt.Sprintf("/testing/testkey/%s", o.GetName()) 863 out := tc.newObjectFunc(i, tc.objectNum, tc.expectNum) 864 if err := store.Create(ctx, key, obj, out, 0); err != nil { 865 b.Fatalf("Set failed: %v", err) 866 } 867 originalRevision = out.(metav1.Object).GetResourceVersion() 868 } 869 870 // prepare result and pred 871 pred := storage.SelectionPredicate{ 872 Label: tc.selector, 873 Field: fields.Everything(), 874 GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { 875 pod, ok := obj.(*examplev1.Pod) 876 if !ok { 877 return nil, nil, fmt.Errorf("not a pod") 878 } 879 return pod.ObjectMeta.Labels, fields.Set{ 880 "metadata.name": pod.Name, 881 }, nil 882 }, 883 } 884 885 // now we start benchmarking 886 b.ResetTimer() 887 for i := 0; i < b.N; i++ { 888 list := tc.newListObjectFunc() 889 if err := store.GetList(ctx, dir, storage.ListOptions{Predicate: pred, Recursive: true}, list); err != nil { 890 b.Errorf("Unexpected List error: %v", err) 891 } 892 listObject := list.(*examplev1.PodList) 893 if originalRevision != listObject.GetResourceVersion() { 894 b.Fatalf("original revision (%s) did not match final revision after linearized reads (%s)", originalRevision, listObject.GetResourceVersion()) 895 } 896 if len(listObject.Items) != tc.expectNum { 897 b.Fatalf("expect (%d) items but got (%d)", tc.expectNum, len(listObject.Items)) 898 } 899 } 900 }) 901 } 902 }