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