k8s.io/apiserver@v0.29.3/pkg/storage/testing/watcher_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 "fmt" 22 "net/http" 23 "sync" 24 "testing" 25 "time" 26 27 "github.com/stretchr/testify/require" 28 29 apiequality "k8s.io/apimachinery/pkg/api/equality" 30 "k8s.io/apimachinery/pkg/api/errors" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 "k8s.io/apimachinery/pkg/fields" 33 "k8s.io/apimachinery/pkg/labels" 34 "k8s.io/apimachinery/pkg/runtime" 35 "k8s.io/apimachinery/pkg/util/wait" 36 "k8s.io/apimachinery/pkg/watch" 37 "k8s.io/apiserver/pkg/apis/example" 38 genericapirequest "k8s.io/apiserver/pkg/endpoints/request" 39 "k8s.io/apiserver/pkg/features" 40 "k8s.io/apiserver/pkg/storage" 41 "k8s.io/apiserver/pkg/storage/value" 42 utilfeature "k8s.io/apiserver/pkg/util/feature" 43 utilflowcontrol "k8s.io/apiserver/pkg/util/flowcontrol" 44 featuregatetesting "k8s.io/component-base/featuregate/testing" 45 "k8s.io/utils/pointer" 46 ) 47 48 func RunTestWatch(ctx context.Context, t *testing.T, store storage.Interface) { 49 testWatch(ctx, t, store, false) 50 testWatch(ctx, t, store, true) 51 } 52 53 // It tests that 54 // - first occurrence of objects should notify Add event 55 // - update should trigger Modified event 56 // - update that gets filtered should trigger Deleted event 57 func testWatch(ctx context.Context, t *testing.T, store storage.Interface, recursive bool) { 58 basePod := &example.Pod{ 59 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 60 Spec: example.PodSpec{NodeName: ""}, 61 } 62 basePodAssigned := &example.Pod{ 63 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 64 Spec: example.PodSpec{NodeName: "bar"}, 65 } 66 67 selectedPod := func(pod *example.Pod) *example.Pod { 68 result := pod.DeepCopy() 69 result.Labels = map[string]string{"select": "true"} 70 return result 71 } 72 73 tests := []struct { 74 name string 75 namespace string 76 key string 77 pred storage.SelectionPredicate 78 watchTests []*testWatchStruct 79 }{{ 80 name: "create a key", 81 namespace: fmt.Sprintf("test-ns-1-%t", recursive), 82 watchTests: []*testWatchStruct{{basePod, true, watch.Added}}, 83 pred: storage.Everything, 84 }, { 85 name: "key updated to match predicate", 86 namespace: fmt.Sprintf("test-ns-2-%t", recursive), 87 watchTests: []*testWatchStruct{{basePod, false, ""}, {basePodAssigned, true, watch.Added}}, 88 pred: storage.SelectionPredicate{ 89 Label: labels.Everything(), 90 Field: fields.ParseSelectorOrDie("spec.nodeName=bar"), 91 GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { 92 pod := obj.(*example.Pod) 93 return nil, fields.Set{"spec.nodeName": pod.Spec.NodeName}, nil 94 }, 95 }, 96 }, { 97 name: "update", 98 namespace: fmt.Sprintf("test-ns-3-%t", recursive), 99 watchTests: []*testWatchStruct{{basePod, true, watch.Added}, {basePodAssigned, true, watch.Modified}}, 100 pred: storage.Everything, 101 }, { 102 name: "delete because of being filtered", 103 namespace: fmt.Sprintf("test-ns-4-%t", recursive), 104 watchTests: []*testWatchStruct{{basePod, true, watch.Added}, {basePodAssigned, true, watch.Deleted}}, 105 pred: storage.SelectionPredicate{ 106 Label: labels.Everything(), 107 Field: fields.ParseSelectorOrDie("spec.nodeName!=bar"), 108 GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { 109 pod := obj.(*example.Pod) 110 return nil, fields.Set{"spec.nodeName": pod.Spec.NodeName}, nil 111 }, 112 }, 113 }, { 114 name: "filtering", 115 namespace: fmt.Sprintf("test-ns-5-%t", recursive), 116 watchTests: []*testWatchStruct{ 117 {selectedPod(basePod), true, watch.Added}, 118 {basePod, true, watch.Deleted}, 119 {selectedPod(basePod), true, watch.Added}, 120 {selectedPod(basePodAssigned), true, watch.Modified}, 121 {nil, true, watch.Deleted}, 122 }, 123 pred: storage.SelectionPredicate{ 124 Label: labels.SelectorFromSet(labels.Set{"select": "true"}), 125 Field: fields.Everything(), 126 GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { 127 pod := obj.(*example.Pod) 128 return labels.Set(pod.Labels), nil, nil 129 }, 130 }, 131 }} 132 for _, tt := range tests { 133 t.Run(tt.name, func(t *testing.T) { 134 watchKey := fmt.Sprintf("/pods/%s", tt.namespace) 135 key := watchKey + "/foo" 136 if !recursive { 137 watchKey = key 138 } 139 140 // Get the current RV from which we can start watching. 141 out := &example.PodList{} 142 if err := store.GetList(ctx, watchKey, storage.ListOptions{ResourceVersion: "", Predicate: tt.pred, Recursive: recursive}, out); err != nil { 143 t.Fatalf("List failed: %v", err) 144 } 145 146 w, err := store.Watch(ctx, watchKey, storage.ListOptions{ResourceVersion: out.ResourceVersion, Predicate: tt.pred, Recursive: recursive}) 147 if err != nil { 148 t.Fatalf("Watch failed: %v", err) 149 } 150 151 // Create a pod in a different namespace first to ensure 152 // that its corresponding event will not be propagated. 153 badKey := fmt.Sprintf("/pods/%s-bad/foo", tt.namespace) 154 badOut := &example.Pod{} 155 err = store.GuaranteedUpdate(ctx, badKey, badOut, true, nil, storage.SimpleUpdate( 156 func(runtime.Object) (runtime.Object, error) { 157 obj := basePod.DeepCopy() 158 obj.Namespace = fmt.Sprintf("%s-bad", tt.namespace) 159 return obj, nil 160 }), nil) 161 if err != nil { 162 t.Fatalf("GuaranteedUpdate of bad pod failed: %v", err) 163 } 164 165 var prevObj *example.Pod 166 for _, watchTest := range tt.watchTests { 167 out := &example.Pod{} 168 if watchTest.obj != nil { 169 err := store.GuaranteedUpdate(ctx, key, out, true, nil, storage.SimpleUpdate( 170 func(runtime.Object) (runtime.Object, error) { 171 obj := watchTest.obj.DeepCopy() 172 obj.Namespace = tt.namespace 173 return obj, nil 174 }), nil) 175 if err != nil { 176 t.Fatalf("GuaranteedUpdate failed: %v", err) 177 } 178 } else { 179 err := store.Delete(ctx, key, out, nil, storage.ValidateAllObjectFunc, nil) 180 if err != nil { 181 t.Fatalf("Delete failed: %v", err) 182 } 183 } 184 if watchTest.expectEvent { 185 expectObj := out 186 if watchTest.watchType == watch.Deleted { 187 expectObj = prevObj 188 expectObj.ResourceVersion = out.ResourceVersion 189 } 190 testCheckResult(t, w, watch.Event{Type: watchTest.watchType, Object: expectObj}) 191 } 192 prevObj = out 193 } 194 w.Stop() 195 testCheckStop(t, w) 196 }) 197 } 198 } 199 200 // RunTestWatchFromZero tests that 201 // - watch from 0 should sync up and grab the object added before 202 // - For testing with etcd, watch from 0 is able to return events for objects 203 // whose previous version has been compacted. If testing with cacher, we 204 // expect compaction to be nil. 205 func RunTestWatchFromZero(ctx context.Context, t *testing.T, store storage.Interface, compaction Compaction) { 206 key, storedObj := testPropagateStore(ctx, t, store, &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test-ns"}}) 207 208 w, err := store.Watch(ctx, key, storage.ListOptions{ResourceVersion: "0", Predicate: storage.Everything}) 209 if err != nil { 210 t.Fatalf("Watch failed: %v", err) 211 } 212 testCheckResult(t, w, watch.Event{Type: watch.Added, Object: storedObj}) 213 214 // Update 215 out := &example.Pod{} 216 err = store.GuaranteedUpdate(ctx, key, out, true, nil, storage.SimpleUpdate( 217 func(runtime.Object) (runtime.Object, error) { 218 return &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test-ns", Annotations: map[string]string{"a": "1"}}}, nil 219 }), nil) 220 if err != nil { 221 t.Fatalf("GuaranteedUpdate failed: %v", err) 222 } 223 224 // Check that we receive a modified watch event. This check also 225 // indirectly ensures that the cache is synced. This is important 226 // when testing with the Cacher since we may have to allow for slow 227 // processing by allowing updates to propagate to the watch cache. 228 // This allows for that. 229 testCheckResult(t, w, watch.Event{Type: watch.Modified, Object: out}) 230 w.Stop() 231 232 // Make sure when we watch from 0 we receive an ADDED event 233 w, err = store.Watch(ctx, key, storage.ListOptions{ResourceVersion: "0", Predicate: storage.Everything}) 234 if err != nil { 235 t.Fatalf("Watch failed: %v", err) 236 } 237 238 testCheckResult(t, w, watch.Event{Type: watch.Added, Object: out}) 239 w.Stop() 240 241 // Compact previous versions 242 if compaction == nil { 243 t.Skip("compaction callback not provided") 244 } 245 246 // Update again 247 newOut := &example.Pod{} 248 err = store.GuaranteedUpdate(ctx, key, newOut, true, nil, storage.SimpleUpdate( 249 func(runtime.Object) (runtime.Object, error) { 250 return &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test-ns"}}, nil 251 }), nil) 252 if err != nil { 253 t.Fatalf("GuaranteedUpdate failed: %v", err) 254 } 255 256 // Compact previous versions 257 compaction(ctx, t, newOut.ResourceVersion) 258 259 // Make sure we can still watch from 0 and receive an ADDED event 260 w, err = store.Watch(ctx, key, storage.ListOptions{ResourceVersion: "0", Predicate: storage.Everything}) 261 defer w.Stop() 262 if err != nil { 263 t.Fatalf("Watch failed: %v", err) 264 } 265 testCheckResult(t, w, watch.Event{Type: watch.Added, Object: newOut}) 266 267 // Make sure we can't watch from older resource versions anymoer and get a "Gone" error. 268 tooOldWatcher, err := store.Watch(ctx, key, storage.ListOptions{ResourceVersion: out.ResourceVersion, Predicate: storage.Everything}) 269 if err != nil { 270 t.Fatalf("Watch failed: %v", err) 271 } 272 defer tooOldWatcher.Stop() 273 expiredError := errors.NewResourceExpired("").ErrStatus 274 // TODO(wojtek-t): It seems that etcd is currently returning a different error, 275 // being an Internal error of "etcd event received with PrevKv=nil". 276 // We temporary allow both but we should unify here. 277 internalError := metav1.Status{ 278 Status: metav1.StatusFailure, 279 Code: http.StatusInternalServerError, 280 Reason: metav1.StatusReasonInternalError, 281 } 282 testCheckResultFunc(t, tooOldWatcher, func(actualEvent watch.Event) { 283 expectNoDiff(t, "incorrect event type", watch.Error, actualEvent.Type) 284 if !apiequality.Semantic.DeepDerivative(&expiredError, actualEvent.Object) && !apiequality.Semantic.DeepDerivative(&internalError, actualEvent.Object) { 285 t.Errorf("expected: %#v; got %#v", &expiredError, actualEvent.Object) 286 } 287 }) 288 } 289 290 func RunTestDeleteTriggerWatch(ctx context.Context, t *testing.T, store storage.Interface) { 291 key, storedObj := testPropagateStore(ctx, t, store, &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test-ns"}}) 292 w, err := store.Watch(ctx, key, storage.ListOptions{ResourceVersion: storedObj.ResourceVersion, Predicate: storage.Everything}) 293 if err != nil { 294 t.Fatalf("Watch failed: %v", err) 295 } 296 if err := store.Delete(ctx, key, &example.Pod{}, nil, storage.ValidateAllObjectFunc, nil); err != nil { 297 t.Fatalf("Delete failed: %v", err) 298 } 299 testCheckEventType(t, w, watch.Deleted) 300 } 301 302 func RunTestWatchFromNonZero(ctx context.Context, t *testing.T, store storage.Interface) { 303 key, storedObj := testPropagateStore(ctx, t, store, &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test-ns"}}) 304 305 w, err := store.Watch(ctx, key, storage.ListOptions{ResourceVersion: storedObj.ResourceVersion, Predicate: storage.Everything}) 306 if err != nil { 307 t.Fatalf("Watch failed: %v", err) 308 } 309 out := &example.Pod{} 310 store.GuaranteedUpdate(ctx, key, out, true, nil, storage.SimpleUpdate( 311 func(runtime.Object) (runtime.Object, error) { 312 newObj := storedObj.DeepCopy() 313 newObj.Annotations = map[string]string{"version": "2"} 314 return newObj, nil 315 }), nil) 316 testCheckResult(t, w, watch.Event{Type: watch.Modified, Object: out}) 317 } 318 319 func RunTestDelayedWatchDelivery(ctx context.Context, t *testing.T, store storage.Interface) { 320 _, storedObj := testPropagateStore(ctx, t, store, &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test-ns"}}) 321 startRV := storedObj.ResourceVersion 322 323 watcher, err := store.Watch(ctx, "/pods/test-ns", storage.ListOptions{ResourceVersion: startRV, Predicate: storage.Everything, Recursive: true}) 324 if err != nil { 325 t.Fatalf("Unexpected error: %v", err) 326 } 327 328 // Depending on the implementation, different number of events that 329 // should be delivered to the watcher can be created before it will 330 // block the implementation and as a result force the watcher to be 331 // closed (as otherwise events would have to be dropped). 332 // For now, this number is smallest for Cacher and it equals 21 for it. 333 totalPods := 21 334 for i := 0; i < totalPods; i++ { 335 out := &example.Pod{} 336 pod := &example.Pod{ 337 ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("foo-%d", i), Namespace: "test-ns"}, 338 } 339 err := store.GuaranteedUpdate(ctx, computePodKey(pod), out, true, nil, storage.SimpleUpdate( 340 func(runtime.Object) (runtime.Object, error) { 341 return pod, nil 342 }), nil) 343 if err != nil { 344 t.Errorf("GuaranteedUpdate failed: %v", err) 345 } 346 } 347 348 // Now stop the watcher and check if the consecutive events are being delivered. 349 watcher.Stop() 350 351 watched := 0 352 for { 353 event, ok := <-watcher.ResultChan() 354 if !ok { 355 break 356 } 357 object := event.Object 358 if co, ok := object.(runtime.CacheableObject); ok { 359 object = co.GetObject() 360 } 361 if a, e := object.(*example.Pod).Name, fmt.Sprintf("foo-%d", watched); e != a { 362 t.Errorf("Unexpected object watched: %s, expected %s", a, e) 363 } 364 watched++ 365 } 366 // We expect at least N events to be delivered, depending on the implementation. 367 // For now, this number is smallest for Cacher and it equals 10 (size of the out buffer). 368 if watched < 10 { 369 t.Errorf("Unexpected number of events: %v, expected: %v", watched, totalPods) 370 } 371 } 372 373 func RunTestWatchError(ctx context.Context, t *testing.T, store InterfaceWithPrefixTransformer) { 374 obj := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test-ns"}} 375 key := computePodKey(obj) 376 377 // Compute the initial resource version from which we can start watching later. 378 list := &example.PodList{} 379 storageOpts := storage.ListOptions{ 380 ResourceVersion: "0", 381 Predicate: storage.Everything, 382 Recursive: true, 383 } 384 if err := store.GetList(ctx, "/pods", storageOpts, list); err != nil { 385 t.Errorf("Unexpected error: %v", err) 386 } 387 388 if err := store.GuaranteedUpdate(ctx, key, &example.Pod{}, true, nil, storage.SimpleUpdate( 389 func(runtime.Object) (runtime.Object, error) { 390 return obj, nil 391 }), nil); err != nil { 392 t.Fatalf("GuaranteedUpdate failed: %v", err) 393 } 394 395 // Now trigger watch error by injecting failing transformer. 396 revertTransformer := store.UpdatePrefixTransformer( 397 func(previousTransformer *PrefixTransformer) value.Transformer { 398 return &failingTransformer{} 399 }) 400 defer revertTransformer() 401 402 w, err := store.Watch(ctx, key, storage.ListOptions{ResourceVersion: list.ResourceVersion, Predicate: storage.Everything}) 403 if err != nil { 404 t.Fatalf("Watch failed: %v", err) 405 } 406 testCheckEventType(t, w, watch.Error) 407 } 408 409 func RunTestWatchContextCancel(ctx context.Context, t *testing.T, store storage.Interface) { 410 canceledCtx, cancel := context.WithCancel(ctx) 411 cancel() 412 // When we watch with a canceled context, we should detect that it's context canceled. 413 // We won't take it as error and also close the watcher. 414 w, err := store.Watch(canceledCtx, "/pods/not-existing", storage.ListOptions{ 415 ResourceVersion: "0", 416 Predicate: storage.Everything, 417 }) 418 if err != nil { 419 t.Fatal(err) 420 } 421 422 select { 423 case _, ok := <-w.ResultChan(): 424 if ok { 425 t.Error("ResultChan() should be closed") 426 } 427 case <-time.After(wait.ForeverTestTimeout): 428 t.Errorf("timeout after %v", wait.ForeverTestTimeout) 429 } 430 } 431 432 func RunTestWatcherTimeout(ctx context.Context, t *testing.T, store storage.Interface) { 433 // initialRV is used to initate the watcher at the beginning of the world. 434 podList := example.PodList{} 435 options := storage.ListOptions{ 436 Predicate: storage.Everything, 437 Recursive: true, 438 } 439 if err := store.GetList(ctx, "/pods", options, &podList); err != nil { 440 t.Fatalf("Failed to list pods: %v", err) 441 } 442 initialRV := podList.ResourceVersion 443 444 options = storage.ListOptions{ 445 ResourceVersion: initialRV, 446 Predicate: storage.Everything, 447 Recursive: true, 448 } 449 450 // Create a number of watchers that will not be reading any result. 451 nonReadingWatchers := 50 452 for i := 0; i < nonReadingWatchers; i++ { 453 watcher, err := store.Watch(ctx, "/pods/test-ns", options) 454 if err != nil { 455 t.Fatalf("Unexpected error: %v", err) 456 } 457 defer watcher.Stop() 458 } 459 460 // Create a second watcher that will be reading result. 461 readingWatcher, err := store.Watch(ctx, "/pods/test-ns", options) 462 if err != nil { 463 t.Fatalf("Unexpected error: %v", err) 464 } 465 defer readingWatcher.Stop() 466 467 // Depending on the implementation, different number of events that 468 // should be delivered to the watcher can be created before it will 469 // block the implementation and as a result force the watcher to be 470 // closed (as otherwise events would have to be dropped). 471 // For now, this number is smallest for Cacher and it equals 21 for it. 472 // 473 // Create more events to ensure that we're not blocking other watchers 474 // forever. 475 startTime := time.Now() 476 for i := 0; i < 22; i++ { 477 out := &example.Pod{} 478 pod := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("foo-%d", i), Namespace: "test-ns"}} 479 if err := store.Create(ctx, computePodKey(pod), pod, out, 0); err != nil { 480 t.Fatalf("Create failed: %v", err) 481 } 482 testCheckResult(t, readingWatcher, watch.Event{Type: watch.Added, Object: out}) 483 } 484 if time.Since(startTime) > time.Duration(250*nonReadingWatchers)*time.Millisecond { 485 t.Errorf("waiting for events took too long: %v", time.Since(startTime)) 486 } 487 } 488 489 func RunTestWatchDeleteEventObjectHaveLatestRV(ctx context.Context, t *testing.T, store storage.Interface) { 490 key, storedObj := testPropagateStore(ctx, t, store, &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test-ns"}}) 491 492 watchCtx, cancel := context.WithTimeout(ctx, wait.ForeverTestTimeout) 493 t.Cleanup(cancel) 494 w, err := store.Watch(watchCtx, key, storage.ListOptions{ResourceVersion: storedObj.ResourceVersion, Predicate: storage.Everything}) 495 if err != nil { 496 t.Fatalf("Watch failed: %v", err) 497 } 498 499 deletedObj := &example.Pod{} 500 if err := store.Delete(ctx, key, deletedObj, &storage.Preconditions{}, storage.ValidateAllObjectFunc, nil); err != nil { 501 t.Fatalf("Delete failed: %v", err) 502 } 503 504 // Verify that ResourceVersion has changed on deletion. 505 if storedObj.ResourceVersion == deletedObj.ResourceVersion { 506 t.Fatalf("ResourceVersion didn't changed on deletion: %s", deletedObj.ResourceVersion) 507 } 508 509 testCheckResult(t, w, watch.Event{Type: watch.Deleted, Object: deletedObj}) 510 } 511 512 func RunTestWatchInitializationSignal(ctx context.Context, t *testing.T, store storage.Interface) { 513 ctx, cancel := context.WithTimeout(ctx, 5*time.Second) 514 t.Cleanup(cancel) 515 initSignal := utilflowcontrol.NewInitializationSignal() 516 ctx = utilflowcontrol.WithInitializationSignal(ctx, initSignal) 517 518 key, storedObj := testPropagateStore(ctx, t, store, &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test-ns"}}) 519 _, err := store.Watch(ctx, key, storage.ListOptions{ResourceVersion: storedObj.ResourceVersion, Predicate: storage.Everything}) 520 if err != nil { 521 t.Fatalf("Watch failed: %v", err) 522 } 523 524 initSignal.Wait() 525 } 526 527 // RunOptionalTestProgressNotify tests ProgressNotify feature of ListOptions. 528 // Given this feature is currently not explicitly used by higher layers of Kubernetes 529 // (it rather is used by wrappers of storage.Interface to implement its functionalities) 530 // this test is currently considered optional. 531 func RunOptionalTestProgressNotify(ctx context.Context, t *testing.T, store storage.Interface) { 532 input := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "name", Namespace: "test-ns"}} 533 key := computePodKey(input) 534 out := &example.Pod{} 535 if err := store.Create(ctx, key, input, out, 0); err != nil { 536 t.Fatalf("Create failed: %v", err) 537 } 538 validateResourceVersion := resourceVersionNotOlderThan(out.ResourceVersion) 539 540 opts := storage.ListOptions{ 541 ResourceVersion: out.ResourceVersion, 542 Predicate: storage.Everything, 543 ProgressNotify: true, 544 } 545 w, err := store.Watch(ctx, key, opts) 546 if err != nil { 547 t.Fatalf("Watch failed: %v", err) 548 } 549 550 // when we send a bookmark event, the client expects the event to contain an 551 // object of the correct type, but with no fields set other than the resourceVersion 552 testCheckResultFunc(t, w, func(actualEvent watch.Event) { 553 expectNoDiff(t, "incorrect event type", watch.Bookmark, actualEvent.Type) 554 // first, check that we have the correct resource version 555 obj, ok := actualEvent.Object.(metav1.Object) 556 if !ok { 557 t.Fatalf("got %T, not metav1.Object", actualEvent.Object) 558 } 559 if err := validateResourceVersion(obj.GetResourceVersion()); err != nil { 560 t.Fatal(err) 561 } 562 563 // then, check that we have the right type and content 564 pod, ok := actualEvent.Object.(*example.Pod) 565 if !ok { 566 t.Fatalf("got %T, not *example.Pod", actualEvent.Object) 567 } 568 pod.ResourceVersion = "" 569 expectNoDiff(t, "bookmark event should contain an object with no fields set other than resourceVersion", &example.Pod{}, pod) 570 }) 571 } 572 573 // It tests watches of cluster-scoped resources. 574 func RunTestClusterScopedWatch(ctx context.Context, t *testing.T, store storage.Interface) { 575 tests := []struct { 576 name string 577 // For watch request, the name of object is specified with field selector 578 // "metadata.name=objectName". So in this watch tests, we should set the 579 // requestedName and field selector "metadata.name=requestedName" at the 580 // same time or set neighter of them. 581 requestedName string 582 recursive bool 583 fieldSelector fields.Selector 584 indexFields []string 585 watchTests []*testWatchStruct 586 }{ 587 { 588 name: "cluster-wide watch, request without name, without field selector", 589 recursive: true, 590 fieldSelector: fields.Everything(), 591 watchTests: []*testWatchStruct{ 592 {basePod("t1-foo1"), true, watch.Added}, 593 {basePodUpdated("t1-foo1"), true, watch.Modified}, 594 {basePodAssigned("t1-foo2", "t1-bar1"), true, watch.Added}, 595 }, 596 }, 597 { 598 name: "cluster-wide watch, request without name, field selector with spec.nodeName", 599 recursive: true, 600 fieldSelector: fields.ParseSelectorOrDie("spec.nodeName=t2-bar1"), 601 indexFields: []string{"spec.nodeName"}, 602 watchTests: []*testWatchStruct{ 603 {basePod("t2-foo1"), false, ""}, 604 {basePodAssigned("t2-foo1", "t2-bar1"), true, watch.Added}, 605 }, 606 }, 607 { 608 name: "cluster-wide watch, request without name, field selector with spec.nodeName to filter out watch", 609 recursive: true, 610 fieldSelector: fields.ParseSelectorOrDie("spec.nodeName!=t3-bar1"), 611 indexFields: []string{"spec.nodeName"}, 612 watchTests: []*testWatchStruct{ 613 {basePod("t3-foo1"), true, watch.Added}, 614 {basePod("t3-foo2"), true, watch.Added}, 615 {basePodUpdated("t3-foo1"), true, watch.Modified}, 616 {basePodAssigned("t3-foo1", "t3-bar1"), true, watch.Deleted}, 617 }, 618 }, 619 { 620 name: "cluster-wide watch, request with name, field selector with metadata.name", 621 requestedName: "t4-foo1", 622 fieldSelector: fields.ParseSelectorOrDie("metadata.name=t4-foo1"), 623 watchTests: []*testWatchStruct{ 624 {basePod("t4-foo1"), true, watch.Added}, 625 {basePod("t4-foo2"), false, ""}, 626 {basePodUpdated("t4-foo1"), true, watch.Modified}, 627 {basePodUpdated("t4-foo2"), false, ""}, 628 }, 629 }, 630 { 631 name: "cluster-wide watch, request with name, field selector with metadata.name and spec.nodeName", 632 requestedName: "t5-foo1", 633 fieldSelector: fields.SelectorFromSet(fields.Set{ 634 "metadata.name": "t5-foo1", 635 "spec.nodeName": "t5-bar1", 636 }), 637 indexFields: []string{"spec.nodeName"}, 638 watchTests: []*testWatchStruct{ 639 {basePod("t5-foo1"), false, ""}, 640 {basePod("t5-foo2"), false, ""}, 641 {basePodUpdated("t5-foo1"), false, ""}, 642 {basePodUpdated("t5-foo2"), false, ""}, 643 {basePodAssigned("t5-foo1", "t5-bar1"), true, watch.Added}, 644 }, 645 }, 646 { 647 name: "cluster-wide watch, request with name, field selector with metadata.name, and with spec.nodeName to filter out watch", 648 requestedName: "t6-foo1", 649 fieldSelector: fields.AndSelectors( 650 fields.ParseSelectorOrDie("spec.nodeName!=t6-bar1"), 651 fields.SelectorFromSet(fields.Set{"metadata.name": "t6-foo1"}), 652 ), 653 indexFields: []string{"spec.nodeName"}, 654 watchTests: []*testWatchStruct{ 655 {basePod("t6-foo1"), true, watch.Added}, 656 {basePod("t6-foo2"), false, ""}, 657 {basePodUpdated("t6-foo1"), true, watch.Modified}, 658 {basePodAssigned("t6-foo1", "t6-bar1"), true, watch.Deleted}, 659 {basePodAssigned("t6-foo2", "t6-bar1"), false, ""}, 660 }, 661 }, 662 } 663 for _, tt := range tests { 664 t.Run(tt.name, func(t *testing.T) { 665 requestInfo := &genericapirequest.RequestInfo{} 666 requestInfo.Name = tt.requestedName 667 requestInfo.Namespace = "" 668 ctx = genericapirequest.WithRequestInfo(ctx, requestInfo) 669 ctx = genericapirequest.WithNamespace(ctx, "") 670 671 watchKey := "/pods" 672 if tt.requestedName != "" { 673 watchKey += "/" + tt.requestedName 674 } 675 676 predicate := createPodPredicate(tt.fieldSelector, false, tt.indexFields) 677 678 list := &example.PodList{} 679 opts := storage.ListOptions{ 680 ResourceVersion: "", 681 Predicate: predicate, 682 Recursive: true, 683 } 684 if err := store.GetList(ctx, "/pods", opts, list); err != nil { 685 t.Errorf("Unexpected error: %v", err) 686 } 687 688 opts.ResourceVersion = list.ResourceVersion 689 opts.Recursive = tt.recursive 690 691 w, err := store.Watch(ctx, watchKey, opts) 692 if err != nil { 693 t.Fatalf("Watch failed: %v", err) 694 } 695 696 currentObjs := map[string]*example.Pod{} 697 for _, watchTest := range tt.watchTests { 698 out := &example.Pod{} 699 key := "pods/" + watchTest.obj.Name 700 err := store.GuaranteedUpdate(ctx, key, out, true, nil, storage.SimpleUpdate( 701 func(runtime.Object) (runtime.Object, error) { 702 obj := watchTest.obj.DeepCopy() 703 return obj, nil 704 }), nil) 705 if err != nil { 706 t.Fatalf("GuaranteedUpdate failed: %v", err) 707 } 708 709 expectObj := out 710 if watchTest.watchType == watch.Deleted { 711 expectObj = currentObjs[watchTest.obj.Name] 712 expectObj.ResourceVersion = out.ResourceVersion 713 delete(currentObjs, watchTest.obj.Name) 714 } else { 715 currentObjs[watchTest.obj.Name] = out 716 } 717 if watchTest.expectEvent { 718 testCheckResult(t, w, watch.Event{Type: watchTest.watchType, Object: expectObj}) 719 } 720 } 721 w.Stop() 722 testCheckStop(t, w) 723 }) 724 } 725 } 726 727 // It tests watch of namespace-scoped resources. 728 func RunTestNamespaceScopedWatch(ctx context.Context, t *testing.T, store storage.Interface) { 729 tests := []struct { 730 name string 731 // For watch request, the name of object is specified with field selector 732 // "metadata.name=objectName". So in this watch tests, we should set the 733 // requestedName and field selector "metadata.name=requestedName" at the 734 // same time or set neighter of them. 735 requestedName string 736 requestedNamespace string 737 recursive bool 738 fieldSelector fields.Selector 739 indexFields []string 740 watchTests []*testWatchStruct 741 }{ 742 { 743 name: "namespaced watch, request without name, request without namespace, without field selector", 744 recursive: true, 745 fieldSelector: fields.Everything(), 746 watchTests: []*testWatchStruct{ 747 {baseNamespacedPod("t1-foo1", "t1-ns1"), true, watch.Added}, 748 {baseNamespacedPod("t1-foo2", "t1-ns2"), true, watch.Added}, 749 {baseNamespacedPodUpdated("t1-foo1", "t1-ns1"), true, watch.Modified}, 750 {baseNamespacedPodUpdated("t1-foo2", "t1-ns2"), true, watch.Modified}, 751 }, 752 }, 753 { 754 name: "namespaced watch, request without name, request without namespace, field selector with metadata.namespace", 755 recursive: true, 756 fieldSelector: fields.ParseSelectorOrDie("metadata.namespace=t2-ns1"), 757 watchTests: []*testWatchStruct{ 758 {baseNamespacedPod("t2-foo1", "t2-ns1"), true, watch.Added}, 759 {baseNamespacedPod("t2-foo1", "t2-ns2"), false, ""}, 760 {baseNamespacedPodUpdated("t2-foo1", "t2-ns1"), true, watch.Modified}, 761 {baseNamespacedPodUpdated("t2-foo1", "t2-ns2"), false, ""}, 762 }, 763 }, 764 { 765 name: "namespaced watch, request without name, request without namespace, field selector with spec.nodename", 766 recursive: true, 767 fieldSelector: fields.ParseSelectorOrDie("spec.nodeName=t3-bar1"), 768 indexFields: []string{"spec.nodeName"}, 769 watchTests: []*testWatchStruct{ 770 {baseNamespacedPod("t3-foo1", "t3-ns1"), false, ""}, 771 {baseNamespacedPod("t3-foo2", "t3-ns2"), false, ""}, 772 {baseNamespacedPodAssigned("t3-foo1", "t3-ns1", "t3-bar1"), true, watch.Added}, 773 {baseNamespacedPodAssigned("t3-foo2", "t3-ns2", "t3-bar1"), true, watch.Added}, 774 }, 775 }, 776 { 777 name: "namespaced watch, request without name, request without namespace, field selector with spec.nodename to filter out watch", 778 recursive: true, 779 fieldSelector: fields.ParseSelectorOrDie("spec.nodeName!=t4-bar1"), 780 indexFields: []string{"spec.nodeName"}, 781 watchTests: []*testWatchStruct{ 782 {baseNamespacedPod("t4-foo1", "t4-ns1"), true, watch.Added}, 783 {baseNamespacedPod("t4-foo2", "t4-ns1"), true, watch.Added}, 784 {baseNamespacedPodUpdated("t4-foo1", "t4-ns1"), true, watch.Modified}, 785 {baseNamespacedPodAssigned("t4-foo1", "t4-ns1", "t4-bar1"), true, watch.Deleted}, 786 }, 787 }, 788 { 789 name: "namespaced watch, request without name, request with namespace, without field selector", 790 requestedNamespace: "t5-ns1", 791 recursive: true, 792 fieldSelector: fields.Everything(), 793 watchTests: []*testWatchStruct{ 794 {baseNamespacedPod("t5-foo1", "t5-ns1"), true, watch.Added}, 795 {baseNamespacedPod("t5-foo1", "t5-ns2"), false, ""}, 796 {baseNamespacedPod("t5-foo2", "t5-ns1"), true, watch.Added}, 797 {baseNamespacedPodUpdated("t5-foo1", "t5-ns1"), true, watch.Modified}, 798 {baseNamespacedPodUpdated("t5-foo1", "t5-ns2"), false, ""}, 799 }, 800 }, 801 { 802 name: "namespaced watch, request without name, request with namespace, field selector with matched metadata.namespace", 803 requestedNamespace: "t6-ns1", 804 recursive: true, 805 fieldSelector: fields.ParseSelectorOrDie("metadata.namespace=t6-ns1"), 806 watchTests: []*testWatchStruct{ 807 {baseNamespacedPod("t6-foo1", "t6-ns1"), true, watch.Added}, 808 {baseNamespacedPod("t6-foo1", "t6-ns2"), false, ""}, 809 {baseNamespacedPodUpdated("t6-foo1", "t6-ns1"), true, watch.Modified}, 810 }, 811 }, 812 { 813 name: "namespaced watch, request without name, request with namespace, field selector with non-matched metadata.namespace", 814 requestedNamespace: "t7-ns1", 815 recursive: true, 816 fieldSelector: fields.ParseSelectorOrDie("metadata.namespace=t7-ns2"), 817 watchTests: []*testWatchStruct{ 818 {baseNamespacedPod("t7-foo1", "t7-ns1"), false, ""}, 819 {baseNamespacedPod("t7-foo1", "t7-ns2"), false, ""}, 820 {baseNamespacedPodUpdated("t7-foo1", "t7-ns1"), false, ""}, 821 {baseNamespacedPodUpdated("t7-foo1", "t7-ns2"), false, ""}, 822 }, 823 }, 824 { 825 name: "namespaced watch, request without name, request with namespace, field selector with spec.nodename", 826 requestedNamespace: "t8-ns1", 827 recursive: true, 828 fieldSelector: fields.ParseSelectorOrDie("spec.nodeName=t8-bar2"), 829 indexFields: []string{"spec.nodeName"}, 830 watchTests: []*testWatchStruct{ 831 {baseNamespacedPod("t8-foo1", "t8-ns1"), false, ""}, 832 {baseNamespacedPodAssigned("t8-foo1", "t8-ns1", "t8-bar1"), false, ""}, 833 {baseNamespacedPodAssigned("t8-foo1", "t8-ns2", "t8-bar2"), false, ""}, 834 {baseNamespacedPodAssigned("t8-foo1", "t8-ns1", "t8-bar2"), true, watch.Added}, 835 }, 836 }, 837 { 838 name: "namespaced watch, request without name, request with namespace, field selector with spec.nodename to filter out watch", 839 requestedNamespace: "t9-ns2", 840 recursive: true, 841 fieldSelector: fields.ParseSelectorOrDie("spec.nodeName!=t9-bar1"), 842 indexFields: []string{"spec.nodeName"}, 843 watchTests: []*testWatchStruct{ 844 {baseNamespacedPod("t9-foo1", "t9-ns1"), false, ""}, 845 {baseNamespacedPod("t9-foo1", "t9-ns2"), true, watch.Added}, 846 {baseNamespacedPodAssigned("t9-foo1", "t9-ns2", "t9-bar1"), true, watch.Deleted}, 847 {baseNamespacedPodAssigned("t9-foo1", "t9-ns2", "t9-bar2"), true, watch.Added}, 848 }, 849 }, 850 { 851 name: "namespaced watch, request with name, request without namespace, field selector with metadata.name", 852 requestedName: "t10-foo1", 853 recursive: true, 854 fieldSelector: fields.ParseSelectorOrDie("metadata.name=t10-foo1"), 855 watchTests: []*testWatchStruct{ 856 {baseNamespacedPod("t10-foo1", "t10-ns1"), true, watch.Added}, 857 {baseNamespacedPod("t10-foo1", "t10-ns2"), true, watch.Added}, 858 {baseNamespacedPod("t10-foo2", "t10-ns1"), false, ""}, 859 {baseNamespacedPodUpdated("t10-foo1", "t10-ns1"), true, watch.Modified}, 860 {baseNamespacedPodAssigned("t10-foo1", "t10-ns1", "t10-bar1"), true, watch.Modified}, 861 }, 862 }, 863 { 864 name: "namespaced watch, request with name, request without namespace, field selector with metadata.name and metadata.namespace", 865 requestedName: "t11-foo1", 866 recursive: true, 867 fieldSelector: fields.SelectorFromSet(fields.Set{ 868 "metadata.name": "t11-foo1", 869 "metadata.namespace": "t11-ns1", 870 }), 871 watchTests: []*testWatchStruct{ 872 {baseNamespacedPod("t11-foo1", "t11-ns1"), true, watch.Added}, 873 {baseNamespacedPod("t11-foo2", "t11-ns1"), false, ""}, 874 {baseNamespacedPod("t11-foo1", "t11-ns2"), false, ""}, 875 {baseNamespacedPodUpdated("t11-foo1", "t11-ns1"), true, watch.Modified}, 876 {baseNamespacedPodAssigned("t11-foo1", "t11-ns1", "t11-bar1"), true, watch.Modified}, 877 }, 878 }, 879 { 880 name: "namespaced watch, request with name, request without namespace, field selector with metadata.name and spec.nodeName", 881 requestedName: "t12-foo1", 882 recursive: true, 883 fieldSelector: fields.SelectorFromSet(fields.Set{ 884 "metadata.name": "t12-foo1", 885 "spec.nodeName": "t12-bar1", 886 }), 887 indexFields: []string{"spec.nodeName"}, 888 watchTests: []*testWatchStruct{ 889 {baseNamespacedPod("t12-foo1", "t12-ns1"), false, ""}, 890 {baseNamespacedPodUpdated("t12-foo1", "t12-ns1"), false, ""}, 891 {baseNamespacedPodAssigned("t12-foo1", "t12-ns1", "t12-bar1"), true, watch.Added}, 892 }, 893 }, 894 { 895 name: "namespaced watch, request with name, request without namespace, field selector with metadata.name, and with spec.nodeName to filter out watch", 896 requestedName: "t15-foo1", 897 recursive: true, 898 fieldSelector: fields.AndSelectors( 899 fields.ParseSelectorOrDie("spec.nodeName!=t15-bar1"), 900 fields.SelectorFromSet(fields.Set{"metadata.name": "t15-foo1"}), 901 ), 902 indexFields: []string{"spec.nodeName"}, 903 watchTests: []*testWatchStruct{ 904 {baseNamespacedPod("t15-foo1", "t15-ns1"), true, watch.Added}, 905 {baseNamespacedPod("t15-foo2", "t15-ns1"), false, ""}, 906 {baseNamespacedPodUpdated("t15-foo1", "t15-ns1"), true, watch.Modified}, 907 {baseNamespacedPodAssigned("t15-foo1", "t15-ns1", "t15-bar1"), true, watch.Deleted}, 908 {baseNamespacedPodAssigned("t15-foo1", "t15-ns1", "t15-bar2"), true, watch.Added}, 909 }, 910 }, 911 { 912 name: "namespaced watch, request with name, request with namespace, with field selector metadata.name", 913 requestedName: "t16-foo1", 914 requestedNamespace: "t16-ns1", 915 fieldSelector: fields.ParseSelectorOrDie("metadata.name=t16-foo1"), 916 watchTests: []*testWatchStruct{ 917 {baseNamespacedPod("t16-foo1", "t16-ns1"), true, watch.Added}, 918 {baseNamespacedPod("t16-foo2", "t16-ns1"), false, ""}, 919 {baseNamespacedPodUpdated("t16-foo1", "t16-ns1"), true, watch.Modified}, 920 {baseNamespacedPodAssigned("t16-foo1", "t16-ns1", "t16-bar1"), true, watch.Modified}, 921 }, 922 }, 923 { 924 name: "namespaced watch, request with name, request with namespace, with field selector metadata.name and metadata.namespace", 925 requestedName: "t17-foo2", 926 requestedNamespace: "t17-ns1", 927 fieldSelector: fields.SelectorFromSet(fields.Set{ 928 "metadata.name": "t17-foo2", 929 "metadata.namespace": "t17-ns1", 930 }), 931 watchTests: []*testWatchStruct{ 932 {baseNamespacedPod("t17-foo1", "t17-ns1"), false, ""}, 933 {baseNamespacedPod("t17-foo2", "t17-ns1"), true, watch.Added}, 934 {baseNamespacedPodUpdated("t17-foo1", "t17-ns1"), false, ""}, 935 {baseNamespacedPodAssigned("t17-foo2", "t17-ns1", "t17-bar1"), true, watch.Modified}, 936 }, 937 }, 938 { 939 name: "namespaced watch, request with name, request with namespace, with field selector metadata.name, metadata.namespace and spec.nodename", 940 requestedName: "t18-foo1", 941 requestedNamespace: "t18-ns1", 942 fieldSelector: fields.SelectorFromSet(fields.Set{ 943 "metadata.name": "t18-foo1", 944 "metadata.namespace": "t18-ns1", 945 "spec.nodeName": "t18-bar1", 946 }), 947 indexFields: []string{"spec.nodeName"}, 948 watchTests: []*testWatchStruct{ 949 {baseNamespacedPod("t18-foo1", "t18-ns1"), false, ""}, 950 {baseNamespacedPod("t18-foo2", "t18-ns1"), false, ""}, 951 {baseNamespacedPod("t18-foo1", "t18-ns2"), false, ""}, 952 {baseNamespacedPodUpdated("t18-foo1", "t18-ns1"), false, ""}, 953 {baseNamespacedPodAssigned("t18-foo1", "t18-ns1", "t18-bar1"), true, watch.Added}, 954 }, 955 }, 956 { 957 name: "namespaced watch, request with name, request with namespace, with field selector metadata.name, metadata.namespace, and with spec.nodename to filter out watch", 958 requestedName: "t19-foo2", 959 requestedNamespace: "t19-ns1", 960 fieldSelector: fields.AndSelectors( 961 fields.ParseSelectorOrDie("spec.nodeName!=t19-bar1"), 962 fields.SelectorFromSet(fields.Set{"metadata.name": "t19-foo2", "metadata.namespace": "t19-ns1"}), 963 ), 964 indexFields: []string{"spec.nodeName"}, 965 watchTests: []*testWatchStruct{ 966 {baseNamespacedPod("t19-foo1", "t19-ns1"), false, ""}, 967 {baseNamespacedPod("t19-foo2", "t19-ns2"), false, ""}, 968 {baseNamespacedPod("t19-foo2", "t19-ns1"), true, watch.Added}, 969 {baseNamespacedPodUpdated("t19-foo2", "t19-ns1"), true, watch.Modified}, 970 {baseNamespacedPodAssigned("t19-foo2", "t19-ns1", "t19-bar1"), true, watch.Deleted}, 971 }, 972 }, 973 } 974 for _, tt := range tests { 975 t.Run(tt.name, func(t *testing.T) { 976 requestInfo := &genericapirequest.RequestInfo{} 977 requestInfo.Name = tt.requestedName 978 requestInfo.Namespace = tt.requestedNamespace 979 ctx = genericapirequest.WithRequestInfo(ctx, requestInfo) 980 ctx = genericapirequest.WithNamespace(ctx, tt.requestedNamespace) 981 982 watchKey := "/pods" 983 if tt.requestedNamespace != "" { 984 watchKey += "/" + tt.requestedNamespace 985 if tt.requestedName != "" { 986 watchKey += "/" + tt.requestedName 987 } 988 } 989 990 predicate := createPodPredicate(tt.fieldSelector, true, tt.indexFields) 991 992 list := &example.PodList{} 993 opts := storage.ListOptions{ 994 ResourceVersion: "", 995 Predicate: predicate, 996 Recursive: true, 997 } 998 if err := store.GetList(ctx, "/pods", opts, list); err != nil { 999 t.Errorf("Unexpected error: %v", err) 1000 } 1001 1002 opts.ResourceVersion = list.ResourceVersion 1003 opts.Recursive = tt.recursive 1004 1005 w, err := store.Watch(ctx, watchKey, opts) 1006 if err != nil { 1007 t.Fatalf("Watch failed: %v", err) 1008 } 1009 1010 currentObjs := map[string]*example.Pod{} 1011 for _, watchTest := range tt.watchTests { 1012 out := &example.Pod{} 1013 key := "pods/" + watchTest.obj.Namespace + "/" + watchTest.obj.Name 1014 err := store.GuaranteedUpdate(ctx, key, out, true, nil, storage.SimpleUpdate( 1015 func(runtime.Object) (runtime.Object, error) { 1016 obj := watchTest.obj.DeepCopy() 1017 return obj, nil 1018 }), nil) 1019 if err != nil { 1020 t.Fatalf("GuaranteedUpdate failed: %v", err) 1021 } 1022 1023 expectObj := out 1024 podIdentifier := watchTest.obj.Namespace + "/" + watchTest.obj.Name 1025 if watchTest.watchType == watch.Deleted { 1026 expectObj = currentObjs[podIdentifier] 1027 expectObj.ResourceVersion = out.ResourceVersion 1028 delete(currentObjs, podIdentifier) 1029 } else { 1030 currentObjs[podIdentifier] = out 1031 } 1032 if watchTest.expectEvent { 1033 testCheckResult(t, w, watch.Event{Type: watchTest.watchType, Object: expectObj}) 1034 } 1035 } 1036 w.Stop() 1037 testCheckStop(t, w) 1038 }) 1039 } 1040 } 1041 1042 // RunOptionalTestWatchDispatchBookmarkEvents tests whether bookmark events are sent. 1043 // This feature is currently implemented in watch cache layer, so this is optional. 1044 // 1045 // TODO(#109831): ProgressNotify feature is effectively implementing the same 1046 // 1047 // functionality, so we should refactor this functionality to share the same input. 1048 func RunTestWatchDispatchBookmarkEvents(ctx context.Context, t *testing.T, store storage.Interface, expectedWatchBookmarks bool) { 1049 key, storedObj := testPropagateStore(ctx, t, store, &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test-ns"}}) 1050 startRV := storedObj.ResourceVersion 1051 1052 tests := []struct { 1053 name string 1054 timeout time.Duration 1055 expected bool 1056 allowWatchBookmarks bool 1057 }{ 1058 { // test old client won't get Bookmark event 1059 name: "allowWatchBookmarks=false", 1060 timeout: 3 * time.Second, 1061 expected: false, 1062 allowWatchBookmarks: false, 1063 }, 1064 { 1065 name: "allowWatchBookmarks=true", 1066 timeout: 3 * time.Second, 1067 expected: expectedWatchBookmarks, 1068 allowWatchBookmarks: true, 1069 }, 1070 } 1071 1072 for i, tt := range tests { 1073 t.Run(tt.name, func(t *testing.T) { 1074 pred := storage.Everything 1075 pred.AllowWatchBookmarks = tt.allowWatchBookmarks 1076 ctx, cancel := context.WithTimeout(context.Background(), tt.timeout) 1077 defer cancel() 1078 1079 watcher, err := store.Watch(ctx, key, storage.ListOptions{ResourceVersion: startRV, Predicate: pred}) 1080 if err != nil { 1081 t.Fatalf("Unexpected error: %v", err) 1082 } 1083 defer watcher.Stop() 1084 1085 // Create events of pods in a different namespace 1086 out := &example.Pod{} 1087 obj := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: fmt.Sprintf("other-ns-%d", i)}} 1088 objKey := computePodKey(obj) 1089 1090 if err := store.Create(ctx, objKey, obj, out, 0); err != nil { 1091 t.Fatalf("Create failed: %v", err) 1092 } 1093 1094 // Now wait for Bookmark event 1095 select { 1096 case event, ok := <-watcher.ResultChan(): 1097 if !ok && tt.expected { 1098 t.Errorf("Unexpected object watched (no objects)") 1099 } 1100 if tt.expected && event.Type != watch.Bookmark { 1101 t.Errorf("Unexpected object watched %#v", event) 1102 } 1103 case <-time.After(time.Second * 3): 1104 if tt.expected { 1105 t.Errorf("Unexpected object watched (timeout)") 1106 } 1107 } 1108 }) 1109 } 1110 } 1111 1112 // RunOptionalTestWatchBookmarksWithCorrectResourceVersion tests whether bookmark events are 1113 // sent with correct resource versions. 1114 // This feature is currently implemented in watch cache layer, so this is optional. 1115 // 1116 // TODO(#109831): ProgressNotify feature is effectively implementing the same 1117 // 1118 // functionality, so we should refactor this functionality to share the same input. 1119 func RunTestOptionalWatchBookmarksWithCorrectResourceVersion(ctx context.Context, t *testing.T, store storage.Interface) { 1120 // Compute the initial resource version. 1121 list := &example.PodList{} 1122 storageOpts := storage.ListOptions{ 1123 Predicate: storage.Everything, 1124 Recursive: true, 1125 } 1126 if err := store.GetList(ctx, "/pods", storageOpts, list); err != nil { 1127 t.Errorf("Unexpected error: %v", err) 1128 } 1129 startRV := list.ResourceVersion 1130 1131 key := "/pods/test-ns" 1132 pred := storage.Everything 1133 pred.AllowWatchBookmarks = true 1134 1135 ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) 1136 defer cancel() 1137 1138 watcher, err := store.Watch(ctx, key, storage.ListOptions{ResourceVersion: startRV, Predicate: pred, Recursive: true}) 1139 if err != nil { 1140 t.Fatalf("Unexpected error: %v", err) 1141 } 1142 defer watcher.Stop() 1143 1144 done := make(chan struct{}) 1145 errc := make(chan error, 1) 1146 var wg sync.WaitGroup 1147 wg.Add(1) 1148 // We must wait for the waitgroup to exit before we terminate the cache or the server in prior defers. 1149 defer wg.Wait() 1150 // Call close first, so the goroutine knows to exit. 1151 defer close(done) 1152 1153 go func() { 1154 defer wg.Done() 1155 for i := 0; i < 100; i++ { 1156 select { 1157 case <-done: 1158 return 1159 default: 1160 out := &example.Pod{} 1161 pod := &example.Pod{ 1162 ObjectMeta: metav1.ObjectMeta{ 1163 Name: fmt.Sprintf("foo-%d", i), 1164 Namespace: "test-ns", 1165 }, 1166 } 1167 podKey := computePodKey(pod) 1168 if err := store.Create(ctx, podKey, pod, out, 0); err != nil { 1169 errc <- fmt.Errorf("failed to create pod %v: %v", pod, err) 1170 return 1171 } 1172 time.Sleep(10 * time.Millisecond) 1173 } 1174 } 1175 }() 1176 1177 bookmarkReceived := false 1178 lastObservedResourceVersion := uint64(0) 1179 1180 for { 1181 select { 1182 case err := <-errc: 1183 t.Fatal(err) 1184 case event, ok := <-watcher.ResultChan(): 1185 if !ok { 1186 // Make sure we have received a bookmark event 1187 if !bookmarkReceived { 1188 t.Fatalf("Unpexected error, we did not received a bookmark event") 1189 } 1190 return 1191 } 1192 rv, err := storage.APIObjectVersioner{}.ObjectResourceVersion(event.Object) 1193 if err != nil { 1194 t.Fatalf("failed to parse resourceVersion from %#v", event) 1195 } 1196 if event.Type == watch.Bookmark { 1197 bookmarkReceived = true 1198 // bookmark event has a RV greater than or equal to the before one 1199 if rv < lastObservedResourceVersion { 1200 t.Fatalf("Unexpected bookmark resourceVersion %v less than observed %v)", rv, lastObservedResourceVersion) 1201 } 1202 } else { 1203 // non-bookmark event has a RV greater than anything before 1204 if rv <= lastObservedResourceVersion { 1205 t.Fatalf("Unexpected event resourceVersion %v less than or equal to bookmark %v)", rv, lastObservedResourceVersion) 1206 } 1207 } 1208 lastObservedResourceVersion = rv 1209 } 1210 } 1211 } 1212 1213 // RunSendInitialEventsBackwardCompatibility test backward compatibility 1214 // when SendInitialEvents option is set against various implementations. 1215 // Backward compatibility is defined as RV = "" || RV = "O" and AllowWatchBookmark is set to false. 1216 // In that case we expect a watch request to be established. 1217 func RunSendInitialEventsBackwardCompatibility(ctx context.Context, t *testing.T, store storage.Interface) { 1218 opts := storage.ListOptions{Predicate: storage.Everything} 1219 opts.SendInitialEvents = pointer.Bool(true) 1220 w, err := store.Watch(ctx, "/pods", opts) 1221 require.NoError(t, err) 1222 w.Stop() 1223 } 1224 1225 // RunWatchSemantics test the following cases: 1226 // 1227 // +-----------------+---------------------+-------------------+ 1228 // | ResourceVersion | AllowWatchBookmarks | SendInitialEvents | 1229 // +=================+=====================+===================+ 1230 // | Unset | true/false | true/false | 1231 // | 0 | true/false | true/false | 1232 // | 1 | true/false | true/false | 1233 // | Current | true/false | true/false | 1234 // +-----------------+---------------------+-------------------+ 1235 // where: 1236 // - false indicates the value of the param was set to "false" by a test case 1237 // - true indicates the value of the param was set to "true" by a test case 1238 func RunWatchSemantics(ctx context.Context, t *testing.T, store storage.Interface) { 1239 trueVal, falseVal := true, false 1240 addEventsFromCreatedPods := func(createdInitialPods []*example.Pod) []watch.Event { 1241 var ret []watch.Event 1242 for _, createdPod := range createdInitialPods { 1243 ret = append(ret, watch.Event{Type: watch.Added, Object: createdPod}) 1244 } 1245 return ret 1246 } 1247 initialEventsEndFromLastCreatedPod := func(createdInitialPods []*example.Pod) watch.Event { 1248 return watch.Event{ 1249 Type: watch.Bookmark, 1250 Object: &example.Pod{ 1251 ObjectMeta: metav1.ObjectMeta{ 1252 ResourceVersion: createdInitialPods[len(createdInitialPods)-1].ResourceVersion, 1253 Annotations: map[string]string{"k8s.io/initial-events-end": "true"}, 1254 }, 1255 }, 1256 } 1257 } 1258 scenarios := []struct { 1259 name string 1260 allowWatchBookmarks bool 1261 sendInitialEvents *bool 1262 resourceVersion string 1263 // useCurrentRV if set gets the current RV from the storage 1264 // after adding the initial pods which is then used to establish a new watch request 1265 useCurrentRV bool 1266 1267 initialPods []*example.Pod 1268 podsAfterEstablishingWatch []*example.Pod 1269 1270 expectedInitialEventsInRandomOrder func(createdInitialPods []*example.Pod) []watch.Event 1271 expectedInitialEventsInStrictOrder func(createdInitialPods []*example.Pod) []watch.Event 1272 expectedEventsAfterEstablishingWatch func(createdPodsAfterWatch []*example.Pod) []watch.Event 1273 }{ 1274 { 1275 name: "allowWatchBookmarks=true, sendInitialEvents=true, RV=unset", 1276 allowWatchBookmarks: true, 1277 sendInitialEvents: &trueVal, 1278 initialPods: []*example.Pod{makePod("1"), makePod("2"), makePod("3")}, 1279 expectedInitialEventsInRandomOrder: addEventsFromCreatedPods, 1280 expectedInitialEventsInStrictOrder: func(createdInitialPods []*example.Pod) []watch.Event { 1281 return []watch.Event{initialEventsEndFromLastCreatedPod(createdInitialPods)} 1282 }, 1283 podsAfterEstablishingWatch: []*example.Pod{makePod("4"), makePod("5")}, 1284 expectedEventsAfterEstablishingWatch: addEventsFromCreatedPods, 1285 }, 1286 { 1287 name: "allowWatchBookmarks=true, sendInitialEvents=false, RV=unset", 1288 allowWatchBookmarks: true, 1289 sendInitialEvents: &falseVal, 1290 initialPods: []*example.Pod{makePod("1"), makePod("2"), makePod("3")}, 1291 podsAfterEstablishingWatch: []*example.Pod{makePod("4"), makePod("5")}, 1292 expectedEventsAfterEstablishingWatch: addEventsFromCreatedPods, 1293 }, 1294 { 1295 name: "allowWatchBookmarks=false, sendInitialEvents=false, RV=unset", 1296 sendInitialEvents: &falseVal, 1297 initialPods: []*example.Pod{makePod("1"), makePod("2"), makePod("3")}, 1298 podsAfterEstablishingWatch: []*example.Pod{makePod("4"), makePod("5")}, 1299 expectedEventsAfterEstablishingWatch: addEventsFromCreatedPods, 1300 }, 1301 { 1302 name: "allowWatchBookmarks=false, sendInitialEvents=true, RV=unset", 1303 sendInitialEvents: &trueVal, 1304 initialPods: []*example.Pod{makePod("1"), makePod("2"), makePod("3")}, 1305 expectedInitialEventsInRandomOrder: addEventsFromCreatedPods, 1306 podsAfterEstablishingWatch: []*example.Pod{makePod("4"), makePod("5")}, 1307 expectedEventsAfterEstablishingWatch: addEventsFromCreatedPods, 1308 }, 1309 1310 { 1311 name: "allowWatchBookmarks=true, sendInitialEvents=true, RV=0", 1312 allowWatchBookmarks: true, 1313 sendInitialEvents: &trueVal, 1314 resourceVersion: "0", 1315 initialPods: []*example.Pod{makePod("1"), makePod("2"), makePod("3")}, 1316 expectedInitialEventsInRandomOrder: addEventsFromCreatedPods, 1317 expectedInitialEventsInStrictOrder: func(createdInitialPods []*example.Pod) []watch.Event { 1318 return []watch.Event{initialEventsEndFromLastCreatedPod(createdInitialPods)} 1319 }, 1320 podsAfterEstablishingWatch: []*example.Pod{makePod("4"), makePod("5")}, 1321 expectedEventsAfterEstablishingWatch: addEventsFromCreatedPods, 1322 }, 1323 { 1324 name: "allowWatchBookmarks=true, sendInitialEvents=false, RV=0", 1325 allowWatchBookmarks: true, 1326 sendInitialEvents: &falseVal, 1327 resourceVersion: "0", 1328 initialPods: []*example.Pod{makePod("1"), makePod("2"), makePod("3")}, 1329 podsAfterEstablishingWatch: []*example.Pod{makePod("4"), makePod("5")}, 1330 expectedEventsAfterEstablishingWatch: addEventsFromCreatedPods, 1331 }, 1332 { 1333 name: "allowWatchBookmarks=false, sendInitialEvents=false, RV=0", 1334 sendInitialEvents: &falseVal, 1335 resourceVersion: "0", 1336 initialPods: []*example.Pod{makePod("1"), makePod("2"), makePod("3")}, 1337 podsAfterEstablishingWatch: []*example.Pod{makePod("4"), makePod("5")}, 1338 expectedEventsAfterEstablishingWatch: addEventsFromCreatedPods, 1339 }, 1340 { 1341 name: "allowWatchBookmarks=false, sendInitialEvents=true, RV=0", 1342 sendInitialEvents: &trueVal, 1343 resourceVersion: "0", 1344 initialPods: []*example.Pod{makePod("1"), makePod("2"), makePod("3")}, 1345 expectedInitialEventsInRandomOrder: addEventsFromCreatedPods, 1346 podsAfterEstablishingWatch: []*example.Pod{makePod("4"), makePod("5")}, 1347 expectedEventsAfterEstablishingWatch: addEventsFromCreatedPods, 1348 }, 1349 1350 { 1351 name: "allowWatchBookmarks=true, sendInitialEvents=true, RV=1", 1352 allowWatchBookmarks: true, 1353 sendInitialEvents: &trueVal, 1354 resourceVersion: "1", 1355 initialPods: []*example.Pod{makePod("1"), makePod("2"), makePod("3")}, 1356 expectedInitialEventsInRandomOrder: addEventsFromCreatedPods, 1357 expectedInitialEventsInStrictOrder: func(createdInitialPods []*example.Pod) []watch.Event { 1358 return []watch.Event{initialEventsEndFromLastCreatedPod(createdInitialPods)} 1359 }, 1360 podsAfterEstablishingWatch: []*example.Pod{makePod("4"), makePod("5")}, 1361 expectedEventsAfterEstablishingWatch: addEventsFromCreatedPods, 1362 }, 1363 { 1364 name: "allowWatchBookmarks=true, sendInitialEvents=false, RV=1", 1365 allowWatchBookmarks: true, 1366 sendInitialEvents: &falseVal, 1367 resourceVersion: "1", 1368 initialPods: []*example.Pod{makePod("1"), makePod("2"), makePod("3")}, 1369 expectedInitialEventsInStrictOrder: addEventsFromCreatedPods, 1370 podsAfterEstablishingWatch: []*example.Pod{makePod("4"), makePod("5")}, 1371 expectedEventsAfterEstablishingWatch: addEventsFromCreatedPods, 1372 }, 1373 { 1374 name: "allowWatchBookmarks=false, sendInitialEvents=false, RV=1", 1375 sendInitialEvents: &falseVal, 1376 resourceVersion: "1", 1377 initialPods: []*example.Pod{makePod("1"), makePod("2"), makePod("3")}, 1378 expectedInitialEventsInStrictOrder: addEventsFromCreatedPods, 1379 podsAfterEstablishingWatch: []*example.Pod{makePod("4"), makePod("5")}, 1380 expectedEventsAfterEstablishingWatch: addEventsFromCreatedPods, 1381 }, 1382 { 1383 name: "allowWatchBookmarks=false, sendInitialEvents=true, RV=1", 1384 sendInitialEvents: &trueVal, 1385 resourceVersion: "1", 1386 initialPods: []*example.Pod{makePod("1"), makePod("2"), makePod("3")}, 1387 expectedInitialEventsInRandomOrder: addEventsFromCreatedPods, 1388 podsAfterEstablishingWatch: []*example.Pod{makePod("4"), makePod("5")}, 1389 expectedEventsAfterEstablishingWatch: addEventsFromCreatedPods, 1390 }, 1391 1392 { 1393 name: "allowWatchBookmarks=true, sendInitialEvents=true, RV=useCurrentRV", 1394 allowWatchBookmarks: true, 1395 sendInitialEvents: &trueVal, 1396 useCurrentRV: true, 1397 initialPods: []*example.Pod{makePod("1"), makePod("2"), makePod("3")}, 1398 expectedInitialEventsInRandomOrder: addEventsFromCreatedPods, 1399 expectedInitialEventsInStrictOrder: func(createdInitialPods []*example.Pod) []watch.Event { 1400 return []watch.Event{initialEventsEndFromLastCreatedPod(createdInitialPods)} 1401 }, 1402 podsAfterEstablishingWatch: []*example.Pod{makePod("4"), makePod("5")}, 1403 expectedEventsAfterEstablishingWatch: addEventsFromCreatedPods, 1404 }, 1405 { 1406 name: "allowWatchBookmarks=true, sendInitialEvents=false, RV=useCurrentRV", 1407 allowWatchBookmarks: true, 1408 sendInitialEvents: &falseVal, 1409 useCurrentRV: true, 1410 initialPods: []*example.Pod{makePod("1"), makePod("2"), makePod("3")}, 1411 podsAfterEstablishingWatch: []*example.Pod{makePod("4"), makePod("5")}, 1412 expectedEventsAfterEstablishingWatch: addEventsFromCreatedPods, 1413 }, 1414 { 1415 name: "allowWatchBookmarks=false, sendInitialEvents=false, RV=useCurrentRV", 1416 sendInitialEvents: &falseVal, 1417 useCurrentRV: true, 1418 initialPods: []*example.Pod{makePod("1"), makePod("2"), makePod("3")}, 1419 podsAfterEstablishingWatch: []*example.Pod{makePod("4"), makePod("5")}, 1420 expectedEventsAfterEstablishingWatch: addEventsFromCreatedPods, 1421 }, 1422 { 1423 name: "allowWatchBookmarks=false, sendInitialEvents=true, RV=useCurrentRV", 1424 sendInitialEvents: &trueVal, 1425 useCurrentRV: true, 1426 initialPods: []*example.Pod{makePod("1"), makePod("2"), makePod("3")}, 1427 expectedInitialEventsInRandomOrder: addEventsFromCreatedPods, 1428 podsAfterEstablishingWatch: []*example.Pod{makePod("4"), makePod("5")}, 1429 expectedEventsAfterEstablishingWatch: addEventsFromCreatedPods, 1430 }, 1431 1432 { 1433 name: "legacy, RV=0", 1434 resourceVersion: "0", 1435 initialPods: []*example.Pod{makePod("1"), makePod("2"), makePod("3")}, 1436 expectedInitialEventsInRandomOrder: addEventsFromCreatedPods, 1437 podsAfterEstablishingWatch: []*example.Pod{makePod("4"), makePod("5")}, 1438 expectedEventsAfterEstablishingWatch: addEventsFromCreatedPods, 1439 }, 1440 { 1441 name: "legacy, RV=unset", 1442 initialPods: []*example.Pod{makePod("1"), makePod("2"), makePod("3")}, 1443 expectedInitialEventsInRandomOrder: addEventsFromCreatedPods, 1444 podsAfterEstablishingWatch: []*example.Pod{makePod("4"), makePod("5")}, 1445 expectedEventsAfterEstablishingWatch: addEventsFromCreatedPods, 1446 }, 1447 } 1448 for idx, scenario := range scenarios { 1449 t.Run(scenario.name, func(t *testing.T) { 1450 // set up env 1451 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.WatchList, true)() 1452 if scenario.expectedInitialEventsInStrictOrder == nil { 1453 scenario.expectedInitialEventsInStrictOrder = func(_ []*example.Pod) []watch.Event { return nil } 1454 } 1455 if scenario.expectedInitialEventsInRandomOrder == nil { 1456 scenario.expectedInitialEventsInRandomOrder = func(_ []*example.Pod) []watch.Event { return nil } 1457 } 1458 if scenario.expectedEventsAfterEstablishingWatch == nil { 1459 scenario.expectedEventsAfterEstablishingWatch = func(_ []*example.Pod) []watch.Event { return nil } 1460 } 1461 1462 var createdPods []*example.Pod 1463 ns := fmt.Sprintf("ns-%v", idx) 1464 for _, obj := range scenario.initialPods { 1465 obj.Namespace = ns 1466 out := &example.Pod{} 1467 err := store.Create(ctx, computePodKey(obj), obj, out, 0) 1468 require.NoError(t, err, "failed to add a pod: %v", obj) 1469 createdPods = append(createdPods, out) 1470 } 1471 1472 if scenario.useCurrentRV { 1473 currentStorageRV, err := storage.GetCurrentResourceVersionFromStorage(ctx, store, func() runtime.Object { return &example.PodList{} }, "/pods", "") 1474 require.NoError(t, err) 1475 scenario.resourceVersion = fmt.Sprintf("%d", currentStorageRV) 1476 } 1477 1478 opts := storage.ListOptions{Predicate: storage.Everything, Recursive: true} 1479 opts.SendInitialEvents = scenario.sendInitialEvents 1480 opts.Predicate.AllowWatchBookmarks = scenario.allowWatchBookmarks 1481 if len(scenario.resourceVersion) > 0 { 1482 opts.ResourceVersion = scenario.resourceVersion 1483 } 1484 1485 w, err := store.Watch(context.Background(), fmt.Sprintf("/pods/%s", ns), opts) 1486 require.NoError(t, err, "failed to create watch: %v") 1487 defer w.Stop() 1488 1489 // make sure we only get initial events 1490 testCheckResultsInRandomOrder(t, w, scenario.expectedInitialEventsInRandomOrder(createdPods)) 1491 testCheckResultsInStrictOrder(t, w, scenario.expectedInitialEventsInStrictOrder(createdPods)) 1492 testCheckNoMoreResults(t, w) 1493 1494 createdPods = []*example.Pod{} 1495 // add a pod that is greater than the storage's RV when the watch was started 1496 for _, obj := range scenario.podsAfterEstablishingWatch { 1497 obj.Namespace = ns 1498 out := &example.Pod{} 1499 err = store.Create(ctx, computePodKey(obj), obj, out, 0) 1500 require.NoError(t, err, "failed to add a pod: %v") 1501 createdPods = append(createdPods, out) 1502 } 1503 testCheckResultsInStrictOrder(t, w, scenario.expectedEventsAfterEstablishingWatch(createdPods)) 1504 testCheckNoMoreResults(t, w) 1505 }) 1506 } 1507 } 1508 1509 // RunWatchSemanticInitialEventsExtended checks if the bookmark event 1510 // marking the end of the list stream contains the global RV. 1511 // 1512 // note that this scenario differs from the one in RunWatchSemantics 1513 // by adding the pod to a different ns to advance the global RV 1514 func RunWatchSemanticInitialEventsExtended(ctx context.Context, t *testing.T, store storage.Interface) { 1515 trueVal := true 1516 expectedInitialEventsInStrictOrder := func(firstPod, secondPod *example.Pod) []watch.Event { 1517 return []watch.Event{ 1518 {Type: watch.Added, Object: firstPod}, 1519 {Type: watch.Bookmark, Object: &example.Pod{ 1520 ObjectMeta: metav1.ObjectMeta{ 1521 ResourceVersion: secondPod.ResourceVersion, 1522 Annotations: map[string]string{"k8s.io/initial-events-end": "true"}, 1523 }, 1524 }}, 1525 } 1526 } 1527 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.WatchList, true)() 1528 1529 ns := "ns-foo" 1530 pod := makePod("1") 1531 pod.Namespace = ns 1532 firstPod := &example.Pod{} 1533 err := store.Create(ctx, computePodKey(pod), pod, firstPod, 0) 1534 require.NoError(t, err, "failed to add a pod: %v") 1535 1536 // add the pod to a different ns to advance the global RV 1537 pod = makePod("2") 1538 pod.Namespace = "other-ns-foo" 1539 secondPod := &example.Pod{} 1540 err = store.Create(ctx, computePodKey(pod), pod, secondPod, 0) 1541 require.NoError(t, err, "failed to add a pod: %v") 1542 1543 opts := storage.ListOptions{Predicate: storage.Everything, Recursive: true} 1544 opts.SendInitialEvents = &trueVal 1545 opts.Predicate.AllowWatchBookmarks = true 1546 1547 w, err := store.Watch(context.Background(), fmt.Sprintf("/pods/%s", ns), opts) 1548 require.NoError(t, err, "failed to create watch: %v") 1549 defer w.Stop() 1550 1551 // make sure we only get initial events from the first ns 1552 // followed by the bookmark with the global RV 1553 testCheckResultsInStrictOrder(t, w, expectedInitialEventsInStrictOrder(firstPod, secondPod)) 1554 testCheckNoMoreResults(t, w) 1555 } 1556 1557 func makePod(namePrefix string) *example.Pod { 1558 return &example.Pod{ 1559 ObjectMeta: metav1.ObjectMeta{ 1560 Name: fmt.Sprintf("pod-%s", namePrefix), 1561 }, 1562 } 1563 } 1564 1565 type testWatchStruct struct { 1566 obj *example.Pod 1567 expectEvent bool 1568 watchType watch.EventType 1569 } 1570 1571 func createPodPredicate(field fields.Selector, namespaceScoped bool, indexField []string) storage.SelectionPredicate { 1572 return storage.SelectionPredicate{ 1573 Label: labels.Everything(), 1574 Field: field, 1575 GetAttrs: determinePodGetAttrFunc(namespaceScoped, indexField), 1576 IndexFields: indexField, 1577 } 1578 } 1579 1580 func determinePodGetAttrFunc(namespaceScoped bool, indexField []string) storage.AttrFunc { 1581 if indexField != nil { 1582 if namespaceScoped { 1583 return namespacedScopedNodeNameAttrFunc 1584 } 1585 return clusterScopedNodeNameAttrFunc 1586 } 1587 if namespaceScoped { 1588 return storage.DefaultNamespaceScopedAttr 1589 } 1590 return storage.DefaultClusterScopedAttr 1591 } 1592 1593 func namespacedScopedNodeNameAttrFunc(obj runtime.Object) (labels.Set, fields.Set, error) { 1594 pod := obj.(*example.Pod) 1595 return nil, fields.Set{ 1596 "spec.nodeName": pod.Spec.NodeName, 1597 "metadata.name": pod.ObjectMeta.Name, 1598 "metadata.namespace": pod.ObjectMeta.Namespace, 1599 }, nil 1600 } 1601 1602 func clusterScopedNodeNameAttrFunc(obj runtime.Object) (labels.Set, fields.Set, error) { 1603 pod := obj.(*example.Pod) 1604 return nil, fields.Set{ 1605 "spec.nodeName": pod.Spec.NodeName, 1606 "metadata.name": pod.ObjectMeta.Name, 1607 }, nil 1608 } 1609 1610 func basePod(podName string) *example.Pod { 1611 return baseNamespacedPod(podName, "") 1612 } 1613 1614 func basePodUpdated(podName string) *example.Pod { 1615 return baseNamespacedPodUpdated(podName, "") 1616 } 1617 1618 func basePodAssigned(podName, nodeName string) *example.Pod { 1619 return baseNamespacedPodAssigned(podName, "", nodeName) 1620 } 1621 1622 func baseNamespacedPod(podName, namespace string) *example.Pod { 1623 return &example.Pod{ 1624 ObjectMeta: metav1.ObjectMeta{Name: podName, Namespace: namespace}, 1625 } 1626 } 1627 1628 func baseNamespacedPodUpdated(podName, namespace string) *example.Pod { 1629 return &example.Pod{ 1630 ObjectMeta: metav1.ObjectMeta{Name: podName, Namespace: namespace}, 1631 Status: example.PodStatus{Phase: "Running"}, 1632 } 1633 } 1634 1635 func baseNamespacedPodAssigned(podName, namespace, nodeName string) *example.Pod { 1636 return &example.Pod{ 1637 ObjectMeta: metav1.ObjectMeta{Name: podName, Namespace: namespace}, 1638 Spec: example.PodSpec{NodeName: nodeName}, 1639 } 1640 }