k8s.io/client-go@v0.31.1/tools/watch/informerwatcher_test.go (about) 1 /* 2 Copyright 2017 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 watch 18 19 import ( 20 "context" 21 "reflect" 22 goruntime "runtime" 23 "sort" 24 "testing" 25 "time" 26 27 "github.com/google/go-cmp/cmp" 28 29 corev1 "k8s.io/api/core/v1" 30 apierrors "k8s.io/apimachinery/pkg/api/errors" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 "k8s.io/apimachinery/pkg/runtime" 33 "k8s.io/apimachinery/pkg/runtime/schema" 34 "k8s.io/apimachinery/pkg/util/dump" 35 "k8s.io/apimachinery/pkg/util/wait" 36 "k8s.io/apimachinery/pkg/watch" 37 clientfeatures "k8s.io/client-go/features" 38 clientfeaturestesting "k8s.io/client-go/features/testing" 39 fakeclientset "k8s.io/client-go/kubernetes/fake" 40 testcore "k8s.io/client-go/testing" 41 "k8s.io/client-go/tools/cache" 42 ) 43 44 // TestEventProcessorExit is expected to timeout if the event processor fails 45 // to exit when stopped. 46 func TestEventProcessorExit(t *testing.T) { 47 event := watch.Event{} 48 49 tests := []struct { 50 name string 51 write func(e *eventProcessor) 52 }{ 53 { 54 name: "exit on blocked read", 55 write: func(e *eventProcessor) { 56 e.push(event) 57 }, 58 }, 59 { 60 name: "exit on blocked write", 61 write: func(e *eventProcessor) { 62 e.push(event) 63 e.push(event) 64 }, 65 }, 66 } 67 for _, test := range tests { 68 t.Run(test.name, func(t *testing.T) { 69 out := make(chan watch.Event) 70 e := newEventProcessor(out) 71 72 test.write(e) 73 74 exited := make(chan struct{}) 75 go func() { 76 e.run() 77 close(exited) 78 }() 79 80 <-out 81 e.stop() 82 goruntime.Gosched() 83 <-exited 84 }) 85 } 86 } 87 88 type apiInt int 89 90 func (apiInt) GetObjectKind() schema.ObjectKind { return nil } 91 func (apiInt) DeepCopyObject() runtime.Object { return nil } 92 93 func TestEventProcessorOrdersEvents(t *testing.T) { 94 out := make(chan watch.Event) 95 e := newEventProcessor(out) 96 go e.run() 97 98 numProcessed := 0 99 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 100 go func() { 101 for i := 0; i < 1000; i++ { 102 e := <-out 103 if got, want := int(e.Object.(apiInt)), i; got != want { 104 t.Errorf("unexpected event: got=%d, want=%d", got, want) 105 } 106 numProcessed++ 107 } 108 cancel() 109 }() 110 111 for i := 0; i < 1000; i++ { 112 e.push(watch.Event{Object: apiInt(i)}) 113 } 114 115 <-ctx.Done() 116 e.stop() 117 118 if numProcessed != 1000 { 119 t.Errorf("unexpected number of events processed: %d", numProcessed) 120 } 121 122 } 123 124 type byEventTypeAndName []watch.Event 125 126 func (a byEventTypeAndName) Len() int { return len(a) } 127 func (a byEventTypeAndName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 128 func (a byEventTypeAndName) Less(i, j int) bool { 129 if a[i].Type < a[j].Type { 130 return true 131 } 132 133 if a[i].Type > a[j].Type { 134 return false 135 } 136 137 return a[i].Object.(*corev1.Secret).Name < a[j].Object.(*corev1.Secret).Name 138 } 139 140 func newTestSecret(name, key, value string) *corev1.Secret { 141 return &corev1.Secret{ 142 ObjectMeta: metav1.ObjectMeta{ 143 Name: name, 144 }, 145 StringData: map[string]string{ 146 key: value, 147 }, 148 } 149 } 150 151 func TestNewInformerWatcher(t *testing.T) { 152 // Make sure there are no 2 same types of events on a secret with the same name or that might be flaky. 153 tt := []struct { 154 name string 155 watchListFeatureEnabled bool 156 objects []runtime.Object 157 inputEvents []watch.Event 158 outputEvents []watch.Event 159 }{ 160 { 161 name: "WatchListClient feature disabled", 162 watchListFeatureEnabled: false, 163 objects: []runtime.Object{ 164 newTestSecret("pod-1", "foo-1", "initial"), 165 newTestSecret("pod-2", "foo-2", "initial"), 166 newTestSecret("pod-3", "foo-3", "initial"), 167 }, 168 inputEvents: []watch.Event{ 169 { 170 Type: watch.Added, 171 Object: newTestSecret("pod-4", "foo-4", "initial"), 172 }, 173 { 174 Type: watch.Modified, 175 Object: newTestSecret("pod-2", "foo-2", "new"), 176 }, 177 { 178 Type: watch.Deleted, 179 Object: newTestSecret("pod-3", "foo-3", "initial"), 180 }, 181 }, 182 outputEvents: []watch.Event{ 183 // When WatchListClient is disabled, ListAndWatch creates fake 184 // ADDED events for each object listed. 185 { 186 Type: watch.Added, 187 Object: newTestSecret("pod-1", "foo-1", "initial"), 188 }, 189 { 190 Type: watch.Added, 191 Object: newTestSecret("pod-2", "foo-2", "initial"), 192 }, 193 { 194 Type: watch.Added, 195 Object: newTestSecret("pod-3", "foo-3", "initial"), 196 }, 197 // Normal events follow. 198 { 199 Type: watch.Added, 200 Object: newTestSecret("pod-4", "foo-4", "initial"), 201 }, 202 { 203 Type: watch.Modified, 204 Object: newTestSecret("pod-2", "foo-2", "new"), 205 }, 206 { 207 Type: watch.Deleted, 208 Object: newTestSecret("pod-3", "foo-3", "initial"), 209 }, 210 }, 211 }, 212 { 213 name: "WatchListClient feature enabled", 214 watchListFeatureEnabled: true, 215 objects: []runtime.Object{ 216 newTestSecret("pod-1", "foo-1", "initial"), 217 newTestSecret("pod-2", "foo-2", "initial"), 218 newTestSecret("pod-3", "foo-3", "initial"), 219 }, 220 inputEvents: []watch.Event{ 221 { 222 Type: watch.Added, 223 Object: newTestSecret("pod-1", "foo-1", "initial"), 224 }, 225 { 226 Type: watch.Added, 227 Object: newTestSecret("pod-2", "foo-2", "initial"), 228 }, 229 { 230 Type: watch.Added, 231 Object: newTestSecret("pod-3", "foo-3", "initial"), 232 }, 233 // ListWatch bookmark indicates that initial listing is done 234 { 235 Type: watch.Bookmark, 236 Object: &corev1.Secret{ 237 ObjectMeta: metav1.ObjectMeta{ 238 Annotations: map[string]string{ 239 metav1.InitialEventsAnnotationKey: "true", 240 }, 241 }, 242 }, 243 }, 244 { 245 Type: watch.Added, 246 Object: newTestSecret("pod-4", "foo-4", "initial"), 247 }, 248 { 249 Type: watch.Modified, 250 Object: newTestSecret("pod-2", "foo-2", "new"), 251 }, 252 { 253 Type: watch.Deleted, 254 Object: newTestSecret("pod-3", "foo-3", "initial"), 255 }, 256 }, 257 outputEvents: []watch.Event{ 258 // When WatchListClient is enabled, WatchList receives 259 // ADDED events from the server for each existing object. 260 { 261 Type: watch.Added, 262 Object: newTestSecret("pod-1", "foo-1", "initial"), 263 }, 264 { 265 Type: watch.Added, 266 Object: newTestSecret("pod-2", "foo-2", "initial"), 267 }, 268 { 269 Type: watch.Added, 270 Object: newTestSecret("pod-3", "foo-3", "initial"), 271 }, 272 // Bookmark event at the end of listing is not sent to the client. 273 // Normal events follow. 274 { 275 Type: watch.Added, 276 Object: newTestSecret("pod-4", "foo-4", "initial"), 277 }, 278 { 279 Type: watch.Modified, 280 Object: newTestSecret("pod-2", "foo-2", "new"), 281 }, 282 { 283 Type: watch.Deleted, 284 Object: newTestSecret("pod-3", "foo-3", "initial"), 285 }, 286 }, 287 }, 288 } 289 290 for _, tc := range tt { 291 t.Run(tc.name, func(t *testing.T) { 292 clientfeaturestesting.SetFeatureDuringTest(t, clientfeatures.WatchListClient, tc.watchListFeatureEnabled) 293 294 fake := fakeclientset.NewSimpleClientset(tc.objects...) 295 inputCh := make(chan watch.Event) 296 inputWatcher := watch.NewProxyWatcher(inputCh) 297 // Indexer should stop the input watcher when the output watcher is stopped. 298 // But stop it at the end of the test, just in case. 299 defer inputWatcher.Stop() 300 inputStopCh := inputWatcher.StopChan() 301 fake.PrependWatchReactor("secrets", testcore.DefaultWatchReactor(inputWatcher, nil)) 302 // Send events and then close the done channel 303 inputDoneCh := make(chan struct{}) 304 go func() { 305 defer close(inputDoneCh) 306 for _, e := range tc.inputEvents { 307 select { 308 case inputCh <- e: 309 case <-inputStopCh: 310 return 311 } 312 } 313 }() 314 315 lw := &cache.ListWatch{ 316 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 317 return fake.CoreV1().Secrets("").List(context.TODO(), options) 318 }, 319 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 320 return fake.CoreV1().Secrets("").Watch(context.TODO(), options) 321 }, 322 } 323 _, _, outputWatcher, informerDoneCh := NewIndexerInformerWatcher(lw, &corev1.Secret{}) 324 outputCh := outputWatcher.ResultChan() 325 timeoutCh := time.After(wait.ForeverTestTimeout) 326 var result []watch.Event 327 loop: 328 for { 329 select { 330 case event, ok := <-outputCh: 331 if !ok { 332 t.Errorf("Output result channel closed prematurely") 333 break loop 334 } 335 result = append(result, *event.DeepCopy()) 336 if len(result) >= len(tc.outputEvents) { 337 break loop 338 } 339 case <-timeoutCh: 340 t.Error("Timed out waiting for events") 341 break loop 342 } 343 } 344 345 // Informers don't guarantee event order so we need to sort these arrays to compare them. 346 sort.Sort(byEventTypeAndName(tc.outputEvents)) 347 sort.Sort(byEventTypeAndName(result)) 348 349 if !reflect.DeepEqual(tc.outputEvents, result) { 350 t.Errorf("\nexpected: %s,\ngot: %s,\ndiff: %s", dump.Pretty(tc.outputEvents), dump.Pretty(result), cmp.Diff(tc.outputEvents, result)) 351 return 352 } 353 354 // Send some more events, but don't read them. 355 // Stop producing events when the consumer stops the watcher. 356 go func() { 357 defer close(inputCh) 358 for _, e := range tc.inputEvents { 359 select { 360 case inputCh <- e: 361 case <-inputStopCh: 362 return 363 } 364 } 365 }() 366 367 // Stop before reading all the data to make sure the informer can deal with closed channel 368 outputWatcher.Stop() 369 370 select { 371 case <-informerDoneCh: 372 case <-time.After(wait.ForeverTestTimeout): 373 t.Error("Timed out waiting for informer to cleanup") 374 } 375 }) 376 } 377 378 } 379 380 // TestInformerWatcherDeletedFinalStateUnknown tests the code path when `DeleteFunc` 381 // in `NewIndexerInformerWatcher` receives a `cache.DeletedFinalStateUnknown` 382 // object from the underlying `DeltaFIFO`. The triggering condition is described 383 // at https://github.com/kubernetes/kubernetes/blob/dc39ab2417bfddcec37be4011131c59921fdbe98/staging/src/k8s.io/client-go/tools/cache/delta_fifo.go#L736-L739. 384 // 385 // Code from @liggitt 386 func TestInformerWatcherDeletedFinalStateUnknown(t *testing.T) { 387 listCalls := 0 388 watchCalls := 0 389 lw := &cache.ListWatch{ 390 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 391 retval := &corev1.SecretList{} 392 if listCalls == 0 { 393 // Return a list with items in it 394 retval.ResourceVersion = "1" 395 retval.Items = []corev1.Secret{{ObjectMeta: metav1.ObjectMeta{Name: "secret1", Namespace: "ns1", ResourceVersion: "123"}}} 396 } else { 397 // Return empty lists after the first call 398 retval.ResourceVersion = "2" 399 } 400 listCalls++ 401 return retval, nil 402 }, 403 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 404 w := watch.NewRaceFreeFake() 405 if options.ResourceVersion == "1" { 406 go func() { 407 // Close with a "Gone" error when trying to start a watch from the first list 408 w.Error(&apierrors.NewGone("gone").ErrStatus) 409 w.Stop() 410 }() 411 } 412 watchCalls++ 413 return w, nil 414 }, 415 } 416 _, _, w, done := NewIndexerInformerWatcher(lw, &corev1.Secret{}) 417 defer w.Stop() 418 419 // Expect secret add 420 select { 421 case event, ok := <-w.ResultChan(): 422 if !ok { 423 t.Fatal("unexpected close") 424 } 425 if event.Type != watch.Added { 426 t.Fatalf("expected Added event, got %#v", event) 427 } 428 if event.Object.(*corev1.Secret).ResourceVersion != "123" { 429 t.Fatalf("expected added Secret with rv=123, got %#v", event.Object) 430 } 431 case <-time.After(time.Second * 10): 432 t.Fatal("timeout") 433 } 434 435 // Expect secret delete because the relist was missing the secret 436 select { 437 case event, ok := <-w.ResultChan(): 438 if !ok { 439 t.Fatal("unexpected close") 440 } 441 if event.Type != watch.Deleted { 442 t.Fatalf("expected Deleted event, got %#v", event) 443 } 444 if event.Object.(*corev1.Secret).ResourceVersion != "123" { 445 t.Fatalf("expected deleted Secret with rv=123, got %#v", event.Object) 446 } 447 case <-time.After(time.Second * 10): 448 t.Fatal("timeout") 449 } 450 451 w.Stop() 452 select { 453 case <-done: 454 case <-time.After(time.Second * 10): 455 t.Fatal("timeout") 456 } 457 458 if listCalls < 2 { 459 t.Fatalf("expected at least 2 list calls, got %d", listCalls) 460 } 461 if watchCalls < 1 { 462 t.Fatalf("expected at least 1 watch call, got %d", watchCalls) 463 } 464 }