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