k8s.io/apiserver@v0.31.1/pkg/storage/testing/store_tests.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 testing 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "math" 24 "reflect" 25 "sort" 26 "strconv" 27 "strings" 28 "sync" 29 "testing" 30 31 "github.com/google/go-cmp/cmp" 32 apierrors "k8s.io/apimachinery/pkg/api/errors" 33 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 34 "k8s.io/apimachinery/pkg/fields" 35 "k8s.io/apimachinery/pkg/labels" 36 "k8s.io/apimachinery/pkg/runtime" 37 "k8s.io/apimachinery/pkg/watch" 38 "k8s.io/apiserver/pkg/apis/example" 39 "k8s.io/apiserver/pkg/storage" 40 "k8s.io/apiserver/pkg/storage/value" 41 utilpointer "k8s.io/utils/pointer" 42 ) 43 44 type KeyValidation func(ctx context.Context, t *testing.T, key string) 45 46 func RunTestCreate(ctx context.Context, t *testing.T, store storage.Interface, validation KeyValidation) { 47 tests := []struct { 48 name string 49 inputObj *example.Pod 50 expectedError error 51 }{{ 52 name: "successful create", 53 inputObj: &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test-ns"}}, 54 }, { 55 name: "create with ResourceVersion set", 56 inputObj: &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "bar", Namespace: "test-ns", ResourceVersion: "1"}}, 57 expectedError: storage.ErrResourceVersionSetOnCreate, 58 }} 59 60 for _, tt := range tests { 61 t.Run(tt.name, func(t *testing.T) { 62 out := &example.Pod{} // reset 63 // verify that kv pair is empty before set 64 key := computePodKey(tt.inputObj) 65 if err := store.Get(ctx, key, storage.GetOptions{}, out); !storage.IsNotFound(err) { 66 t.Fatalf("expecting empty result on key %s, got %v", key, err) 67 } 68 69 err := store.Create(ctx, key, tt.inputObj, out, 0) 70 if !errors.Is(err, tt.expectedError) { 71 t.Errorf("expecting error %v, but get: %v", tt.expectedError, err) 72 } 73 if err != nil { 74 return 75 } 76 // basic tests of the output 77 if tt.inputObj.ObjectMeta.Name != out.ObjectMeta.Name { 78 t.Errorf("pod name want=%s, get=%s", tt.inputObj.ObjectMeta.Name, out.ObjectMeta.Name) 79 } 80 if out.ResourceVersion == "" { 81 t.Errorf("output should have non-empty resource version") 82 } 83 validation(ctx, t, key) 84 }) 85 } 86 } 87 88 func RunTestCreateWithTTL(ctx context.Context, t *testing.T, store storage.Interface) { 89 input := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test-ns"}} 90 out := &example.Pod{} 91 92 key := computePodKey(input) 93 if err := store.Create(ctx, key, input, out, 1); err != nil { 94 t.Fatalf("Create failed: %v", err) 95 } 96 97 w, err := store.Watch(ctx, key, storage.ListOptions{ResourceVersion: out.ResourceVersion, Predicate: storage.Everything}) 98 if err != nil { 99 t.Fatalf("Watch failed: %v", err) 100 } 101 testCheckEventType(t, w, watch.Deleted) 102 } 103 104 func RunTestCreateWithKeyExist(ctx context.Context, t *testing.T, store storage.Interface) { 105 obj := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test-ns"}} 106 key, _ := testPropagateStore(ctx, t, store, obj) 107 out := &example.Pod{} 108 109 err := store.Create(ctx, key, obj, out, 0) 110 if err == nil || !storage.IsExist(err) { 111 t.Errorf("expecting key exists error, but get: %s", err) 112 } 113 } 114 115 func RunTestGet(ctx context.Context, t *testing.T, store storage.Interface) { 116 // create an object to test 117 key, createdObj := testPropagateStore(ctx, t, store, &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test-ns"}}) 118 // update the object once to allow get by exact resource version to be tested 119 updateObj := createdObj.DeepCopy() 120 updateObj.Annotations = map[string]string{"test-annotation": "1"} 121 storedObj := &example.Pod{} 122 err := store.GuaranteedUpdate(ctx, key, storedObj, true, nil, 123 func(_ runtime.Object, _ storage.ResponseMeta) (runtime.Object, *uint64, error) { 124 ttl := uint64(1) 125 return updateObj, &ttl, nil 126 }, nil) 127 if err != nil { 128 t.Fatalf("Update failed: %v", err) 129 } 130 // create an additional object to increment the resource version for pods above the resource version of the foo object 131 secondObj := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "bar", Namespace: "test-ns"}} 132 lastUpdatedObj := &example.Pod{} 133 if err := store.Create(ctx, computePodKey(secondObj), secondObj, lastUpdatedObj, 0); err != nil { 134 t.Fatalf("Set failed: %v", err) 135 } 136 137 currentRV, _ := strconv.Atoi(storedObj.ResourceVersion) 138 lastUpdatedCurrentRV, _ := strconv.Atoi(lastUpdatedObj.ResourceVersion) 139 140 // TODO(jpbetz): Add exact test cases 141 tests := []struct { 142 name string 143 key string 144 ignoreNotFound bool 145 expectNotFoundErr bool 146 expectRVTooLarge bool 147 expectedOut *example.Pod 148 expectedAlternatives []*example.Pod 149 rv string 150 }{{ 151 name: "get existing", 152 key: key, 153 ignoreNotFound: false, 154 expectNotFoundErr: false, 155 expectedOut: storedObj, 156 }, { 157 // For RV=0 arbitrarily old version is allowed, including from the moment 158 // when the object didn't yet exist. 159 // As a result, we allow it by setting ignoreNotFound and allowing an empty 160 // object in expectedOut. 161 name: "resource version 0", 162 key: key, 163 ignoreNotFound: true, 164 expectedAlternatives: []*example.Pod{{}, createdObj, storedObj}, 165 rv: "0", 166 }, { 167 // Given that Get with set ResourceVersion is effectively always 168 // NotOlderThan semantic, both versions of object are allowed. 169 name: "object created resource version", 170 key: key, 171 expectedAlternatives: []*example.Pod{createdObj, storedObj}, 172 rv: createdObj.ResourceVersion, 173 }, { 174 name: "current object resource version, match=NotOlderThan", 175 key: key, 176 expectedOut: storedObj, 177 rv: fmt.Sprintf("%d", currentRV), 178 }, { 179 name: "latest resource version", 180 key: key, 181 expectedOut: storedObj, 182 rv: fmt.Sprintf("%d", lastUpdatedCurrentRV), 183 }, { 184 name: "too high resource version", 185 key: key, 186 expectRVTooLarge: true, 187 rv: strconv.FormatInt(math.MaxInt64, 10), 188 }, { 189 name: "get non-existing", 190 key: "/non-existing", 191 ignoreNotFound: false, 192 expectNotFoundErr: true, 193 }, { 194 name: "get non-existing, ignore not found", 195 key: "/non-existing", 196 ignoreNotFound: true, 197 expectNotFoundErr: false, 198 expectedOut: &example.Pod{}, 199 }} 200 201 for _, tt := range tests { 202 tt := tt 203 t.Run(tt.name, func(t *testing.T) { 204 // For some asynchronous implementations of storage interface (in particular watchcache), 205 // certain requests may impact result of further requests. As an example, if we first 206 // ensure that watchcache is synchronized up to ResourceVersion X (using Get/List requests 207 // with NotOlderThan semantic), the further requests (even specifying earlier resource 208 // version) will also return the result synchronized to at least ResourceVersion X. 209 // By parallelizing test cases we ensure that the order in which test cases are defined 210 // doesn't automatically preclude some scenarios from happening. 211 t.Parallel() 212 213 out := &example.Pod{} 214 err := store.Get(ctx, tt.key, storage.GetOptions{IgnoreNotFound: tt.ignoreNotFound, ResourceVersion: tt.rv}, out) 215 if tt.expectNotFoundErr { 216 if err == nil || !storage.IsNotFound(err) { 217 t.Errorf("expecting not found error, but get: %v", err) 218 } 219 return 220 } 221 if tt.expectRVTooLarge { 222 if err == nil || !storage.IsTooLargeResourceVersion(err) { 223 t.Errorf("expecting resource version too high error, but get: %v", err) 224 } 225 return 226 } 227 if err != nil { 228 t.Fatalf("Get failed: %v", err) 229 } 230 231 if tt.expectedAlternatives == nil { 232 expectNoDiff(t, fmt.Sprintf("%s: incorrect pod", tt.name), tt.expectedOut, out) 233 } else { 234 ExpectContains(t, fmt.Sprintf("%s: incorrect pod", tt.name), toInterfaceSlice(tt.expectedAlternatives), out) 235 } 236 }) 237 } 238 } 239 240 func RunTestUnconditionalDelete(ctx context.Context, t *testing.T, store storage.Interface) { 241 key, storedObj := testPropagateStore(ctx, t, store, &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test-ns"}}) 242 243 tests := []struct { 244 name string 245 key string 246 expectedObj *example.Pod 247 expectNotFoundErr bool 248 }{{ 249 name: "existing key", 250 key: key, 251 expectedObj: storedObj, 252 expectNotFoundErr: false, 253 }, { 254 name: "non-existing key", 255 key: "/non-existing", 256 expectedObj: nil, 257 expectNotFoundErr: true, 258 }} 259 260 for _, tt := range tests { 261 t.Run(tt.name, func(t *testing.T) { 262 out := &example.Pod{} // reset 263 err := store.Delete(ctx, tt.key, out, nil, storage.ValidateAllObjectFunc, nil) 264 if tt.expectNotFoundErr { 265 if err == nil || !storage.IsNotFound(err) { 266 t.Errorf("expecting not found error, but get: %s", err) 267 } 268 return 269 } 270 if err != nil { 271 t.Fatalf("Delete failed: %v", err) 272 } 273 // We expect the resource version of the returned object to be 274 // updated compared to the last existing object. 275 if storedObj.ResourceVersion == out.ResourceVersion { 276 t.Errorf("expecting resource version to be updated, but get: %s", out.ResourceVersion) 277 } 278 out.ResourceVersion = storedObj.ResourceVersion 279 expectNoDiff(t, "incorrect pod:", tt.expectedObj, out) 280 }) 281 } 282 } 283 284 func RunTestConditionalDelete(ctx context.Context, t *testing.T, store storage.Interface) { 285 obj := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test-ns", UID: "A"}} 286 key, storedObj := testPropagateStore(ctx, t, store, obj) 287 288 tests := []struct { 289 name string 290 precondition *storage.Preconditions 291 expectInvalidObjErr bool 292 }{{ 293 name: "UID match", 294 precondition: storage.NewUIDPreconditions("A"), 295 expectInvalidObjErr: false, 296 }, { 297 name: "UID mismatch", 298 precondition: storage.NewUIDPreconditions("B"), 299 expectInvalidObjErr: true, 300 }} 301 302 for _, tt := range tests { 303 t.Run(tt.name, func(t *testing.T) { 304 out := &example.Pod{} 305 err := store.Delete(ctx, key, out, tt.precondition, storage.ValidateAllObjectFunc, nil) 306 if tt.expectInvalidObjErr { 307 if err == nil || !storage.IsInvalidObj(err) { 308 t.Errorf("expecting invalid UID error, but get: %s", err) 309 } 310 return 311 } 312 if err != nil { 313 t.Fatalf("Delete failed: %v", err) 314 } 315 // We expect the resource version of the returned object to be 316 // updated compared to the last existing object. 317 if storedObj.ResourceVersion == out.ResourceVersion { 318 t.Errorf("expecting resource version to be updated, but get: %s", out.ResourceVersion) 319 } 320 out.ResourceVersion = storedObj.ResourceVersion 321 expectNoDiff(t, "incorrect pod:", storedObj, out) 322 obj := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test-ns", UID: "A"}} 323 key, storedObj = testPropagateStore(ctx, t, store, obj) 324 }) 325 } 326 } 327 328 // The following set of Delete tests are testing the logic of adding `suggestion` 329 // as a parameter with probably value of the current state. 330 // Introducing it for GuaranteedUpdate cause a number of issues, so we're addressing 331 // all of those upfront by adding appropriate tests: 332 // - https://github.com/kubernetes/kubernetes/pull/35415 333 // [DONE] Lack of tests originally - added TestDeleteWithSuggestion. 334 // - https://github.com/kubernetes/kubernetes/pull/40664 335 // [DONE] Irrelevant for delete, as Delete doesn't write data (nor compare it). 336 // - https://github.com/kubernetes/kubernetes/pull/47703 337 // [DONE] Irrelevant for delete, because Delete doesn't persist data. 338 // - https://github.com/kubernetes/kubernetes/pull/48394/ 339 // [DONE] Irrelevant for delete, because Delete doesn't compare data. 340 // - https://github.com/kubernetes/kubernetes/pull/43152 341 // [DONE] Added TestDeleteWithSuggestionAndConflict 342 // - https://github.com/kubernetes/kubernetes/pull/54780 343 // [DONE] Irrelevant for delete, because Delete doesn't compare data. 344 // - https://github.com/kubernetes/kubernetes/pull/58375 345 // [DONE] Irrelevant for delete, because Delete doesn't compare data. 346 // - https://github.com/kubernetes/kubernetes/pull/77619 347 // [DONE] Added TestValidateDeletionWithSuggestion for corresponding delete checks. 348 // - https://github.com/kubernetes/kubernetes/pull/78713 349 // [DONE] Bug was in getState function which is shared with the new code. 350 // - https://github.com/kubernetes/kubernetes/pull/78713 351 // [DONE] Added TestPreconditionalDeleteWithSuggestion 352 353 func RunTestDeleteWithSuggestion(ctx context.Context, t *testing.T, store storage.Interface) { 354 key, originalPod := testPropagateStore(ctx, t, store, &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "name", Namespace: "test-ns"}}) 355 356 out := &example.Pod{} 357 if err := store.Delete(ctx, key, out, nil, storage.ValidateAllObjectFunc, originalPod); err != nil { 358 t.Errorf("Unexpected failure during deletion: %v", err) 359 } 360 361 if err := store.Get(ctx, key, storage.GetOptions{}, &example.Pod{}); !storage.IsNotFound(err) { 362 t.Errorf("Unexpected error on reading object: %v", err) 363 } 364 } 365 366 func RunTestDeleteWithSuggestionAndConflict(ctx context.Context, t *testing.T, store storage.Interface) { 367 key, originalPod := testPropagateStore(ctx, t, store, &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "name", Namespace: "test-ns"}}) 368 369 // First update, so originalPod is outdated. 370 updatedPod := &example.Pod{} 371 if err := store.GuaranteedUpdate(ctx, key, updatedPod, false, nil, 372 storage.SimpleUpdate(func(obj runtime.Object) (runtime.Object, error) { 373 pod := obj.(*example.Pod) 374 pod.ObjectMeta.Labels = map[string]string{"foo": "bar"} 375 return pod, nil 376 }), nil); err != nil { 377 t.Errorf("Unexpected failure during updated: %v", err) 378 } 379 380 out := &example.Pod{} 381 if err := store.Delete(ctx, key, out, nil, storage.ValidateAllObjectFunc, originalPod); err != nil { 382 t.Errorf("Unexpected failure during deletion: %v", err) 383 } 384 385 if err := store.Get(ctx, key, storage.GetOptions{}, &example.Pod{}); !storage.IsNotFound(err) { 386 t.Errorf("Unexpected error on reading object: %v", err) 387 } 388 updatedPod.ObjectMeta.ResourceVersion = out.ObjectMeta.ResourceVersion 389 expectNoDiff(t, "incorrect pod:", updatedPod, out) 390 } 391 392 // RunTestDeleteWithConflict tests the case when another conflicting update happened before the delete completed. 393 func RunTestDeleteWithConflict(ctx context.Context, t *testing.T, store storage.Interface) { 394 key, _ := testPropagateStore(ctx, t, store, &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "name", Namespace: "test-ns"}}) 395 396 // First update, so originalPod is outdated. 397 updatedPod := &example.Pod{} 398 validateCount := 0 399 updateCount := 0 400 // Simulate a conflicting update in the middle of delete. 401 validateAllWithUpdate := func(_ context.Context, _ runtime.Object) error { 402 validateCount++ 403 if validateCount > 1 { 404 return nil 405 } 406 if err := store.GuaranteedUpdate(ctx, key, updatedPod, false, nil, 407 storage.SimpleUpdate(func(obj runtime.Object) (runtime.Object, error) { 408 pod := obj.(*example.Pod) 409 pod.ObjectMeta.Labels = map[string]string{"foo": "bar"} 410 return pod, nil 411 }), nil); err != nil { 412 t.Errorf("Unexpected failure during updated: %v", err) 413 } 414 updateCount++ 415 return nil 416 } 417 418 out := &example.Pod{} 419 if err := store.Delete(ctx, key, out, nil, validateAllWithUpdate, nil); err != nil { 420 t.Errorf("Unexpected failure during deletion: %v", err) 421 } 422 423 if validateCount != 2 { 424 t.Errorf("Expect validateCount = %d, but got %d", 2, validateCount) 425 } 426 if updateCount != 1 { 427 t.Errorf("Expect updateCount = %d, but got %d", 1, updateCount) 428 } 429 430 if err := store.Get(ctx, key, storage.GetOptions{}, &example.Pod{}); !storage.IsNotFound(err) { 431 t.Errorf("Unexpected error on reading object: %v", err) 432 } 433 updatedPod.ObjectMeta.ResourceVersion = out.ObjectMeta.ResourceVersion 434 expectNoDiff(t, "incorrect pod:", updatedPod, out) 435 } 436 437 func RunTestDeleteWithSuggestionOfDeletedObject(ctx context.Context, t *testing.T, store storage.Interface) { 438 key, originalPod := testPropagateStore(ctx, t, store, &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "name", Namespace: "test-ns"}}) 439 440 // First delete, so originalPod is outdated. 441 deletedPod := &example.Pod{} 442 if err := store.Delete(ctx, key, deletedPod, nil, storage.ValidateAllObjectFunc, originalPod); err != nil { 443 t.Errorf("Unexpected failure during deletion: %v", err) 444 } 445 446 // Now try deleting with stale object. 447 out := &example.Pod{} 448 if err := store.Delete(ctx, key, out, nil, storage.ValidateAllObjectFunc, originalPod); !storage.IsNotFound(err) { 449 t.Errorf("Unexpected error during deletion: %v, expected not-found", err) 450 } 451 } 452 453 func RunTestValidateDeletionWithSuggestion(ctx context.Context, t *testing.T, store storage.Interface) { 454 key, originalPod := testPropagateStore(ctx, t, store, &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "name", Namespace: "test-ns"}}) 455 456 // Check that validaing fresh object fails is called once and fails. 457 validationCalls := 0 458 validationError := fmt.Errorf("validation error") 459 validateNothing := func(_ context.Context, _ runtime.Object) error { 460 validationCalls++ 461 return validationError 462 } 463 out := &example.Pod{} 464 if err := store.Delete(ctx, key, out, nil, validateNothing, originalPod); err != validationError { 465 t.Errorf("Unexpected failure during deletion: %v", err) 466 } 467 if validationCalls != 1 { 468 t.Errorf("validate function should have been called once, called %d", validationCalls) 469 } 470 471 // First update, so originalPod is outdated. 472 updatedPod := &example.Pod{} 473 if err := store.GuaranteedUpdate(ctx, key, updatedPod, false, nil, 474 storage.SimpleUpdate(func(obj runtime.Object) (runtime.Object, error) { 475 pod := obj.(*example.Pod) 476 pod.ObjectMeta.Labels = map[string]string{"foo": "bar"} 477 return pod, nil 478 }), nil); err != nil { 479 t.Errorf("Unexpected failure during updated: %v", err) 480 } 481 482 calls := 0 483 validateFresh := func(_ context.Context, obj runtime.Object) error { 484 calls++ 485 pod := obj.(*example.Pod) 486 if pod.ObjectMeta.Labels == nil || pod.ObjectMeta.Labels["foo"] != "bar" { 487 return fmt.Errorf("stale object") 488 } 489 return nil 490 } 491 492 if err := store.Delete(ctx, key, out, nil, validateFresh, originalPod); err != nil { 493 t.Errorf("Unexpected failure during deletion: %v", err) 494 } 495 496 // Implementations of the storage interface are allowed to ignore the suggestion, 497 // in which case just one validation call is possible. 498 if calls > 2 { 499 t.Errorf("validate function should have been called at most twice, called %d", calls) 500 } 501 502 if err := store.Get(ctx, key, storage.GetOptions{}, &example.Pod{}); !storage.IsNotFound(err) { 503 t.Errorf("Unexpected error on reading object: %v", err) 504 } 505 } 506 507 // RunTestValidateDeletionWithOnlySuggestionValid tests the case of delete with validateDeletion function, 508 // when the suggested cachedExistingObject passes the validate function while the current version does not pass the validate function. 509 func RunTestValidateDeletionWithOnlySuggestionValid(ctx context.Context, t *testing.T, store storage.Interface) { 510 key, originalPod := testPropagateStore(ctx, t, store, &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "name", Namespace: "test-ns", Labels: map[string]string{"foo": "bar"}}}) 511 512 // Check that validaing fresh object fails is called once and fails. 513 validationCalls := 0 514 validationError := fmt.Errorf("validation error") 515 validateNothing := func(_ context.Context, _ runtime.Object) error { 516 validationCalls++ 517 return validationError 518 } 519 out := &example.Pod{} 520 if err := store.Delete(ctx, key, out, nil, validateNothing, originalPod); err != validationError { 521 t.Errorf("Unexpected failure during deletion: %v", err) 522 } 523 if validationCalls != 1 { 524 t.Errorf("validate function should have been called once, called %d", validationCalls) 525 } 526 527 // First update, so originalPod is outdated. 528 updatedPod := &example.Pod{} 529 if err := store.GuaranteedUpdate(ctx, key, updatedPod, false, nil, 530 storage.SimpleUpdate(func(obj runtime.Object) (runtime.Object, error) { 531 pod := obj.(*example.Pod) 532 pod.ObjectMeta.Labels = map[string]string{"foo": "barbar"} 533 return pod, nil 534 }), nil); err != nil { 535 t.Errorf("Unexpected failure during updated: %v", err) 536 } 537 538 calls := 0 539 validateFresh := func(_ context.Context, obj runtime.Object) error { 540 calls++ 541 pod := obj.(*example.Pod) 542 if pod.ObjectMeta.Labels == nil || pod.ObjectMeta.Labels["foo"] != "bar" { 543 return fmt.Errorf("stale object") 544 } 545 return nil 546 } 547 548 err := store.Delete(ctx, key, out, nil, validateFresh, originalPod) 549 if err == nil || err.Error() != "stale object" { 550 t.Errorf("expecting stale object error, but get: %s", err) 551 } 552 553 // Implementations of the storage interface are allowed to ignore the suggestion, 554 // in which case just one validation call is possible. 555 if calls > 2 { 556 t.Errorf("validate function should have been called at most twice, called %d", calls) 557 } 558 559 if err = store.Get(ctx, key, storage.GetOptions{}, out); err != nil { 560 t.Errorf("Unexpected error on reading object: %v", err) 561 } 562 expectNoDiff(t, "incorrect pod:", updatedPod, out) 563 } 564 565 func RunTestPreconditionalDeleteWithSuggestion(ctx context.Context, t *testing.T, store storage.Interface) { 566 key, originalPod := testPropagateStore(ctx, t, store, &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "name", Namespace: "test-ns"}}) 567 568 // First update, so originalPod is outdated. 569 updatedPod := &example.Pod{} 570 if err := store.GuaranteedUpdate(ctx, key, updatedPod, false, nil, 571 storage.SimpleUpdate(func(obj runtime.Object) (runtime.Object, error) { 572 pod := obj.(*example.Pod) 573 pod.ObjectMeta.UID = "myUID" 574 return pod, nil 575 }), nil); err != nil { 576 t.Errorf("Unexpected failure during updated: %v", err) 577 } 578 579 prec := storage.NewUIDPreconditions("myUID") 580 581 out := &example.Pod{} 582 if err := store.Delete(ctx, key, out, prec, storage.ValidateAllObjectFunc, originalPod); err != nil { 583 t.Errorf("Unexpected failure during deletion: %v", err) 584 } 585 586 if err := store.Get(ctx, key, storage.GetOptions{}, &example.Pod{}); !storage.IsNotFound(err) { 587 t.Errorf("Unexpected error on reading object: %v", err) 588 } 589 } 590 591 // RunTestPreconditionalDeleteWithOnlySuggestionPass tests the case of delete with preconditions, 592 // when the suggested cachedExistingObject passes the preconditions while the current version does not pass the preconditions. 593 func RunTestPreconditionalDeleteWithOnlySuggestionPass(ctx context.Context, t *testing.T, store storage.Interface) { 594 key, originalPod := testPropagateStore(ctx, t, store, &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "name", Namespace: "test-ns", UID: "myUID"}}) 595 596 // First update, so originalPod is outdated. 597 updatedPod := &example.Pod{} 598 if err := store.GuaranteedUpdate(ctx, key, updatedPod, false, nil, 599 storage.SimpleUpdate(func(obj runtime.Object) (runtime.Object, error) { 600 pod := obj.(*example.Pod) 601 pod.ObjectMeta.UID = "otherUID" 602 return pod, nil 603 }), nil); err != nil { 604 t.Errorf("Unexpected failure during updated: %v", err) 605 } 606 607 prec := storage.NewUIDPreconditions("myUID") 608 // Although originalPod passes the precondition, its delete would fail due to conflict. 609 // The 2nd try with updatedPod would fail the precondition. 610 out := &example.Pod{} 611 err := store.Delete(ctx, key, out, prec, storage.ValidateAllObjectFunc, originalPod) 612 if err == nil || !storage.IsInvalidObj(err) { 613 t.Errorf("expecting invalid UID error, but get: %s", err) 614 } 615 616 if err = store.Get(ctx, key, storage.GetOptions{}, out); err != nil { 617 t.Errorf("Unexpected error on reading object: %v", err) 618 } 619 expectNoDiff(t, "incorrect pod:", updatedPod, out) 620 } 621 622 func RunTestList(ctx context.Context, t *testing.T, store storage.Interface, compaction Compaction, ignoreWatchCacheTests bool) { 623 initialRV, preset, err := seedMultiLevelData(ctx, store) 624 if err != nil { 625 t.Fatal(err) 626 } 627 628 list := &example.PodList{} 629 storageOpts := storage.ListOptions{ 630 // Ensure we're listing from "now". 631 ResourceVersion: "", 632 Predicate: storage.Everything, 633 Recursive: true, 634 } 635 if err := store.GetList(ctx, "/second", storageOpts, list); err != nil { 636 t.Errorf("Unexpected error: %v", err) 637 } 638 continueRV, _ := strconv.Atoi(list.ResourceVersion) 639 secondContinuation, err := storage.EncodeContinue("/second/foo", "/second/", int64(continueRV)) 640 if err != nil { 641 t.Fatal(err) 642 } 643 644 getAttrs := func(obj runtime.Object) (labels.Set, fields.Set, error) { 645 pod := obj.(*example.Pod) 646 return nil, fields.Set{"metadata.name": pod.Name, "spec.nodeName": pod.Spec.NodeName}, nil 647 } 648 // Use compact to increase etcd global revision without changes to any resources. 649 // The increase in resources version comes from Kubernetes compaction updating hidden key. 650 // Used to test consistent List to confirm it returns latest etcd revision. 651 compaction(ctx, t, initialRV) 652 currentRV := fmt.Sprintf("%d", continueRV+1) 653 654 tests := []struct { 655 name string 656 rv string 657 rvMatch metav1.ResourceVersionMatch 658 prefix string 659 pred storage.SelectionPredicate 660 ignoreForWatchCache bool 661 expectedOut []example.Pod 662 expectedAlternatives [][]example.Pod 663 expectContinue bool 664 expectedRemainingItemCount *int64 665 expectError bool 666 expectRVTooLarge bool 667 expectRV string 668 expectRVFunc func(string) error 669 }{ 670 { 671 name: "rejects invalid resource version", 672 prefix: "/pods", 673 pred: storage.Everything, 674 rv: "abc", 675 expectError: true, 676 }, 677 { 678 name: "rejects resource version and continue token", 679 prefix: "/pods", 680 pred: storage.SelectionPredicate{ 681 Label: labels.Everything(), 682 Field: fields.Everything(), 683 Limit: 1, 684 Continue: secondContinuation, 685 }, 686 rv: "1", 687 expectError: true, 688 }, 689 { 690 name: "rejects resource version set too high", 691 prefix: "/pods", 692 rv: strconv.FormatInt(math.MaxInt64, 10), 693 expectRVTooLarge: true, 694 }, 695 { 696 name: "test List on existing key", 697 prefix: "/pods/first/", 698 pred: storage.Everything, 699 expectedOut: []example.Pod{*preset[0]}, 700 }, 701 { 702 name: "test List on existing key with resource version set to 0", 703 prefix: "/pods/first/", 704 pred: storage.Everything, 705 expectedAlternatives: [][]example.Pod{{}, {*preset[0]}}, 706 rv: "0", 707 }, 708 { 709 name: "test List on existing key with resource version set before first write, match=Exact", 710 prefix: "/pods/first/", 711 pred: storage.Everything, 712 expectedOut: []example.Pod{}, 713 rv: initialRV, 714 rvMatch: metav1.ResourceVersionMatchExact, 715 expectRV: initialRV, 716 }, 717 { 718 name: "test List on existing key with resource version set to 0, match=NotOlderThan", 719 prefix: "/pods/first/", 720 pred: storage.Everything, 721 expectedAlternatives: [][]example.Pod{{}, {*preset[0]}}, 722 rv: "0", 723 rvMatch: metav1.ResourceVersionMatchNotOlderThan, 724 }, 725 { 726 name: "test List on existing key with resource version set to 0, match=Invalid", 727 prefix: "/pods/first/", 728 pred: storage.Everything, 729 rv: "0", 730 rvMatch: "Invalid", 731 expectError: true, 732 }, 733 { 734 name: "test List on existing key with resource version set before first write, match=NotOlderThan", 735 prefix: "/pods/first/", 736 pred: storage.Everything, 737 expectedAlternatives: [][]example.Pod{{}, {*preset[0]}}, 738 rv: initialRV, 739 rvMatch: metav1.ResourceVersionMatchNotOlderThan, 740 }, 741 { 742 name: "test List on existing key with resource version set before first write, match=Invalid", 743 prefix: "/pods/first/", 744 pred: storage.Everything, 745 rv: initialRV, 746 rvMatch: "Invalid", 747 expectError: true, 748 }, 749 { 750 name: "test List on existing key with resource version set to current resource version", 751 prefix: "/pods/first/", 752 pred: storage.Everything, 753 expectedOut: []example.Pod{*preset[0]}, 754 rv: list.ResourceVersion, 755 }, 756 { 757 name: "test List on existing key with resource version set to current resource version, match=Exact", 758 prefix: "/pods/first/", 759 pred: storage.Everything, 760 expectedOut: []example.Pod{*preset[0]}, 761 rv: list.ResourceVersion, 762 rvMatch: metav1.ResourceVersionMatchExact, 763 expectRV: list.ResourceVersion, 764 }, 765 { 766 name: "test List on existing key with resource version set to current resource version, match=NotOlderThan", 767 prefix: "/pods/first/", 768 pred: storage.Everything, 769 expectedOut: []example.Pod{*preset[0]}, 770 rv: list.ResourceVersion, 771 rvMatch: metav1.ResourceVersionMatchNotOlderThan, 772 }, 773 { 774 name: "test List on non-existing key", 775 prefix: "/pods/non-existing/", 776 pred: storage.Everything, 777 expectedOut: []example.Pod{}, 778 }, 779 { 780 name: "test List with pod name matching", 781 prefix: "/pods/first/", 782 pred: storage.SelectionPredicate{ 783 Label: labels.Everything(), 784 Field: fields.ParseSelectorOrDie("metadata.name!=bar"), 785 }, 786 expectedOut: []example.Pod{}, 787 }, 788 { 789 name: "test List with pod name matching with resource version set to current resource version, match=NotOlderThan", 790 prefix: "/pods/first/", 791 pred: storage.SelectionPredicate{ 792 Label: labels.Everything(), 793 Field: fields.ParseSelectorOrDie("metadata.name!=bar"), 794 }, 795 expectedOut: []example.Pod{}, 796 rv: list.ResourceVersion, 797 rvMatch: metav1.ResourceVersionMatchNotOlderThan, 798 }, 799 { 800 name: "test List with limit", 801 prefix: "/pods/second/", 802 pred: storage.SelectionPredicate{ 803 Label: labels.Everything(), 804 Field: fields.Everything(), 805 Limit: 1, 806 }, 807 expectedOut: []example.Pod{*preset[1]}, 808 expectContinue: true, 809 expectedRemainingItemCount: utilpointer.Int64(1), 810 }, 811 { 812 name: "test List with limit at current resource version", 813 prefix: "/pods/second/", 814 pred: storage.SelectionPredicate{ 815 Label: labels.Everything(), 816 Field: fields.Everything(), 817 Limit: 1, 818 }, 819 expectedOut: []example.Pod{*preset[1]}, 820 expectContinue: true, 821 expectedRemainingItemCount: utilpointer.Int64(1), 822 rv: list.ResourceVersion, 823 expectRV: list.ResourceVersion, 824 }, 825 { 826 name: "test List with limit at current resource version and match=Exact", 827 prefix: "/pods/second/", 828 pred: storage.SelectionPredicate{ 829 Label: labels.Everything(), 830 Field: fields.Everything(), 831 Limit: 1, 832 }, 833 expectedOut: []example.Pod{*preset[1]}, 834 expectContinue: true, 835 expectedRemainingItemCount: utilpointer.Int64(1), 836 rv: list.ResourceVersion, 837 rvMatch: metav1.ResourceVersionMatchExact, 838 expectRV: list.ResourceVersion, 839 }, 840 { 841 name: "test List with limit at current resource version and match=NotOlderThan", 842 prefix: "/pods/second/", 843 pred: storage.SelectionPredicate{ 844 Label: labels.Everything(), 845 Field: fields.Everything(), 846 Limit: 1, 847 }, 848 expectedOut: []example.Pod{*preset[1]}, 849 expectContinue: true, 850 expectedRemainingItemCount: utilpointer.Int64(1), 851 rv: list.ResourceVersion, 852 rvMatch: metav1.ResourceVersionMatchNotOlderThan, 853 expectRVFunc: resourceVersionNotOlderThan(list.ResourceVersion), 854 }, 855 { 856 name: "test List with limit at resource version 0", 857 prefix: "/pods/second/", 858 pred: storage.SelectionPredicate{ 859 Label: labels.Everything(), 860 Field: fields.Everything(), 861 Limit: 1, 862 }, 863 // TODO(#108003): As of now, watchcache is deliberately ignoring 864 // limit if RV=0 is specified, returning whole list of objects. 865 // While this should eventually get fixed, for now we're explicitly 866 // ignoring this testcase for watchcache. 867 ignoreForWatchCache: true, 868 expectedOut: []example.Pod{*preset[1]}, 869 expectContinue: true, 870 expectedRemainingItemCount: utilpointer.Int64(1), 871 rv: "0", 872 expectRVFunc: resourceVersionNotOlderThan(list.ResourceVersion), 873 }, 874 { 875 name: "test List with limit at resource version 0 match=NotOlderThan", 876 prefix: "/pods/second/", 877 pred: storage.SelectionPredicate{ 878 Label: labels.Everything(), 879 Field: fields.Everything(), 880 Limit: 1, 881 }, 882 // TODO(#108003): As of now, watchcache is deliberately ignoring 883 // limit if RV=0 is specified, returning whole list of objects. 884 // While this should eventually get fixed, for now we're explicitly 885 // ignoring this testcase for watchcache. 886 ignoreForWatchCache: true, 887 expectedOut: []example.Pod{*preset[1]}, 888 expectContinue: true, 889 expectedRemainingItemCount: utilpointer.Int64(1), 890 rv: "0", 891 rvMatch: metav1.ResourceVersionMatchNotOlderThan, 892 expectRVFunc: resourceVersionNotOlderThan(list.ResourceVersion), 893 }, 894 { 895 name: "test List with limit at resource version before first write and match=Exact", 896 prefix: "/pods/second/", 897 pred: storage.SelectionPredicate{ 898 Label: labels.Everything(), 899 Field: fields.Everything(), 900 Limit: 1, 901 }, 902 expectedOut: []example.Pod{}, 903 expectContinue: false, 904 rv: initialRV, 905 rvMatch: metav1.ResourceVersionMatchExact, 906 expectRV: initialRV, 907 }, 908 { 909 name: "test List with pregenerated continue token", 910 prefix: "/pods/second/", 911 pred: storage.SelectionPredicate{ 912 Label: labels.Everything(), 913 Field: fields.Everything(), 914 Limit: 1, 915 Continue: secondContinuation, 916 }, 917 expectedOut: []example.Pod{*preset[2]}, 918 }, 919 { 920 name: "ignores resource version 0 for List with pregenerated continue token", 921 prefix: "/pods/second/", 922 pred: storage.SelectionPredicate{ 923 Label: labels.Everything(), 924 Field: fields.Everything(), 925 Limit: 1, 926 Continue: secondContinuation, 927 }, 928 rv: "0", 929 expectedOut: []example.Pod{*preset[2]}, 930 }, 931 { 932 name: "test List with multiple levels of directories and expect flattened result", 933 prefix: "/pods/second/", 934 pred: storage.Everything, 935 expectedOut: []example.Pod{*preset[1], *preset[2]}, 936 }, 937 { 938 name: "test List with multiple levels of directories and expect flattened result with current resource version and match=NotOlderThan", 939 prefix: "/pods/second/", 940 pred: storage.Everything, 941 rv: list.ResourceVersion, 942 rvMatch: metav1.ResourceVersionMatchNotOlderThan, 943 expectedOut: []example.Pod{*preset[1], *preset[2]}, 944 }, 945 { 946 name: "test List with filter returning only one item, ensure only a single page returned", 947 prefix: "/pods", 948 pred: storage.SelectionPredicate{ 949 Field: fields.OneTermEqualSelector("metadata.name", "barfoo"), 950 Label: labels.Everything(), 951 Limit: 1, 952 }, 953 expectedOut: []example.Pod{*preset[3]}, 954 expectContinue: true, 955 }, 956 { 957 name: "test List with filter returning only one item, ensure only a single page returned with current resource version and match=NotOlderThan", 958 prefix: "/pods", 959 pred: storage.SelectionPredicate{ 960 Field: fields.OneTermEqualSelector("metadata.name", "barfoo"), 961 Label: labels.Everything(), 962 Limit: 1, 963 }, 964 rv: list.ResourceVersion, 965 rvMatch: metav1.ResourceVersionMatchNotOlderThan, 966 expectedOut: []example.Pod{*preset[3]}, 967 expectContinue: true, 968 }, 969 { 970 name: "test List with filter returning only one item, covers the entire list", 971 prefix: "/pods", 972 pred: storage.SelectionPredicate{ 973 Field: fields.OneTermEqualSelector("metadata.name", "barfoo"), 974 Label: labels.Everything(), 975 Limit: 2, 976 }, 977 expectedOut: []example.Pod{*preset[3]}, 978 expectContinue: false, 979 }, 980 { 981 name: "test List with filter returning only one item, covers the entire list with current resource version and match=NotOlderThan", 982 prefix: "/pods", 983 pred: storage.SelectionPredicate{ 984 Field: fields.OneTermEqualSelector("metadata.name", "barfoo"), 985 Label: labels.Everything(), 986 Limit: 2, 987 }, 988 rv: list.ResourceVersion, 989 rvMatch: metav1.ResourceVersionMatchNotOlderThan, 990 expectedOut: []example.Pod{*preset[3]}, 991 expectContinue: false, 992 }, 993 { 994 name: "test List with filter returning only one item, covers the entire list, with resource version 0", 995 prefix: "/pods", 996 pred: storage.SelectionPredicate{ 997 Field: fields.OneTermEqualSelector("metadata.name", "barfoo"), 998 Label: labels.Everything(), 999 Limit: 2, 1000 }, 1001 rv: "0", 1002 expectedAlternatives: [][]example.Pod{{}, {*preset[3]}}, 1003 expectContinue: false, 1004 }, 1005 { 1006 name: "test List with filter returning two items, more pages possible", 1007 prefix: "/pods", 1008 pred: storage.SelectionPredicate{ 1009 Field: fields.OneTermEqualSelector("metadata.name", "bar"), 1010 Label: labels.Everything(), 1011 Limit: 2, 1012 }, 1013 expectContinue: true, 1014 expectedOut: []example.Pod{*preset[0], *preset[1]}, 1015 }, 1016 { 1017 name: "test List with filter returning two items, more pages possible with current resource version and match=NotOlderThan", 1018 prefix: "/pods", 1019 pred: storage.SelectionPredicate{ 1020 Field: fields.OneTermEqualSelector("metadata.name", "bar"), 1021 Label: labels.Everything(), 1022 Limit: 2, 1023 }, 1024 rv: list.ResourceVersion, 1025 rvMatch: metav1.ResourceVersionMatchNotOlderThan, 1026 expectContinue: true, 1027 expectedOut: []example.Pod{*preset[0], *preset[1]}, 1028 }, 1029 { 1030 name: "filter returns two items split across multiple pages", 1031 prefix: "/pods", 1032 pred: storage.SelectionPredicate{ 1033 Field: fields.OneTermEqualSelector("metadata.name", "foo"), 1034 Label: labels.Everything(), 1035 Limit: 2, 1036 }, 1037 expectedOut: []example.Pod{*preset[2], *preset[4]}, 1038 }, 1039 { 1040 name: "filter returns two items split across multiple pages with current resource version and match=NotOlderThan", 1041 prefix: "/pods", 1042 pred: storage.SelectionPredicate{ 1043 Field: fields.OneTermEqualSelector("metadata.name", "foo"), 1044 Label: labels.Everything(), 1045 Limit: 2, 1046 }, 1047 rv: list.ResourceVersion, 1048 rvMatch: metav1.ResourceVersionMatchNotOlderThan, 1049 expectedOut: []example.Pod{*preset[2], *preset[4]}, 1050 }, 1051 { 1052 name: "filter returns one item for last page, ends on last item, not full", 1053 prefix: "/pods", 1054 pred: storage.SelectionPredicate{ 1055 Field: fields.OneTermEqualSelector("metadata.name", "foo"), 1056 Label: labels.Everything(), 1057 Limit: 2, 1058 Continue: encodeContinueOrDie("third/barfoo", int64(continueRV)), 1059 }, 1060 expectedOut: []example.Pod{*preset[4]}, 1061 }, 1062 { 1063 name: "filter returns one item for last page, starts on last item, full", 1064 prefix: "/pods", 1065 pred: storage.SelectionPredicate{ 1066 Field: fields.OneTermEqualSelector("metadata.name", "foo"), 1067 Label: labels.Everything(), 1068 Limit: 1, 1069 Continue: encodeContinueOrDie("third/barfoo", int64(continueRV)), 1070 }, 1071 expectedOut: []example.Pod{*preset[4]}, 1072 }, 1073 { 1074 name: "filter returns one item for last page, starts on last item, partial page", 1075 prefix: "/pods", 1076 pred: storage.SelectionPredicate{ 1077 Field: fields.OneTermEqualSelector("metadata.name", "foo"), 1078 Label: labels.Everything(), 1079 Limit: 2, 1080 Continue: encodeContinueOrDie("third/barfoo", int64(continueRV)), 1081 }, 1082 expectedOut: []example.Pod{*preset[4]}, 1083 }, 1084 { 1085 name: "filter returns two items, page size equal to total list size", 1086 prefix: "/pods", 1087 pred: storage.SelectionPredicate{ 1088 Field: fields.OneTermEqualSelector("metadata.name", "foo"), 1089 Label: labels.Everything(), 1090 Limit: 5, 1091 }, 1092 expectedOut: []example.Pod{*preset[2], *preset[4]}, 1093 }, 1094 { 1095 name: "filter returns two items, page size equal to total list size with current resource version and match=NotOlderThan", 1096 prefix: "/pods", 1097 pred: storage.SelectionPredicate{ 1098 Field: fields.OneTermEqualSelector("metadata.name", "foo"), 1099 Label: labels.Everything(), 1100 Limit: 5, 1101 }, 1102 rv: list.ResourceVersion, 1103 rvMatch: metav1.ResourceVersionMatchNotOlderThan, 1104 expectedOut: []example.Pod{*preset[2], *preset[4]}, 1105 }, 1106 { 1107 name: "filter returns one item, page size equal to total list size", 1108 prefix: "/pods", 1109 pred: storage.SelectionPredicate{ 1110 Field: fields.OneTermEqualSelector("metadata.name", "barfoo"), 1111 Label: labels.Everything(), 1112 Limit: 5, 1113 }, 1114 expectedOut: []example.Pod{*preset[3]}, 1115 }, 1116 { 1117 name: "filter returns one item, page size equal to total list size with current resource version and match=NotOlderThan", 1118 prefix: "/pods", 1119 pred: storage.SelectionPredicate{ 1120 Field: fields.OneTermEqualSelector("metadata.name", "barfoo"), 1121 Label: labels.Everything(), 1122 Limit: 5, 1123 }, 1124 rv: list.ResourceVersion, 1125 rvMatch: metav1.ResourceVersionMatchNotOlderThan, 1126 expectedOut: []example.Pod{*preset[3]}, 1127 }, 1128 { 1129 name: "list all items", 1130 prefix: "/pods", 1131 pred: storage.Everything, 1132 expectedOut: []example.Pod{*preset[0], *preset[1], *preset[2], *preset[3], *preset[4]}, 1133 }, 1134 { 1135 name: "list all items with current resource version and match=NotOlderThan", 1136 prefix: "/pods", 1137 pred: storage.Everything, 1138 rv: list.ResourceVersion, 1139 rvMatch: metav1.ResourceVersionMatchNotOlderThan, 1140 expectedOut: []example.Pod{*preset[0], *preset[1], *preset[2], *preset[3], *preset[4]}, 1141 }, 1142 { 1143 name: "verify list returns updated version of object; filter returns one item, page size equal to total list size with current resource version and match=NotOlderThan", 1144 prefix: "/pods", 1145 pred: storage.SelectionPredicate{ 1146 Field: fields.OneTermEqualSelector("spec.nodeName", "fakeNode"), 1147 Label: labels.Everything(), 1148 Limit: 5, 1149 }, 1150 rv: list.ResourceVersion, 1151 rvMatch: metav1.ResourceVersionMatchNotOlderThan, 1152 expectedOut: []example.Pod{*preset[0]}, 1153 }, 1154 { 1155 name: "verify list does not return deleted object; filter for deleted object, page size equal to total list size with current resource version and match=NotOlderThan", 1156 prefix: "/pods", 1157 pred: storage.SelectionPredicate{ 1158 Field: fields.OneTermEqualSelector("metadata.name", "baz"), 1159 Label: labels.Everything(), 1160 Limit: 5, 1161 }, 1162 rv: list.ResourceVersion, 1163 rvMatch: metav1.ResourceVersionMatchNotOlderThan, 1164 expectedOut: []example.Pod{}, 1165 }, 1166 { 1167 name: "test consistent List", 1168 prefix: "/pods/empty", 1169 pred: storage.Everything, 1170 rv: "", 1171 expectRV: currentRV, 1172 expectedOut: []example.Pod{}, 1173 }, 1174 { 1175 name: "test non-consistent List", 1176 prefix: "/pods/empty", 1177 pred: storage.Everything, 1178 rv: "0", 1179 expectRVFunc: resourceVersionNotOlderThan(list.ResourceVersion), 1180 expectedOut: []example.Pod{}, 1181 }, 1182 } 1183 1184 for _, tt := range tests { 1185 tt := tt 1186 t.Run(tt.name, func(t *testing.T) { 1187 // For some asynchronous implementations of storage interface (in particular watchcache), 1188 // certain requests may impact result of further requests. As an example, if we first 1189 // ensure that watchcache is synchronized up to ResourceVersion X (using Get/List requests 1190 // with NotOlderThan semantic), the further requests (even specifying earlier resource 1191 // version) will also return the result synchronized to at least ResourceVersion X. 1192 // By parallelizing test cases we ensure that the order in which test cases are defined 1193 // doesn't automatically preclude some scenarios from happening. 1194 t.Parallel() 1195 1196 if ignoreWatchCacheTests && tt.ignoreForWatchCache { 1197 t.Skip() 1198 } 1199 1200 if tt.pred.GetAttrs == nil { 1201 tt.pred.GetAttrs = getAttrs 1202 } 1203 1204 out := &example.PodList{} 1205 storageOpts := storage.ListOptions{ 1206 ResourceVersion: tt.rv, 1207 ResourceVersionMatch: tt.rvMatch, 1208 Predicate: tt.pred, 1209 Recursive: true, 1210 } 1211 err := store.GetList(ctx, tt.prefix, storageOpts, out) 1212 if tt.expectRVTooLarge { 1213 if err == nil || !apierrors.IsTimeout(err) || !storage.IsTooLargeResourceVersion(err) { 1214 t.Fatalf("expecting resource version too high error, but get: %s", err) 1215 } 1216 return 1217 } 1218 1219 if err != nil { 1220 if !tt.expectError { 1221 t.Fatalf("GetList failed: %v", err) 1222 } 1223 return 1224 } 1225 if tt.expectError { 1226 t.Fatalf("expected error but got none") 1227 } 1228 if (len(out.Continue) > 0) != tt.expectContinue { 1229 t.Errorf("unexpected continue token: %q", out.Continue) 1230 } 1231 1232 // If a client requests an exact resource version, it must be echoed back to them. 1233 if tt.expectRV != "" { 1234 if tt.expectRV != out.ResourceVersion { 1235 t.Errorf("resourceVersion in list response want=%s, got=%s", tt.expectRV, out.ResourceVersion) 1236 } 1237 } 1238 if tt.expectRVFunc != nil { 1239 if err := tt.expectRVFunc(out.ResourceVersion); err != nil { 1240 t.Errorf("resourceVersion in list response invalid: %v", err) 1241 } 1242 } 1243 1244 if tt.expectedAlternatives == nil { 1245 sort.Sort(sortablePodList(tt.expectedOut)) 1246 expectNoDiff(t, "incorrect list pods", tt.expectedOut, out.Items) 1247 } else { 1248 ExpectContains(t, "incorrect list pods", toInterfaceSlice(tt.expectedAlternatives), out.Items) 1249 } 1250 if !cmp.Equal(tt.expectedRemainingItemCount, out.RemainingItemCount) { 1251 t.Fatalf("unexpected remainingItemCount, diff: %s", cmp.Diff(tt.expectedRemainingItemCount, out.RemainingItemCount)) 1252 } 1253 }) 1254 } 1255 } 1256 1257 func RunTestConsistentList(ctx context.Context, t *testing.T, store storage.Interface, compaction Compaction, cacheEnabled, consistentReadsSupported bool) { 1258 outPod := &example.Pod{} 1259 inPod := &example.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "foo"}} 1260 err := store.Create(ctx, computePodKey(inPod), inPod, outPod, 0) 1261 if err != nil { 1262 t.Errorf("Unexpected error: %v", err) 1263 } 1264 lastObjecRV := outPod.ResourceVersion 1265 compaction(ctx, t, outPod.ResourceVersion) 1266 parsedRV, _ := strconv.Atoi(outPod.ResourceVersion) 1267 currentRV := fmt.Sprintf("%d", parsedRV+1) 1268 1269 firstNonConsistentReadRV := lastObjecRV 1270 if consistentReadsSupported && !cacheEnabled { 1271 firstNonConsistentReadRV = currentRV 1272 } 1273 1274 secondNonConsistentReadRV := lastObjecRV 1275 if consistentReadsSupported { 1276 secondNonConsistentReadRV = currentRV 1277 } 1278 1279 tcs := []struct { 1280 name string 1281 requestRV string 1282 expectResponseRV string 1283 }{ 1284 { 1285 name: "Non-consistent list before sync", 1286 requestRV: "0", 1287 expectResponseRV: firstNonConsistentReadRV, 1288 }, 1289 { 1290 name: "Consistent request returns currentRV", 1291 requestRV: "", 1292 expectResponseRV: currentRV, 1293 }, 1294 { 1295 name: "Non-consistent request after sync returns currentRV", 1296 requestRV: "0", 1297 expectResponseRV: secondNonConsistentReadRV, 1298 }, 1299 } 1300 for _, tc := range tcs { 1301 t.Run(tc.name, func(t *testing.T) { 1302 out := &example.PodList{} 1303 opts := storage.ListOptions{ 1304 ResourceVersion: tc.requestRV, 1305 Predicate: storage.Everything, 1306 } 1307 err = store.GetList(ctx, "/pods/empty", opts, out) 1308 if err != nil { 1309 t.Fatalf("GetList failed: %v", err) 1310 } 1311 if out.ResourceVersion != tc.expectResponseRV { 1312 t.Errorf("resourceVersion in list response want=%s, got=%s", tc.expectResponseRV, out.ResourceVersion) 1313 } 1314 }) 1315 } 1316 } 1317 1318 // seedMultiLevelData creates a set of keys with a multi-level structure, returning a resourceVersion 1319 // from before any were created along with the full set of objects that were persisted 1320 func seedMultiLevelData(ctx context.Context, store storage.Interface) (string, []*example.Pod, error) { 1321 // Setup storage with the following structure: 1322 // / 1323 // - first/ 1324 // | - bar 1325 // | 1326 // - second/ 1327 // | - bar 1328 // | - foo 1329 // | - [deleted] baz 1330 // | 1331 // - third/ 1332 // | - barfoo 1333 // | - foo 1334 barFirst := &example.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "first", Name: "bar"}} 1335 barSecond := &example.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "second", Name: "bar"}} 1336 fooSecond := &example.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "second", Name: "foo"}} 1337 bazSecond := &example.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "second", Name: "baz"}} 1338 barfooThird := &example.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "third", Name: "barfoo"}} 1339 fooThird := &example.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "third", Name: "foo"}} 1340 1341 preset := []struct { 1342 key string 1343 obj *example.Pod 1344 storedObj *example.Pod 1345 }{ 1346 { 1347 key: computePodKey(barFirst), 1348 obj: barFirst, 1349 }, 1350 { 1351 key: computePodKey(barSecond), 1352 obj: barSecond, 1353 }, 1354 { 1355 key: computePodKey(fooSecond), 1356 obj: fooSecond, 1357 }, 1358 { 1359 key: computePodKey(barfooThird), 1360 obj: barfooThird, 1361 }, 1362 { 1363 key: computePodKey(fooThird), 1364 obj: fooThird, 1365 }, 1366 { 1367 key: computePodKey(bazSecond), 1368 obj: bazSecond, 1369 }, 1370 } 1371 1372 // we want to figure out the resourceVersion before we create anything 1373 initialList := &example.PodList{} 1374 if err := store.GetList(ctx, "/pods", storage.ListOptions{Predicate: storage.Everything, Recursive: true}, initialList); err != nil { 1375 return "", nil, fmt.Errorf("failed to determine starting resourceVersion: %w", err) 1376 } 1377 initialRV := initialList.ResourceVersion 1378 1379 for i, ps := range preset { 1380 preset[i].storedObj = &example.Pod{} 1381 err := store.Create(ctx, ps.key, ps.obj, preset[i].storedObj, 0) 1382 if err != nil { 1383 return "", nil, fmt.Errorf("failed to create object: %w", err) 1384 } 1385 } 1386 1387 // For barFirst, we first create it with key /pods/first/bar and then we update 1388 // it by changing its spec.nodeName. The point of doing this is to be able to 1389 // test that if a pod with key /pods/first/bar is in fact returned, the returned 1390 // pod is the updated one (i.e. with spec.nodeName changed). 1391 preset[0].storedObj = &example.Pod{} 1392 if err := store.GuaranteedUpdate(ctx, computePodKey(barFirst), preset[0].storedObj, true, nil, 1393 func(input runtime.Object, _ storage.ResponseMeta) (output runtime.Object, ttl *uint64, err error) { 1394 pod := input.(*example.Pod).DeepCopy() 1395 pod.Spec.NodeName = "fakeNode" 1396 return pod, nil, nil 1397 }, nil); err != nil { 1398 return "", nil, fmt.Errorf("failed to update object: %w", err) 1399 } 1400 1401 // We now delete bazSecond provided it has been created first. We do this to enable 1402 // testing cases that had an object exist initially and then was deleted and how this 1403 // would be reflected in responses of different calls. 1404 if err := store.Delete(ctx, computePodKey(bazSecond), preset[len(preset)-1].storedObj, nil, storage.ValidateAllObjectFunc, nil); err != nil { 1405 return "", nil, fmt.Errorf("failed to delete object: %w", err) 1406 } 1407 1408 // Since we deleted bazSecond (last element of preset), we remove it from preset. 1409 preset = preset[:len(preset)-1] 1410 var created []*example.Pod 1411 for _, item := range preset { 1412 created = append(created, item.storedObj) 1413 } 1414 return initialRV, created, nil 1415 } 1416 1417 func RunTestGetListNonRecursive(ctx context.Context, t *testing.T, compaction Compaction, store storage.Interface) { 1418 key, prevStoredObj := testPropagateStore(ctx, t, store, &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test-ns"}}) 1419 prevRV, _ := strconv.Atoi(prevStoredObj.ResourceVersion) 1420 1421 storedObj := &example.Pod{} 1422 if err := store.GuaranteedUpdate(ctx, key, storedObj, false, nil, 1423 func(_ runtime.Object, _ storage.ResponseMeta) (runtime.Object, *uint64, error) { 1424 newPod := prevStoredObj.DeepCopy() 1425 newPod.Annotations = map[string]string{"version": "second"} 1426 return newPod, nil, nil 1427 }, nil); err != nil { 1428 t.Fatalf("update failed: %v", err) 1429 } 1430 objRV, _ := strconv.Atoi(storedObj.ResourceVersion) 1431 // Use compact to increase etcd global revision without changes to any resources. 1432 // The increase in resources version comes from Kubernetes compaction updating hidden key. 1433 // Used to test consistent List to confirm it returns latest etcd revision. 1434 compaction(ctx, t, prevStoredObj.ResourceVersion) 1435 1436 tests := []struct { 1437 name string 1438 key string 1439 pred storage.SelectionPredicate 1440 expectedOut []example.Pod 1441 expectedAlternatives [][]example.Pod 1442 rv string 1443 rvMatch metav1.ResourceVersionMatch 1444 expectRVTooLarge bool 1445 }{{ 1446 name: "existing key", 1447 key: key, 1448 pred: storage.Everything, 1449 expectedOut: []example.Pod{*storedObj}, 1450 }, { 1451 name: "existing key, resourceVersion=0", 1452 key: key, 1453 pred: storage.Everything, 1454 expectedAlternatives: [][]example.Pod{{}, {*prevStoredObj}, {*storedObj}}, 1455 rv: "0", 1456 }, { 1457 name: "existing key, resourceVersion=0, resourceVersionMatch=notOlderThan", 1458 key: key, 1459 pred: storage.Everything, 1460 expectedAlternatives: [][]example.Pod{{}, {*prevStoredObj}, {*storedObj}}, 1461 rv: "0", 1462 rvMatch: metav1.ResourceVersionMatchNotOlderThan, 1463 }, { 1464 name: "existing key, resourceVersion=current", 1465 key: key, 1466 pred: storage.Everything, 1467 expectedOut: []example.Pod{*storedObj}, 1468 rv: fmt.Sprintf("%d", objRV), 1469 }, { 1470 name: "existing key, resourceVersion=current, resourceVersionMatch=notOlderThan", 1471 key: key, 1472 pred: storage.Everything, 1473 expectedOut: []example.Pod{*storedObj}, 1474 rv: fmt.Sprintf("%d", objRV), 1475 rvMatch: metav1.ResourceVersionMatchNotOlderThan, 1476 }, { 1477 name: "existing key, resourceVersion=previous, resourceVersionMatch=notOlderThan", 1478 key: key, 1479 pred: storage.Everything, 1480 expectedAlternatives: [][]example.Pod{{*prevStoredObj}, {*storedObj}}, 1481 rv: fmt.Sprintf("%d", prevRV), 1482 rvMatch: metav1.ResourceVersionMatchNotOlderThan, 1483 }, { 1484 name: "existing key, resourceVersion=current, resourceVersionMatch=exact", 1485 key: key, 1486 pred: storage.Everything, 1487 expectedOut: []example.Pod{*storedObj}, 1488 rv: fmt.Sprintf("%d", objRV), 1489 rvMatch: metav1.ResourceVersionMatchExact, 1490 }, { 1491 name: "existing key, resourceVersion=previous, resourceVersionMatch=exact", 1492 key: key, 1493 pred: storage.Everything, 1494 expectedOut: []example.Pod{*prevStoredObj}, 1495 rv: fmt.Sprintf("%d", prevRV), 1496 rvMatch: metav1.ResourceVersionMatchExact, 1497 }, { 1498 name: "existing key, resourceVersion=too high", 1499 key: key, 1500 pred: storage.Everything, 1501 expectedOut: []example.Pod{*storedObj}, 1502 rv: strconv.FormatInt(math.MaxInt64, 10), 1503 expectRVTooLarge: true, 1504 }, { 1505 name: "non-existing key", 1506 key: "/non-existing", 1507 pred: storage.Everything, 1508 expectedOut: []example.Pod{}, 1509 }, { 1510 name: "with matching pod name", 1511 key: "/non-existing", 1512 pred: storage.SelectionPredicate{ 1513 Label: labels.Everything(), 1514 Field: fields.ParseSelectorOrDie("metadata.name!=" + storedObj.Name), 1515 GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { 1516 pod := obj.(*example.Pod) 1517 return nil, fields.Set{"metadata.name": pod.Name}, nil 1518 }, 1519 }, 1520 expectedOut: []example.Pod{}, 1521 }, { 1522 name: "existing key, resourceVersion=current, with not matching pod name", 1523 key: key, 1524 pred: storage.SelectionPredicate{ 1525 Label: labels.Everything(), 1526 Field: fields.ParseSelectorOrDie("metadata.name!=" + storedObj.Name), 1527 GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { 1528 pod := obj.(*example.Pod) 1529 return nil, fields.Set{"metadata.name": pod.Name}, nil 1530 }, 1531 }, 1532 expectedOut: []example.Pod{}, 1533 rv: fmt.Sprintf("%d", objRV), 1534 }} 1535 1536 for _, tt := range tests { 1537 tt := tt 1538 t.Run(tt.name, func(t *testing.T) { 1539 // For some asynchronous implementations of storage interface (in particular watchcache), 1540 // certain requests may impact result of further requests. As an example, if we first 1541 // ensure that watchcache is synchronized up to ResourceVersion X (using Get/List requests 1542 // with NotOlderThan semantic), the further requests (even specifying earlier resource 1543 // version) will also return the result synchronized to at least ResourceVersion X. 1544 // By parallelizing test cases we ensure that the order in which test cases are defined 1545 // doesn't automatically preclude some scenarios from happening. 1546 t.Parallel() 1547 1548 out := &example.PodList{} 1549 storageOpts := storage.ListOptions{ 1550 ResourceVersion: tt.rv, 1551 ResourceVersionMatch: tt.rvMatch, 1552 Predicate: tt.pred, 1553 Recursive: false, 1554 } 1555 err := store.GetList(ctx, tt.key, storageOpts, out) 1556 1557 if tt.expectRVTooLarge { 1558 if err == nil || !storage.IsTooLargeResourceVersion(err) { 1559 t.Errorf("%s: expecting resource version too high error, but get: %s", tt.name, err) 1560 } 1561 return 1562 } 1563 1564 if err != nil { 1565 t.Fatalf("GetList failed: %v", err) 1566 } 1567 if len(out.ResourceVersion) == 0 { 1568 t.Errorf("%s: unset resourceVersion", tt.name) 1569 } 1570 1571 if tt.expectedAlternatives == nil { 1572 expectNoDiff(t, "incorrect list pods", tt.expectedOut, out.Items) 1573 } else { 1574 ExpectContains(t, "incorrect list pods", toInterfaceSlice(tt.expectedAlternatives), out.Items) 1575 } 1576 }) 1577 } 1578 } 1579 1580 // RunTestGetListRecursivePrefix tests how recursive parameter works for object keys that are prefixes of each other. 1581 func RunTestGetListRecursivePrefix(ctx context.Context, t *testing.T, store storage.Interface) { 1582 fooKey, fooObj := testPropagateStore(ctx, t, store, &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test-ns"}}) 1583 fooBarKey, fooBarObj := testPropagateStore(ctx, t, store, &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foobar", Namespace: "test-ns"}}) 1584 _, otherNamespaceObj := testPropagateStore(ctx, t, store, &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test-ns2"}}) 1585 lastRev := otherNamespaceObj.ResourceVersion 1586 1587 tests := []struct { 1588 name string 1589 key string 1590 recursive bool 1591 expectedOut []example.Pod 1592 }{ 1593 { 1594 name: "NonRecursive on resource prefix doesn't return any objects", 1595 key: "/pods/", 1596 recursive: false, 1597 expectedOut: []example.Pod{}, 1598 }, 1599 { 1600 name: "Recursive on resource prefix returns all objects", 1601 key: "/pods/", 1602 recursive: true, 1603 expectedOut: []example.Pod{*fooObj, *fooBarObj, *otherNamespaceObj}, 1604 }, 1605 { 1606 name: "NonRecursive on namespace prefix doesn't return any objects", 1607 key: "/pods/test-ns/", 1608 recursive: false, 1609 expectedOut: []example.Pod{}, 1610 }, 1611 { 1612 name: "Recursive on resource prefix returns objects in the namespace", 1613 key: "/pods/test-ns/", 1614 recursive: true, 1615 expectedOut: []example.Pod{*fooObj, *fooBarObj}, 1616 }, 1617 { 1618 name: "NonRecursive on object key (prefix) returns object and no other objects with the same prefix", 1619 key: fooKey, 1620 recursive: false, 1621 expectedOut: []example.Pod{*fooObj}, 1622 }, 1623 { 1624 name: "Recursive on object key (prefix) doesn't return anything", 1625 key: fooKey, 1626 recursive: true, 1627 expectedOut: []example.Pod{}, 1628 }, 1629 { 1630 name: "NonRecursive on object key (no-prefix) return object", 1631 key: fooBarKey, 1632 recursive: false, 1633 expectedOut: []example.Pod{*fooBarObj}, 1634 }, 1635 { 1636 name: "Recursive on object key (no-prefix) doesn't return anything", 1637 key: fooBarKey, 1638 recursive: true, 1639 expectedOut: []example.Pod{}, 1640 }, 1641 } 1642 1643 listTypes := []struct { 1644 name string 1645 ResourceVersion string 1646 Match metav1.ResourceVersionMatch 1647 }{ 1648 { 1649 name: "Exact", 1650 ResourceVersion: lastRev, 1651 Match: metav1.ResourceVersionMatchExact, 1652 }, 1653 { 1654 name: "Consistent", 1655 ResourceVersion: "", 1656 }, 1657 { 1658 name: "NotOlderThan", 1659 ResourceVersion: "0", 1660 Match: metav1.ResourceVersionMatchNotOlderThan, 1661 }, 1662 } 1663 1664 for _, listType := range listTypes { 1665 listType := listType 1666 t.Run(listType.name, func(t *testing.T) { 1667 for _, tt := range tests { 1668 tt := tt 1669 t.Run(tt.name, func(t *testing.T) { 1670 out := &example.PodList{} 1671 storageOpts := storage.ListOptions{ 1672 ResourceVersion: listType.ResourceVersion, 1673 ResourceVersionMatch: listType.Match, 1674 Recursive: tt.recursive, 1675 Predicate: storage.Everything, 1676 } 1677 err := store.GetList(ctx, tt.key, storageOpts, out) 1678 if err != nil { 1679 t.Fatalf("GetList failed: %v", err) 1680 } 1681 expectNoDiff(t, "incorrect list pods", tt.expectedOut, out.Items) 1682 }) 1683 } 1684 }) 1685 } 1686 } 1687 1688 type CallsValidation func(t *testing.T, pageSize, estimatedProcessedObjects uint64) 1689 1690 func RunTestListContinuation(ctx context.Context, t *testing.T, store storage.Interface, validation CallsValidation) { 1691 // Setup storage with the following structure: 1692 // / 1693 // - first/ 1694 // | - bar 1695 // | 1696 // - second/ 1697 // | - bar 1698 // | - foo 1699 barFirst := &example.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "first", Name: "bar"}} 1700 barSecond := &example.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "second", Name: "bar"}} 1701 fooSecond := &example.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "second", Name: "foo"}} 1702 preset := []struct { 1703 key string 1704 obj *example.Pod 1705 storedObj *example.Pod 1706 }{ 1707 { 1708 key: computePodKey(barFirst), 1709 obj: barFirst, 1710 }, 1711 { 1712 key: computePodKey(barSecond), 1713 obj: barSecond, 1714 }, 1715 { 1716 key: computePodKey(fooSecond), 1717 obj: fooSecond, 1718 }, 1719 } 1720 1721 var currentRV string 1722 for i, ps := range preset { 1723 preset[i].storedObj = &example.Pod{} 1724 err := store.Create(ctx, ps.key, ps.obj, preset[i].storedObj, 0) 1725 if err != nil { 1726 t.Fatalf("Set failed: %v", err) 1727 } 1728 currentRV = preset[i].storedObj.ResourceVersion 1729 } 1730 1731 // test continuations 1732 out := &example.PodList{} 1733 pred := func(limit int64, continueValue string) storage.SelectionPredicate { 1734 return storage.SelectionPredicate{ 1735 Limit: limit, 1736 Continue: continueValue, 1737 Label: labels.Everything(), 1738 Field: fields.Everything(), 1739 GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { 1740 pod := obj.(*example.Pod) 1741 return nil, fields.Set{"metadata.name": pod.Name}, nil 1742 }, 1743 } 1744 } 1745 options := storage.ListOptions{ 1746 // Limit is ignored when ResourceVersion is set to 0. 1747 // Set it to consistent read. 1748 ResourceVersion: "", 1749 Predicate: pred(1, ""), 1750 Recursive: true, 1751 } 1752 if err := store.GetList(ctx, "/pods", options, out); err != nil { 1753 t.Fatalf("Unable to get initial list: %v", err) 1754 } 1755 if len(out.Continue) == 0 { 1756 t.Fatalf("No continuation token set") 1757 } 1758 expectNoDiff(t, "incorrect first page", []example.Pod{*preset[0].storedObj}, out.Items) 1759 if out.ResourceVersion != currentRV { 1760 t.Errorf("Expect output.ResourceVersion = %s, but got %s", currentRV, out.ResourceVersion) 1761 } 1762 if validation != nil { 1763 validation(t, 1, 1) 1764 } 1765 1766 continueFromSecondItem := out.Continue 1767 1768 // no limit, should get two items 1769 out = &example.PodList{} 1770 options = storage.ListOptions{ 1771 // ResourceVersion should be unset when setting continuation token. 1772 ResourceVersion: "", 1773 Predicate: pred(0, continueFromSecondItem), 1774 Recursive: true, 1775 } 1776 if err := store.GetList(ctx, "/pods", options, out); err != nil { 1777 t.Fatalf("Unable to get second page: %v", err) 1778 } 1779 if len(out.Continue) != 0 { 1780 t.Fatalf("Unexpected continuation token set") 1781 } 1782 key, rv, err := storage.DecodeContinue(continueFromSecondItem, "/pods") 1783 t.Logf("continue token was %d %s %v", rv, key, err) 1784 expectNoDiff(t, "incorrect second page", []example.Pod{*preset[1].storedObj, *preset[2].storedObj}, out.Items) 1785 if out.ResourceVersion != currentRV { 1786 t.Errorf("Expect output.ResourceVersion = %s, but got %s", currentRV, out.ResourceVersion) 1787 } 1788 if validation != nil { 1789 validation(t, 0, 2) 1790 } 1791 1792 // limit, should get two more pages 1793 out = &example.PodList{} 1794 options = storage.ListOptions{ 1795 // ResourceVersion should be unset when setting continuation token. 1796 ResourceVersion: "", 1797 Predicate: pred(1, continueFromSecondItem), 1798 Recursive: true, 1799 } 1800 if err := store.GetList(ctx, "/pods", options, out); err != nil { 1801 t.Fatalf("Unable to get second page: %v", err) 1802 } 1803 if len(out.Continue) == 0 { 1804 t.Fatalf("No continuation token set") 1805 } 1806 expectNoDiff(t, "incorrect second page", []example.Pod{*preset[1].storedObj}, out.Items) 1807 if out.ResourceVersion != currentRV { 1808 t.Errorf("Expect output.ResourceVersion = %s, but got %s", currentRV, out.ResourceVersion) 1809 } 1810 if validation != nil { 1811 validation(t, 1, 1) 1812 } 1813 1814 continueFromThirdItem := out.Continue 1815 1816 out = &example.PodList{} 1817 options = storage.ListOptions{ 1818 // ResourceVersion should be unset when setting continuation token. 1819 ResourceVersion: "", 1820 Predicate: pred(1, continueFromThirdItem), 1821 Recursive: true, 1822 } 1823 if err := store.GetList(ctx, "/pods", options, out); err != nil { 1824 t.Fatalf("Unable to get second page: %v", err) 1825 } 1826 if len(out.Continue) != 0 { 1827 t.Fatalf("Unexpected continuation token set") 1828 } 1829 expectNoDiff(t, "incorrect third page", []example.Pod{*preset[2].storedObj}, out.Items) 1830 if out.ResourceVersion != currentRV { 1831 t.Errorf("Expect output.ResourceVersion = %s, but got %s", currentRV, out.ResourceVersion) 1832 } 1833 if validation != nil { 1834 validation(t, 1, 1) 1835 } 1836 } 1837 1838 func RunTestListPaginationRareObject(ctx context.Context, t *testing.T, store storage.Interface, validation CallsValidation) { 1839 podCount := 1000 1840 var pods []*example.Pod 1841 for i := 0; i < podCount; i++ { 1842 obj := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("pod-%d", i)}} 1843 key := computePodKey(obj) 1844 storedObj := &example.Pod{} 1845 err := store.Create(ctx, key, obj, storedObj, 0) 1846 if err != nil { 1847 t.Fatalf("Set failed: %v", err) 1848 } 1849 pods = append(pods, storedObj) 1850 } 1851 1852 out := &example.PodList{} 1853 options := storage.ListOptions{ 1854 Predicate: storage.SelectionPredicate{ 1855 Limit: 1, 1856 Label: labels.Everything(), 1857 Field: fields.OneTermEqualSelector("metadata.name", "pod-999"), 1858 GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { 1859 pod := obj.(*example.Pod) 1860 return nil, fields.Set{"metadata.name": pod.Name}, nil 1861 }, 1862 }, 1863 Recursive: true, 1864 } 1865 if err := store.GetList(ctx, "/pods", options, out); err != nil { 1866 t.Fatalf("Unable to get initial list: %v", err) 1867 } 1868 if len(out.Continue) != 0 { 1869 t.Errorf("Unexpected continuation token set") 1870 } 1871 if len(out.Items) != 1 || !reflect.DeepEqual(&out.Items[0], pods[999]) { 1872 t.Fatalf("Unexpected first page: %#v", out.Items) 1873 } 1874 if validation != nil { 1875 validation(t, 1, uint64(podCount)) 1876 } 1877 } 1878 1879 func RunTestListContinuationWithFilter(ctx context.Context, t *testing.T, store storage.Interface, validation CallsValidation) { 1880 foo1 := &example.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "1", Name: "foo"}} 1881 bar2 := &example.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "2", Name: "bar"}} // this should not match 1882 foo3 := &example.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "3", Name: "foo"}} 1883 foo4 := &example.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "4", Name: "foo"}} 1884 preset := []struct { 1885 key string 1886 obj *example.Pod 1887 storedObj *example.Pod 1888 }{ 1889 { 1890 key: computePodKey(foo1), 1891 obj: foo1, 1892 }, 1893 { 1894 key: computePodKey(bar2), 1895 obj: bar2, 1896 }, 1897 { 1898 key: computePodKey(foo3), 1899 obj: foo3, 1900 }, 1901 { 1902 key: computePodKey(foo4), 1903 obj: foo4, 1904 }, 1905 } 1906 1907 var currentRV string 1908 for i, ps := range preset { 1909 preset[i].storedObj = &example.Pod{} 1910 err := store.Create(ctx, ps.key, ps.obj, preset[i].storedObj, 0) 1911 if err != nil { 1912 t.Fatalf("Set failed: %v", err) 1913 } 1914 currentRV = preset[i].storedObj.ResourceVersion 1915 } 1916 1917 // the first list call should try to get 2 items from etcd (and only those items should be returned) 1918 // the field selector should result in it reading 3 items via the transformer 1919 // the chunking should result in 2 etcd Gets 1920 // there should be a continueValue because there is more data 1921 out := &example.PodList{} 1922 pred := func(limit int64, continueValue string) storage.SelectionPredicate { 1923 return storage.SelectionPredicate{ 1924 Limit: limit, 1925 Continue: continueValue, 1926 Label: labels.Everything(), 1927 Field: fields.OneTermNotEqualSelector("metadata.name", "bar"), 1928 GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { 1929 pod := obj.(*example.Pod) 1930 return nil, fields.Set{"metadata.name": pod.Name}, nil 1931 }, 1932 } 1933 } 1934 options := storage.ListOptions{ 1935 // Limit is ignored when ResourceVersion is set to 0. 1936 // Set it to consistent read. 1937 ResourceVersion: "", 1938 Predicate: pred(2, ""), 1939 Recursive: true, 1940 } 1941 if err := store.GetList(ctx, "/pods", options, out); err != nil { 1942 t.Errorf("Unable to get initial list: %v", err) 1943 } 1944 if len(out.Continue) == 0 { 1945 t.Errorf("No continuation token set") 1946 } 1947 expectNoDiff(t, "incorrect first page", []example.Pod{*preset[0].storedObj, *preset[2].storedObj}, out.Items) 1948 if out.ResourceVersion != currentRV { 1949 t.Errorf("Expect output.ResourceVersion = %s, but got %s", currentRV, out.ResourceVersion) 1950 } 1951 if validation != nil { 1952 validation(t, 2, 3) 1953 } 1954 1955 // the rest of the test does not make sense if the previous call failed 1956 if t.Failed() { 1957 return 1958 } 1959 1960 cont := out.Continue 1961 1962 // the second list call should try to get 2 more items from etcd 1963 // but since there is only one item left, that is all we should get with no continueValue 1964 // both read counters should be incremented for the singular calls they make in this case 1965 out = &example.PodList{} 1966 options = storage.ListOptions{ 1967 // ResourceVersion should be unset when setting continuation token. 1968 ResourceVersion: "", 1969 Predicate: pred(2, cont), 1970 Recursive: true, 1971 } 1972 if err := store.GetList(ctx, "/pods", options, out); err != nil { 1973 t.Errorf("Unable to get second page: %v", err) 1974 } 1975 if len(out.Continue) != 0 { 1976 t.Errorf("Unexpected continuation token set") 1977 } 1978 expectNoDiff(t, "incorrect second page", []example.Pod{*preset[3].storedObj}, out.Items) 1979 if out.ResourceVersion != currentRV { 1980 t.Errorf("Expect output.ResourceVersion = %s, but got %s", currentRV, out.ResourceVersion) 1981 } 1982 if validation != nil { 1983 validation(t, 2, 1) 1984 } 1985 } 1986 1987 type Compaction func(ctx context.Context, t *testing.T, resourceVersion string) 1988 1989 func RunTestListInconsistentContinuation(ctx context.Context, t *testing.T, store storage.Interface, compaction Compaction) { 1990 if compaction == nil { 1991 t.Skipf("compaction callback not provided") 1992 } 1993 1994 // Setup storage with the following structure: 1995 // / 1996 // - first/ 1997 // | - bar 1998 // | 1999 // - second/ 2000 // | - bar 2001 // | - foo 2002 barFirst := &example.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "first", Name: "bar"}} 2003 barSecond := &example.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "second", Name: "bar"}} 2004 fooSecond := &example.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "second", Name: "foo"}} 2005 preset := []struct { 2006 key string 2007 obj *example.Pod 2008 storedObj *example.Pod 2009 }{ 2010 { 2011 key: computePodKey(barFirst), 2012 obj: barFirst, 2013 }, 2014 { 2015 key: computePodKey(barSecond), 2016 obj: barSecond, 2017 }, 2018 { 2019 key: computePodKey(fooSecond), 2020 obj: fooSecond, 2021 }, 2022 } 2023 for i, ps := range preset { 2024 preset[i].storedObj = &example.Pod{} 2025 err := store.Create(ctx, ps.key, ps.obj, preset[i].storedObj, 0) 2026 if err != nil { 2027 t.Fatalf("Set failed: %v", err) 2028 } 2029 } 2030 2031 pred := func(limit int64, continueValue string) storage.SelectionPredicate { 2032 return storage.SelectionPredicate{ 2033 Limit: limit, 2034 Continue: continueValue, 2035 Label: labels.Everything(), 2036 Field: fields.Everything(), 2037 GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { 2038 pod := obj.(*example.Pod) 2039 return nil, fields.Set{"metadata.name": pod.Name}, nil 2040 }, 2041 } 2042 } 2043 2044 out := &example.PodList{} 2045 options := storage.ListOptions{ 2046 ResourceVersion: "0", 2047 Predicate: pred(1, ""), 2048 Recursive: true, 2049 } 2050 if err := store.GetList(ctx, "/pods", options, out); err != nil { 2051 t.Fatalf("Unable to get initial list: %v", err) 2052 } 2053 if len(out.Continue) == 0 { 2054 t.Fatalf("No continuation token set") 2055 } 2056 expectNoDiff(t, "incorrect first page", []example.Pod{*preset[0].storedObj}, out.Items) 2057 2058 continueFromSecondItem := out.Continue 2059 2060 // update /second/bar 2061 oldName := preset[2].obj.Name 2062 newPod := &example.Pod{ 2063 ObjectMeta: metav1.ObjectMeta{ 2064 Name: oldName, 2065 Labels: map[string]string{ 2066 "state": "new", 2067 }, 2068 }, 2069 } 2070 if err := store.GuaranteedUpdate(ctx, preset[2].key, preset[2].storedObj, false, nil, 2071 func(_ runtime.Object, _ storage.ResponseMeta) (runtime.Object, *uint64, error) { 2072 return newPod, nil, nil 2073 }, newPod); err != nil { 2074 t.Fatalf("update failed: %v", err) 2075 } 2076 2077 // compact to latest revision. 2078 lastRVString := preset[2].storedObj.ResourceVersion 2079 compaction(ctx, t, lastRVString) 2080 2081 // The old continue token should have expired 2082 options = storage.ListOptions{ 2083 ResourceVersion: "0", 2084 Predicate: pred(0, continueFromSecondItem), 2085 Recursive: true, 2086 } 2087 err := store.GetList(ctx, "/pods", options, out) 2088 if err == nil { 2089 t.Fatalf("unexpected no error") 2090 } 2091 if !strings.Contains(err.Error(), "The provided continue parameter is too old ") { 2092 t.Fatalf("unexpected error message %v", err) 2093 } 2094 status, ok := err.(apierrors.APIStatus) 2095 if !ok { 2096 t.Fatalf("expect error of implements the APIStatus interface, got %v", reflect.TypeOf(err)) 2097 } 2098 inconsistentContinueFromSecondItem := status.Status().ListMeta.Continue 2099 if len(inconsistentContinueFromSecondItem) == 0 { 2100 t.Fatalf("expect non-empty continue token") 2101 } 2102 2103 out = &example.PodList{} 2104 options = storage.ListOptions{ 2105 ResourceVersion: "0", 2106 Predicate: pred(1, inconsistentContinueFromSecondItem), 2107 Recursive: true, 2108 } 2109 if err := store.GetList(ctx, "/pods", options, out); err != nil { 2110 t.Fatalf("Unable to get second page: %v", err) 2111 } 2112 if len(out.Continue) == 0 { 2113 t.Fatalf("No continuation token set") 2114 } 2115 validateResourceVersion := resourceVersionNotOlderThan(lastRVString) 2116 expectNoDiff(t, "incorrect second page", []example.Pod{*preset[1].storedObj}, out.Items) 2117 if err := validateResourceVersion(out.ResourceVersion); err != nil { 2118 t.Fatal(err) 2119 } 2120 continueFromThirdItem := out.Continue 2121 resolvedResourceVersionFromThirdItem := out.ResourceVersion 2122 out = &example.PodList{} 2123 options = storage.ListOptions{ 2124 ResourceVersion: "0", 2125 Predicate: pred(1, continueFromThirdItem), 2126 Recursive: true, 2127 } 2128 if err := store.GetList(ctx, "/pods", options, out); err != nil { 2129 t.Fatalf("Unable to get second page: %v", err) 2130 } 2131 if len(out.Continue) != 0 { 2132 t.Fatalf("Unexpected continuation token set") 2133 } 2134 expectNoDiff(t, "incorrect third page", []example.Pod{*preset[2].storedObj}, out.Items) 2135 if out.ResourceVersion != resolvedResourceVersionFromThirdItem { 2136 t.Fatalf("Expected list resource version to be %s, got %s", resolvedResourceVersionFromThirdItem, out.ResourceVersion) 2137 } 2138 } 2139 2140 type PrefixTransformerModifier func(*PrefixTransformer) value.Transformer 2141 2142 type InterfaceWithPrefixTransformer interface { 2143 storage.Interface 2144 2145 UpdatePrefixTransformer(PrefixTransformerModifier) func() 2146 } 2147 2148 func RunTestListResourceVersionMatch(ctx context.Context, t *testing.T, store InterfaceWithPrefixTransformer) { 2149 nextPod := func(index uint32) (string, *example.Pod) { 2150 obj := &example.Pod{ 2151 ObjectMeta: metav1.ObjectMeta{ 2152 Name: fmt.Sprintf("pod-%d", index), 2153 Labels: map[string]string{ 2154 "even": strconv.FormatBool(index%2 == 0), 2155 }, 2156 }, 2157 } 2158 return computePodKey(obj), obj 2159 } 2160 2161 transformer := &reproducingTransformer{ 2162 store: store, 2163 nextObject: nextPod, 2164 } 2165 2166 revertTransformer := store.UpdatePrefixTransformer( 2167 func(previousTransformer *PrefixTransformer) value.Transformer { 2168 transformer.wrapped = previousTransformer 2169 return transformer 2170 }) 2171 defer revertTransformer() 2172 2173 for i := 0; i < 5; i++ { 2174 if err := transformer.createObject(ctx); err != nil { 2175 t.Fatalf("failed to create object: %v", err) 2176 } 2177 } 2178 2179 getAttrs := func(obj runtime.Object) (labels.Set, fields.Set, error) { 2180 pod, ok := obj.(*example.Pod) 2181 if !ok { 2182 return nil, nil, fmt.Errorf("invalid object") 2183 } 2184 return labels.Set(pod.Labels), nil, nil 2185 } 2186 predicate := storage.SelectionPredicate{ 2187 Label: labels.Set{"even": "true"}.AsSelector(), 2188 GetAttrs: getAttrs, 2189 Limit: 4, 2190 } 2191 2192 result1 := example.PodList{} 2193 options := storage.ListOptions{ 2194 Predicate: predicate, 2195 Recursive: true, 2196 } 2197 if err := store.GetList(ctx, "/pods", options, &result1); err != nil { 2198 t.Fatalf("failed to list objects: %v", err) 2199 } 2200 2201 // List objects from the returned resource version. 2202 options = storage.ListOptions{ 2203 Predicate: predicate, 2204 ResourceVersion: result1.ResourceVersion, 2205 ResourceVersionMatch: metav1.ResourceVersionMatchExact, 2206 Recursive: true, 2207 } 2208 2209 result2 := example.PodList{} 2210 if err := store.GetList(ctx, "/pods", options, &result2); err != nil { 2211 t.Fatalf("failed to list objects: %v", err) 2212 } 2213 2214 expectNoDiff(t, "incorrect lists", result1, result2) 2215 2216 // Now also verify the ResourceVersionMatchNotOlderThan. 2217 options.ResourceVersionMatch = metav1.ResourceVersionMatchNotOlderThan 2218 2219 result3 := example.PodList{} 2220 if err := store.GetList(ctx, "/pods", options, &result3); err != nil { 2221 t.Fatalf("failed to list objects: %v", err) 2222 } 2223 2224 options.ResourceVersion = result3.ResourceVersion 2225 options.ResourceVersionMatch = metav1.ResourceVersionMatchExact 2226 2227 result4 := example.PodList{} 2228 if err := store.GetList(ctx, "/pods", options, &result4); err != nil { 2229 t.Fatalf("failed to list objects: %v", err) 2230 } 2231 2232 expectNoDiff(t, "incorrect lists", result3, result4) 2233 } 2234 2235 func RunTestGuaranteedUpdate(ctx context.Context, t *testing.T, store InterfaceWithPrefixTransformer, validation KeyValidation) { 2236 inputObj := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test-ns", UID: "A"}} 2237 key := computePodKey(inputObj) 2238 2239 tests := []struct { 2240 name string 2241 key string 2242 ignoreNotFound bool 2243 precondition *storage.Preconditions 2244 expectNotFoundErr bool 2245 expectInvalidObjErr bool 2246 expectNoUpdate bool 2247 transformStale bool 2248 hasSelfLink bool 2249 }{{ 2250 name: "non-existing key, ignoreNotFound=false", 2251 key: "/non-existing", 2252 ignoreNotFound: false, 2253 precondition: nil, 2254 expectNotFoundErr: true, 2255 expectInvalidObjErr: false, 2256 expectNoUpdate: false, 2257 }, { 2258 name: "non-existing key, ignoreNotFound=true", 2259 key: "/non-existing", 2260 ignoreNotFound: true, 2261 precondition: nil, 2262 expectNotFoundErr: false, 2263 expectInvalidObjErr: false, 2264 expectNoUpdate: false, 2265 }, { 2266 name: "existing key", 2267 key: key, 2268 ignoreNotFound: false, 2269 precondition: nil, 2270 expectNotFoundErr: false, 2271 expectInvalidObjErr: false, 2272 expectNoUpdate: false, 2273 }, { 2274 name: "same data", 2275 key: key, 2276 ignoreNotFound: false, 2277 precondition: nil, 2278 expectNotFoundErr: false, 2279 expectInvalidObjErr: false, 2280 expectNoUpdate: true, 2281 }, { 2282 name: "same data, a selfLink", 2283 key: key, 2284 ignoreNotFound: false, 2285 precondition: nil, 2286 expectNotFoundErr: false, 2287 expectInvalidObjErr: false, 2288 expectNoUpdate: true, 2289 hasSelfLink: true, 2290 }, { 2291 name: "same data, stale", 2292 key: key, 2293 ignoreNotFound: false, 2294 precondition: nil, 2295 expectNotFoundErr: false, 2296 expectInvalidObjErr: false, 2297 expectNoUpdate: false, 2298 transformStale: true, 2299 }, { 2300 name: "UID match", 2301 key: key, 2302 ignoreNotFound: false, 2303 precondition: storage.NewUIDPreconditions("A"), 2304 expectNotFoundErr: false, 2305 expectInvalidObjErr: false, 2306 expectNoUpdate: true, 2307 }, { 2308 name: "UID mismatch", 2309 key: key, 2310 ignoreNotFound: false, 2311 precondition: storage.NewUIDPreconditions("B"), 2312 expectNotFoundErr: false, 2313 expectInvalidObjErr: true, 2314 expectNoUpdate: true, 2315 }} 2316 2317 for i, tt := range tests { 2318 t.Run(tt.name, func(t *testing.T) { 2319 key, storeObj := testPropagateStore(ctx, t, store, inputObj) 2320 2321 out := &example.Pod{} 2322 annotations := map[string]string{"version": fmt.Sprintf("%d", i)} 2323 if tt.expectNoUpdate { 2324 annotations = nil 2325 } 2326 2327 if tt.transformStale { 2328 revertTransformer := store.UpdatePrefixTransformer( 2329 func(transformer *PrefixTransformer) value.Transformer { 2330 transformer.stale = true 2331 return transformer 2332 }) 2333 defer revertTransformer() 2334 } 2335 2336 version := storeObj.ResourceVersion 2337 err := store.GuaranteedUpdate(ctx, tt.key, out, tt.ignoreNotFound, tt.precondition, 2338 storage.SimpleUpdate(func(obj runtime.Object) (runtime.Object, error) { 2339 if tt.expectNotFoundErr && tt.ignoreNotFound { 2340 if pod := obj.(*example.Pod); pod.Name != "" { 2341 t.Errorf("%s: expecting zero value, but get=%#v", tt.name, pod) 2342 } 2343 } 2344 pod := *storeObj 2345 if tt.hasSelfLink { 2346 pod.SelfLink = "testlink" 2347 } 2348 pod.Annotations = annotations 2349 return &pod, nil 2350 }), nil) 2351 2352 if tt.expectNotFoundErr { 2353 if err == nil || !storage.IsNotFound(err) { 2354 t.Errorf("%s: expecting not found error, but get: %v", tt.name, err) 2355 } 2356 return 2357 } 2358 if tt.expectInvalidObjErr { 2359 if err == nil || !storage.IsInvalidObj(err) { 2360 t.Errorf("%s: expecting invalid UID error, but get: %s", tt.name, err) 2361 } 2362 return 2363 } 2364 if err != nil { 2365 t.Fatalf("%s: GuaranteedUpdate failed: %v", tt.name, err) 2366 } 2367 if !reflect.DeepEqual(out.ObjectMeta.Annotations, annotations) { 2368 t.Errorf("%s: pod annotations want=%s, get=%s", tt.name, annotations, out.ObjectMeta.Annotations) 2369 } 2370 if out.SelfLink != "" { 2371 t.Errorf("%s: selfLink should not be set", tt.name) 2372 } 2373 2374 // verify that kv pair is not empty after set and that the underlying data matches expectations 2375 validation(ctx, t, key) 2376 2377 switch tt.expectNoUpdate { 2378 case true: 2379 if version != out.ResourceVersion { 2380 t.Errorf("%s: expect no version change, before=%s, after=%s", tt.name, version, out.ResourceVersion) 2381 } 2382 case false: 2383 if version == out.ResourceVersion { 2384 t.Errorf("%s: expect version change, but get the same version=%s", tt.name, version) 2385 } 2386 } 2387 }) 2388 } 2389 } 2390 2391 func RunTestGuaranteedUpdateWithTTL(ctx context.Context, t *testing.T, store storage.Interface) { 2392 input := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test-ns"}} 2393 key := computePodKey(input) 2394 2395 out := &example.Pod{} 2396 err := store.GuaranteedUpdate(ctx, key, out, true, nil, 2397 func(_ runtime.Object, _ storage.ResponseMeta) (runtime.Object, *uint64, error) { 2398 ttl := uint64(1) 2399 return input, &ttl, nil 2400 }, nil) 2401 if err != nil { 2402 t.Fatalf("Create failed: %v", err) 2403 } 2404 2405 w, err := store.Watch(ctx, key, storage.ListOptions{ResourceVersion: out.ResourceVersion, Predicate: storage.Everything}) 2406 if err != nil { 2407 t.Fatalf("Watch failed: %v", err) 2408 } 2409 testCheckEventType(t, w, watch.Deleted) 2410 } 2411 2412 func RunTestGuaranteedUpdateChecksStoredData(ctx context.Context, t *testing.T, store InterfaceWithPrefixTransformer) { 2413 input := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test-ns"}} 2414 key := computePodKey(input) 2415 2416 // serialize input into etcd with data that would be normalized by a write - 2417 // in this case, leading whitespace 2418 revertTransformer := store.UpdatePrefixTransformer( 2419 func(transformer *PrefixTransformer) value.Transformer { 2420 transformer.prefix = []byte(string(transformer.prefix) + " ") 2421 return transformer 2422 }) 2423 _, initial := testPropagateStore(ctx, t, store, input) 2424 revertTransformer() 2425 2426 // this update should write the canonical value to etcd because the new serialization differs 2427 // from the stored serialization 2428 input.ResourceVersion = initial.ResourceVersion 2429 out := &example.Pod{} 2430 err := store.GuaranteedUpdate(ctx, key, out, true, nil, 2431 func(_ runtime.Object, _ storage.ResponseMeta) (runtime.Object, *uint64, error) { 2432 return input, nil, nil 2433 }, input) 2434 if err != nil { 2435 t.Fatalf("Update failed: %v", err) 2436 } 2437 if out.ResourceVersion == initial.ResourceVersion { 2438 t.Errorf("guaranteed update should have updated the serialized data, got %#v", out) 2439 } 2440 2441 lastVersion := out.ResourceVersion 2442 2443 // this update should not write to etcd because the input matches the stored data 2444 input = out 2445 out = &example.Pod{} 2446 err = store.GuaranteedUpdate(ctx, key, out, true, nil, 2447 func(_ runtime.Object, _ storage.ResponseMeta) (runtime.Object, *uint64, error) { 2448 return input, nil, nil 2449 }, input) 2450 if err != nil { 2451 t.Fatalf("Update failed: %v", err) 2452 } 2453 if out.ResourceVersion != lastVersion { 2454 t.Errorf("guaranteed update should have short-circuited write, got %#v", out) 2455 } 2456 2457 revertTransformer = store.UpdatePrefixTransformer( 2458 func(transformer *PrefixTransformer) value.Transformer { 2459 transformer.stale = true 2460 return transformer 2461 }) 2462 defer revertTransformer() 2463 2464 // this update should write to etcd because the transformer reported stale 2465 err = store.GuaranteedUpdate(ctx, key, out, true, nil, 2466 func(_ runtime.Object, _ storage.ResponseMeta) (runtime.Object, *uint64, error) { 2467 return input, nil, nil 2468 }, input) 2469 if err != nil { 2470 t.Fatalf("Update failed: %v", err) 2471 } 2472 if out.ResourceVersion == lastVersion { 2473 t.Errorf("guaranteed update should have written to etcd when transformer reported stale, got %#v", out) 2474 } 2475 } 2476 2477 func RunTestGuaranteedUpdateWithConflict(ctx context.Context, t *testing.T, store storage.Interface) { 2478 key, _ := testPropagateStore(ctx, t, store, &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test-ns"}}) 2479 2480 errChan := make(chan error, 1) 2481 var firstToFinish sync.WaitGroup 2482 var secondToEnter sync.WaitGroup 2483 firstToFinish.Add(1) 2484 secondToEnter.Add(1) 2485 2486 go func() { 2487 err := store.GuaranteedUpdate(ctx, key, &example.Pod{}, false, nil, 2488 storage.SimpleUpdate(func(obj runtime.Object) (runtime.Object, error) { 2489 pod := obj.(*example.Pod) 2490 pod.Name = "foo-1" 2491 secondToEnter.Wait() 2492 return pod, nil 2493 }), nil) 2494 firstToFinish.Done() 2495 errChan <- err 2496 }() 2497 2498 updateCount := 0 2499 err := store.GuaranteedUpdate(ctx, key, &example.Pod{}, false, nil, 2500 storage.SimpleUpdate(func(obj runtime.Object) (runtime.Object, error) { 2501 if updateCount == 0 { 2502 secondToEnter.Done() 2503 firstToFinish.Wait() 2504 } 2505 updateCount++ 2506 pod := obj.(*example.Pod) 2507 pod.Name = "foo-2" 2508 return pod, nil 2509 }), nil) 2510 if err != nil { 2511 t.Fatalf("Second GuaranteedUpdate error %#v", err) 2512 } 2513 if err := <-errChan; err != nil { 2514 t.Fatalf("First GuaranteedUpdate error %#v", err) 2515 } 2516 2517 if updateCount != 2 { 2518 t.Errorf("Should have conflict and called update func twice") 2519 } 2520 } 2521 2522 func RunTestGuaranteedUpdateWithSuggestionAndConflict(ctx context.Context, t *testing.T, store storage.Interface) { 2523 key, originalPod := testPropagateStore(ctx, t, store, &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test-ns"}}) 2524 2525 // First, update without a suggestion so originalPod is outdated 2526 updatedPod := &example.Pod{} 2527 err := store.GuaranteedUpdate(ctx, key, updatedPod, false, nil, 2528 storage.SimpleUpdate(func(obj runtime.Object) (runtime.Object, error) { 2529 pod := obj.(*example.Pod) 2530 pod.Generation = 2 2531 return pod, nil 2532 }), 2533 nil, 2534 ) 2535 if err != nil { 2536 t.Fatalf("unexpected error: %v", err) 2537 } 2538 2539 // Second, update using the outdated originalPod as the suggestion. Return a conflict error when 2540 // passed originalPod, and make sure that SimpleUpdate is called a second time after a live lookup 2541 // with the value of updatedPod. 2542 sawConflict := false 2543 updatedPod2 := &example.Pod{} 2544 err = store.GuaranteedUpdate(ctx, key, updatedPod2, false, nil, 2545 storage.SimpleUpdate(func(obj runtime.Object) (runtime.Object, error) { 2546 pod := obj.(*example.Pod) 2547 if pod.Generation != 2 { 2548 if sawConflict { 2549 t.Fatalf("unexpected second conflict") 2550 } 2551 sawConflict = true 2552 // simulated stale object - return a conflict 2553 return nil, apierrors.NewConflict(example.SchemeGroupVersion.WithResource("pods").GroupResource(), "name", errors.New("foo")) 2554 } 2555 pod.Generation = 3 2556 return pod, nil 2557 }), 2558 originalPod, 2559 ) 2560 if err != nil { 2561 t.Fatalf("unexpected error: %v", err) 2562 } 2563 if updatedPod2.Generation != 3 { 2564 t.Errorf("unexpected pod generation: %q", updatedPod2.Generation) 2565 } 2566 2567 // Third, update using a current version as the suggestion. 2568 // Return an error and make sure that SimpleUpdate is NOT called a second time, 2569 // since the live lookup shows the suggestion was already up to date. 2570 attempts := 0 2571 updatedPod3 := &example.Pod{} 2572 err = store.GuaranteedUpdate(ctx, key, updatedPod3, false, nil, 2573 storage.SimpleUpdate(func(obj runtime.Object) (runtime.Object, error) { 2574 pod := obj.(*example.Pod) 2575 if pod.Generation != updatedPod2.Generation || pod.ResourceVersion != updatedPod2.ResourceVersion { 2576 t.Logf("stale object (rv=%s), expected rv=%s", pod.ResourceVersion, updatedPod2.ResourceVersion) 2577 } 2578 attempts++ 2579 return nil, fmt.Errorf("validation or admission error") 2580 }), 2581 updatedPod2, 2582 ) 2583 if err == nil { 2584 t.Fatalf("expected error, got none") 2585 } 2586 // Implementations of the storage interface are allowed to ignore the suggestion, 2587 // in which case two attempts are possible. 2588 if attempts > 2 { 2589 t.Errorf("update function should have been called at most twice, called %d", attempts) 2590 } 2591 } 2592 2593 func RunTestTransformationFailure(ctx context.Context, t *testing.T, store InterfaceWithPrefixTransformer) { 2594 barFirst := &example.Pod{ 2595 ObjectMeta: metav1.ObjectMeta{Namespace: "first", Name: "bar"}, 2596 Spec: DeepEqualSafePodSpec(), 2597 } 2598 bazSecond := &example.Pod{ 2599 ObjectMeta: metav1.ObjectMeta{Namespace: "second", Name: "baz"}, 2600 Spec: DeepEqualSafePodSpec(), 2601 } 2602 2603 preset := []struct { 2604 key string 2605 obj *example.Pod 2606 storedObj *example.Pod 2607 }{{ 2608 key: computePodKey(barFirst), 2609 obj: barFirst, 2610 }, { 2611 key: computePodKey(bazSecond), 2612 obj: bazSecond, 2613 }} 2614 for i, ps := range preset[:1] { 2615 preset[i].storedObj = &example.Pod{} 2616 err := store.Create(ctx, ps.key, ps.obj, preset[:1][i].storedObj, 0) 2617 if err != nil { 2618 t.Fatalf("Set failed: %v", err) 2619 } 2620 } 2621 2622 // create a second resource with an invalid prefix 2623 revertTransformer := store.UpdatePrefixTransformer( 2624 func(transformer *PrefixTransformer) value.Transformer { 2625 return NewPrefixTransformer([]byte("otherprefix!"), false) 2626 }) 2627 for i, ps := range preset[1:] { 2628 preset[1:][i].storedObj = &example.Pod{} 2629 err := store.Create(ctx, ps.key, ps.obj, preset[1:][i].storedObj, 0) 2630 if err != nil { 2631 t.Fatalf("Set failed: %v", err) 2632 } 2633 } 2634 revertTransformer() 2635 2636 // List should fail 2637 var got example.PodList 2638 storageOpts := storage.ListOptions{ 2639 Predicate: storage.Everything, 2640 Recursive: true, 2641 } 2642 if err := store.GetList(ctx, "/pods", storageOpts, &got); !storage.IsInternalError(err) { 2643 t.Errorf("Unexpected error %v", err) 2644 } 2645 2646 // Get should fail 2647 if err := store.Get(ctx, preset[1].key, storage.GetOptions{}, &example.Pod{}); !storage.IsInternalError(err) { 2648 t.Errorf("Unexpected error: %v", err) 2649 } 2650 2651 updateFunc := func(input runtime.Object, res storage.ResponseMeta) (runtime.Object, *uint64, error) { 2652 return input, nil, nil 2653 } 2654 // GuaranteedUpdate without suggestion should return an error 2655 if err := store.GuaranteedUpdate(ctx, preset[1].key, &example.Pod{}, false, nil, updateFunc, nil); !storage.IsInternalError(err) { 2656 t.Errorf("Unexpected error: %v", err) 2657 } 2658 // GuaranteedUpdate with suggestion should return an error if we don't change the object 2659 if err := store.GuaranteedUpdate(ctx, preset[1].key, &example.Pod{}, false, nil, updateFunc, preset[1].obj); err == nil { 2660 t.Errorf("Unexpected error: %v", err) 2661 } 2662 2663 // Delete fails with internal error. 2664 if err := store.Delete(ctx, preset[1].key, &example.Pod{}, nil, storage.ValidateAllObjectFunc, nil); !storage.IsInternalError(err) { 2665 t.Errorf("Unexpected error: %v", err) 2666 } 2667 if err := store.Get(ctx, preset[1].key, storage.GetOptions{}, &example.Pod{}); !storage.IsInternalError(err) { 2668 t.Errorf("Unexpected error: %v", err) 2669 } 2670 } 2671 2672 func RunTestCount(ctx context.Context, t *testing.T, store storage.Interface) { 2673 resourceA := "/foo.bar.io/abc" 2674 2675 // resourceA is intentionally a prefix of resourceB to ensure that the count 2676 // for resourceA does not include any objects from resourceB. 2677 resourceB := fmt.Sprintf("%sdef", resourceA) 2678 2679 resourceACountExpected := 5 2680 for i := 1; i <= resourceACountExpected; i++ { 2681 obj := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("foo-%d", i)}} 2682 2683 key := fmt.Sprintf("%s/%d", resourceA, i) 2684 if err := store.Create(ctx, key, obj, nil, 0); err != nil { 2685 t.Fatalf("Create failed: %v", err) 2686 } 2687 } 2688 2689 resourceBCount := 4 2690 for i := 1; i <= resourceBCount; i++ { 2691 obj := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("foo-%d", i)}} 2692 2693 key := fmt.Sprintf("%s/%d", resourceB, i) 2694 if err := store.Create(ctx, key, obj, nil, 0); err != nil { 2695 t.Fatalf("Create failed: %v", err) 2696 } 2697 } 2698 2699 resourceACountGot, err := store.Count(resourceA) 2700 if err != nil { 2701 t.Fatalf("store.Count failed: %v", err) 2702 } 2703 2704 // count for resourceA should not include the objects for resourceB 2705 // even though resourceA is a prefix of resourceB. 2706 if int64(resourceACountExpected) != resourceACountGot { 2707 t.Fatalf("store.Count for resource %s: expected %d but got %d", resourceA, resourceACountExpected, resourceACountGot) 2708 } 2709 }