k8s.io/client-go@v0.22.2/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/davecgh/go-spew/spew" 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/diff" 35 "k8s.io/apimachinery/pkg/watch" 36 fakeclientset "k8s.io/client-go/kubernetes/fake" 37 testcore "k8s.io/client-go/testing" 38 "k8s.io/client-go/tools/cache" 39 ) 40 41 // TestEventProcessorExit is expected to timeout if the event processor fails 42 // to exit when stopped. 43 func TestEventProcessorExit(t *testing.T) { 44 event := watch.Event{} 45 46 tests := []struct { 47 name string 48 write func(e *eventProcessor) 49 }{ 50 { 51 name: "exit on blocked read", 52 write: func(e *eventProcessor) { 53 e.push(event) 54 }, 55 }, 56 { 57 name: "exit on blocked write", 58 write: func(e *eventProcessor) { 59 e.push(event) 60 e.push(event) 61 }, 62 }, 63 } 64 for _, test := range tests { 65 t.Run(test.name, func(t *testing.T) { 66 out := make(chan watch.Event) 67 e := newEventProcessor(out) 68 69 test.write(e) 70 71 exited := make(chan struct{}) 72 go func() { 73 e.run() 74 close(exited) 75 }() 76 77 <-out 78 e.stop() 79 goruntime.Gosched() 80 <-exited 81 }) 82 } 83 } 84 85 type apiInt int 86 87 func (apiInt) GetObjectKind() schema.ObjectKind { return nil } 88 func (apiInt) DeepCopyObject() runtime.Object { return nil } 89 90 func TestEventProcessorOrdersEvents(t *testing.T) { 91 out := make(chan watch.Event) 92 e := newEventProcessor(out) 93 go e.run() 94 95 numProcessed := 0 96 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 97 go func() { 98 for i := 0; i < 1000; i++ { 99 e := <-out 100 if got, want := int(e.Object.(apiInt)), i; got != want { 101 t.Errorf("unexpected event: got=%d, want=%d", got, want) 102 } 103 numProcessed++ 104 } 105 cancel() 106 }() 107 108 for i := 0; i < 1000; i++ { 109 e.push(watch.Event{Object: apiInt(i)}) 110 } 111 112 <-ctx.Done() 113 e.stop() 114 115 if numProcessed != 1000 { 116 t.Errorf("unexpected number of events processed: %d", numProcessed) 117 } 118 119 } 120 121 type byEventTypeAndName []watch.Event 122 123 func (a byEventTypeAndName) Len() int { return len(a) } 124 func (a byEventTypeAndName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 125 func (a byEventTypeAndName) Less(i, j int) bool { 126 if a[i].Type < a[j].Type { 127 return true 128 } 129 130 if a[i].Type > a[j].Type { 131 return false 132 } 133 134 return a[i].Object.(*corev1.Secret).Name < a[j].Object.(*corev1.Secret).Name 135 } 136 137 func TestNewInformerWatcher(t *testing.T) { 138 // Make sure there are no 2 same types of events on a secret with the same name or that might be flaky. 139 tt := []struct { 140 name string 141 objects []runtime.Object 142 events []watch.Event 143 }{ 144 { 145 name: "basic test", 146 objects: []runtime.Object{ 147 &corev1.Secret{ 148 ObjectMeta: metav1.ObjectMeta{ 149 Name: "pod-1", 150 }, 151 StringData: map[string]string{ 152 "foo-1": "initial", 153 }, 154 }, 155 &corev1.Secret{ 156 ObjectMeta: metav1.ObjectMeta{ 157 Name: "pod-2", 158 }, 159 StringData: map[string]string{ 160 "foo-2": "initial", 161 }, 162 }, 163 &corev1.Secret{ 164 ObjectMeta: metav1.ObjectMeta{ 165 Name: "pod-3", 166 }, 167 StringData: map[string]string{ 168 "foo-3": "initial", 169 }, 170 }, 171 }, 172 events: []watch.Event{ 173 { 174 Type: watch.Added, 175 Object: &corev1.Secret{ 176 ObjectMeta: metav1.ObjectMeta{ 177 Name: "pod-4", 178 }, 179 StringData: map[string]string{ 180 "foo-4": "initial", 181 }, 182 }, 183 }, 184 { 185 Type: watch.Modified, 186 Object: &corev1.Secret{ 187 ObjectMeta: metav1.ObjectMeta{ 188 Name: "pod-2", 189 }, 190 StringData: map[string]string{ 191 "foo-2": "new", 192 }, 193 }, 194 }, 195 { 196 Type: watch.Deleted, 197 Object: &corev1.Secret{ 198 ObjectMeta: metav1.ObjectMeta{ 199 Name: "pod-3", 200 }, 201 }, 202 }, 203 }, 204 }, 205 } 206 207 for _, tc := range tt { 208 t.Run(tc.name, func(t *testing.T) { 209 var expected []watch.Event 210 for _, o := range tc.objects { 211 expected = append(expected, watch.Event{ 212 Type: watch.Added, 213 Object: o.DeepCopyObject(), 214 }) 215 } 216 for _, e := range tc.events { 217 expected = append(expected, *e.DeepCopy()) 218 } 219 220 fake := fakeclientset.NewSimpleClientset(tc.objects...) 221 fakeWatch := watch.NewFakeWithChanSize(len(tc.events), false) 222 fake.PrependWatchReactor("secrets", testcore.DefaultWatchReactor(fakeWatch, nil)) 223 224 for _, e := range tc.events { 225 fakeWatch.Action(e.Type, e.Object) 226 } 227 228 lw := &cache.ListWatch{ 229 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 230 return fake.CoreV1().Secrets("").List(context.TODO(), options) 231 }, 232 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 233 return fake.CoreV1().Secrets("").Watch(context.TODO(), options) 234 }, 235 } 236 _, _, w, done := NewIndexerInformerWatcher(lw, &corev1.Secret{}) 237 238 var result []watch.Event 239 loop: 240 for { 241 var event watch.Event 242 var ok bool 243 select { 244 case event, ok = <-w.ResultChan(): 245 if !ok { 246 t.Errorf("Failed to read event: channel is already closed!") 247 return 248 } 249 250 result = append(result, *event.DeepCopy()) 251 case <-time.After(time.Second * 1): 252 // All the events are buffered -> this means we are done 253 // Also the one sec will make sure that we would detect RetryWatcher's incorrect behaviour after last event 254 break loop 255 } 256 } 257 258 // Informers don't guarantee event order so we need to sort these arrays to compare them 259 sort.Sort(byEventTypeAndName(expected)) 260 sort.Sort(byEventTypeAndName(result)) 261 262 if !reflect.DeepEqual(expected, result) { 263 t.Error(spew.Errorf("\nexpected: %#v,\ngot: %#v,\ndiff: %s", expected, result, diff.ObjectReflectDiff(expected, result))) 264 return 265 } 266 267 // Fill in some data to test watch closing while there are some events to be read 268 for _, e := range tc.events { 269 fakeWatch.Action(e.Type, e.Object) 270 } 271 272 // Stop before reading all the data to make sure the informer can deal with closed channel 273 w.Stop() 274 275 <-done 276 }) 277 } 278 279 } 280 281 // TestInformerWatcherDeletedFinalStateUnknown tests the code path when `DeleteFunc` 282 // in `NewIndexerInformerWatcher` receives a `cache.DeletedFinalStateUnknown` 283 // object from the underlying `DeltaFIFO`. The triggering condition is described 284 // at https://github.com/kubernetes/kubernetes/blob/dc39ab2417bfddcec37be4011131c59921fdbe98/staging/src/k8s.io/client-go/tools/cache/delta_fifo.go#L736-L739. 285 // 286 // Code from @liggitt 287 func TestInformerWatcherDeletedFinalStateUnknown(t *testing.T) { 288 listCalls := 0 289 watchCalls := 0 290 lw := &cache.ListWatch{ 291 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 292 retval := &corev1.SecretList{} 293 if listCalls == 0 { 294 // Return a list with items in it 295 retval.ResourceVersion = "1" 296 retval.Items = []corev1.Secret{{ObjectMeta: metav1.ObjectMeta{Name: "secret1", Namespace: "ns1", ResourceVersion: "123"}}} 297 } else { 298 // Return empty lists after the first call 299 retval.ResourceVersion = "2" 300 } 301 listCalls++ 302 return retval, nil 303 }, 304 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 305 w := watch.NewFake() 306 if options.ResourceVersion == "1" { 307 go func() { 308 // Close with a "Gone" error when trying to start a watch from the first list 309 w.Error(&apierrors.NewGone("gone").ErrStatus) 310 w.Stop() 311 }() 312 } 313 watchCalls++ 314 return w, nil 315 }, 316 } 317 _, _, w, done := NewIndexerInformerWatcher(lw, &corev1.Secret{}) 318 319 // Expect secret add 320 select { 321 case event, ok := <-w.ResultChan(): 322 if !ok { 323 t.Fatal("unexpected close") 324 } 325 if event.Type != watch.Added { 326 t.Fatalf("expected Added event, got %#v", event) 327 } 328 if event.Object.(*corev1.Secret).ResourceVersion != "123" { 329 t.Fatalf("expected added Secret with rv=123, got %#v", event.Object) 330 } 331 case <-time.After(time.Second * 10): 332 t.Fatal("timeout") 333 } 334 335 // Expect secret delete because the relist was missing the secret 336 select { 337 case event, ok := <-w.ResultChan(): 338 if !ok { 339 t.Fatal("unexpected close") 340 } 341 if event.Type != watch.Deleted { 342 t.Fatalf("expected Deleted event, got %#v", event) 343 } 344 if event.Object.(*corev1.Secret).ResourceVersion != "123" { 345 t.Fatalf("expected deleted Secret with rv=123, got %#v", event.Object) 346 } 347 case <-time.After(time.Second * 10): 348 t.Fatal("timeout") 349 } 350 351 w.Stop() 352 select { 353 case <-done: 354 case <-time.After(time.Second * 10): 355 t.Fatal("timeout") 356 } 357 358 if listCalls < 2 { 359 t.Fatalf("expected at least 2 list calls, got %d", listCalls) 360 } 361 if watchCalls < 1 { 362 t.Fatalf("expected at least 1 watch call, got %d", watchCalls) 363 } 364 }