k8s.io/client-go@v0.22.2/tools/watch/until_test.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 watch 18 19 import ( 20 "context" 21 "errors" 22 "reflect" 23 "strings" 24 "testing" 25 "time" 26 27 corev1 "k8s.io/api/core/v1" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/runtime" 30 "k8s.io/apimachinery/pkg/runtime/schema" 31 "k8s.io/apimachinery/pkg/util/wait" 32 "k8s.io/apimachinery/pkg/watch" 33 fakeclient "k8s.io/client-go/kubernetes/fake" 34 "k8s.io/client-go/tools/cache" 35 ) 36 37 type fakePod struct { 38 } 39 40 func (obj *fakePod) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } 41 func (obj *fakePod) DeepCopyObject() runtime.Object { panic("DeepCopyObject not supported by fakePod") } 42 43 func TestUntil(t *testing.T) { 44 fw := watch.NewFake() 45 go func() { 46 var obj *fakePod 47 fw.Add(obj) 48 fw.Modify(obj) 49 }() 50 conditions := []ConditionFunc{ 51 func(event watch.Event) (bool, error) { return event.Type == watch.Added, nil }, 52 func(event watch.Event) (bool, error) { return event.Type == watch.Modified, nil }, 53 } 54 55 ctx, cancel := context.WithTimeout(context.Background(), time.Minute) 56 defer cancel() 57 58 lastEvent, err := UntilWithoutRetry(ctx, fw, conditions...) 59 if err != nil { 60 t.Fatalf("expected nil error, got %#v", err) 61 } 62 if lastEvent == nil { 63 t.Fatal("expected an event") 64 } 65 if lastEvent.Type != watch.Modified { 66 t.Fatalf("expected MODIFIED event type, got %v", lastEvent.Type) 67 } 68 if got, isPod := lastEvent.Object.(*fakePod); !isPod { 69 t.Fatalf("expected a pod event, got %#v", got) 70 } 71 } 72 73 func TestUntilMultipleConditions(t *testing.T) { 74 fw := watch.NewFake() 75 go func() { 76 var obj *fakePod 77 fw.Add(obj) 78 }() 79 conditions := []ConditionFunc{ 80 func(event watch.Event) (bool, error) { return event.Type == watch.Added, nil }, 81 func(event watch.Event) (bool, error) { return event.Type == watch.Added, nil }, 82 } 83 84 ctx, cancel := context.WithTimeout(context.Background(), time.Minute) 85 defer cancel() 86 87 lastEvent, err := UntilWithoutRetry(ctx, fw, conditions...) 88 if err != nil { 89 t.Fatalf("expected nil error, got %#v", err) 90 } 91 if lastEvent == nil { 92 t.Fatal("expected an event") 93 } 94 if lastEvent.Type != watch.Added { 95 t.Fatalf("expected MODIFIED event type, got %v", lastEvent.Type) 96 } 97 if got, isPod := lastEvent.Object.(*fakePod); !isPod { 98 t.Fatalf("expected a pod event, got %#v", got) 99 } 100 } 101 102 func TestUntilMultipleConditionsFail(t *testing.T) { 103 fw := watch.NewFake() 104 go func() { 105 var obj *fakePod 106 fw.Add(obj) 107 }() 108 conditions := []ConditionFunc{ 109 func(event watch.Event) (bool, error) { return event.Type == watch.Added, nil }, 110 func(event watch.Event) (bool, error) { return event.Type == watch.Added, nil }, 111 func(event watch.Event) (bool, error) { return event.Type == watch.Deleted, nil }, 112 } 113 114 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 115 defer cancel() 116 117 lastEvent, err := UntilWithoutRetry(ctx, fw, conditions...) 118 if err != wait.ErrWaitTimeout { 119 t.Fatalf("expected ErrWaitTimeout error, got %#v", err) 120 } 121 if lastEvent == nil { 122 t.Fatal("expected an event") 123 } 124 if lastEvent.Type != watch.Added { 125 t.Fatalf("expected ADDED event type, got %v", lastEvent.Type) 126 } 127 if got, isPod := lastEvent.Object.(*fakePod); !isPod { 128 t.Fatalf("expected a pod event, got %#v", got) 129 } 130 } 131 132 func TestUntilTimeout(t *testing.T) { 133 fw := watch.NewFake() 134 go func() { 135 var obj *fakePod 136 fw.Add(obj) 137 fw.Modify(obj) 138 }() 139 conditions := []ConditionFunc{ 140 func(event watch.Event) (bool, error) { 141 return event.Type == watch.Added, nil 142 }, 143 func(event watch.Event) (bool, error) { 144 return event.Type == watch.Modified, nil 145 }, 146 } 147 148 lastEvent, err := UntilWithoutRetry(context.Background(), fw, conditions...) 149 if err != nil { 150 t.Fatalf("expected nil error, got %#v", err) 151 } 152 if lastEvent == nil { 153 t.Fatal("expected an event") 154 } 155 if lastEvent.Type != watch.Modified { 156 t.Fatalf("expected MODIFIED event type, got %v", lastEvent.Type) 157 } 158 if got, isPod := lastEvent.Object.(*fakePod); !isPod { 159 t.Fatalf("expected a pod event, got %#v", got) 160 } 161 } 162 163 func TestUntilErrorCondition(t *testing.T) { 164 fw := watch.NewFake() 165 go func() { 166 var obj *fakePod 167 fw.Add(obj) 168 }() 169 expected := "something bad" 170 conditions := []ConditionFunc{ 171 func(event watch.Event) (bool, error) { return event.Type == watch.Added, nil }, 172 func(event watch.Event) (bool, error) { return false, errors.New(expected) }, 173 } 174 175 ctx, cancel := context.WithTimeout(context.Background(), time.Minute) 176 defer cancel() 177 178 _, err := UntilWithoutRetry(ctx, fw, conditions...) 179 if err == nil { 180 t.Fatal("expected an error") 181 } 182 if !strings.Contains(err.Error(), expected) { 183 t.Fatalf("expected %q in error string, got %q", expected, err.Error()) 184 } 185 } 186 187 func TestUntilWithSync(t *testing.T) { 188 // FIXME: test preconditions 189 tt := []struct { 190 name string 191 lw *cache.ListWatch 192 preconditionFunc PreconditionFunc 193 conditionFunc ConditionFunc 194 expectedErr error 195 expectedEvent *watch.Event 196 }{ 197 { 198 name: "doesn't wait for sync with no precondition", 199 lw: &cache.ListWatch{ 200 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 201 select {} 202 }, 203 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 204 select {} 205 }, 206 }, 207 preconditionFunc: nil, 208 conditionFunc: func(e watch.Event) (bool, error) { 209 return true, nil 210 }, 211 expectedErr: errors.New("timed out waiting for the condition"), 212 expectedEvent: nil, 213 }, 214 { 215 name: "waits indefinitely with precondition if it can't sync", 216 lw: &cache.ListWatch{ 217 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 218 select {} 219 }, 220 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 221 select {} 222 }, 223 }, 224 preconditionFunc: func(store cache.Store) (bool, error) { 225 return true, nil 226 }, 227 conditionFunc: func(e watch.Event) (bool, error) { 228 return true, nil 229 }, 230 expectedErr: errors.New("UntilWithSync: unable to sync caches: context deadline exceeded"), 231 expectedEvent: nil, 232 }, 233 { 234 name: "precondition can stop the loop", 235 lw: func() *cache.ListWatch { 236 fakeclient := fakeclient.NewSimpleClientset(&corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "first"}}) 237 238 return &cache.ListWatch{ 239 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 240 return fakeclient.CoreV1().Secrets("").List(context.TODO(), options) 241 }, 242 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 243 return fakeclient.CoreV1().Secrets("").Watch(context.TODO(), options) 244 }, 245 } 246 }(), 247 preconditionFunc: func(store cache.Store) (bool, error) { 248 _, exists, err := store.Get(&metav1.ObjectMeta{Namespace: "", Name: "first"}) 249 if err != nil { 250 return true, err 251 } 252 if exists { 253 return true, nil 254 } 255 return false, nil 256 }, 257 conditionFunc: func(e watch.Event) (bool, error) { 258 return true, errors.New("should never reach this") 259 }, 260 expectedErr: nil, 261 expectedEvent: nil, 262 }, 263 { 264 name: "precondition lets it proceed to regular condition", 265 lw: func() *cache.ListWatch { 266 fakeclient := fakeclient.NewSimpleClientset(&corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "first"}}) 267 268 return &cache.ListWatch{ 269 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 270 return fakeclient.CoreV1().Secrets("").List(context.TODO(), options) 271 }, 272 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 273 return fakeclient.CoreV1().Secrets("").Watch(context.TODO(), options) 274 }, 275 } 276 }(), 277 preconditionFunc: func(store cache.Store) (bool, error) { 278 return false, nil 279 }, 280 conditionFunc: func(e watch.Event) (bool, error) { 281 if e.Type == watch.Added { 282 return true, nil 283 } 284 panic("no other events are expected") 285 }, 286 expectedErr: nil, 287 expectedEvent: &watch.Event{Type: watch.Added, Object: &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "first"}}}, 288 }, 289 } 290 291 for _, tc := range tt { 292 t.Run(tc.name, func(t *testing.T) { 293 // Informer waits for caches to sync by polling in 100ms intervals, 294 // timeout needs to be reasonably higher 295 ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) 296 defer cancel() 297 298 event, err := UntilWithSync(ctx, tc.lw, &corev1.Secret{}, tc.preconditionFunc, tc.conditionFunc) 299 300 if !reflect.DeepEqual(err, tc.expectedErr) { 301 t.Errorf("expected error %#v, got %#v", tc.expectedErr, err) 302 } 303 304 if !reflect.DeepEqual(event, tc.expectedEvent) { 305 t.Errorf("expected event %#v, got %#v", tc.expectedEvent, event) 306 } 307 }) 308 } 309 }