k8s.io/apiserver@v0.29.3/pkg/storage/etcd3/watcher_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 etcd3 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "sync" 24 "testing" 25 "time" 26 27 "github.com/google/go-cmp/cmp" 28 clientv3 "go.etcd.io/etcd/client/v3" 29 30 apierrors "k8s.io/apimachinery/pkg/api/errors" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 "k8s.io/apimachinery/pkg/fields" 33 "k8s.io/apimachinery/pkg/labels" 34 "k8s.io/apimachinery/pkg/watch" 35 "k8s.io/apiserver/pkg/apis/example" 36 "k8s.io/apiserver/pkg/features" 37 "k8s.io/apiserver/pkg/storage" 38 "k8s.io/apiserver/pkg/storage/etcd3/testserver" 39 storagetesting "k8s.io/apiserver/pkg/storage/testing" 40 utilfeature "k8s.io/apiserver/pkg/util/feature" 41 featuregatetesting "k8s.io/component-base/featuregate/testing" 42 "k8s.io/utils/ptr" 43 ) 44 45 func TestWatch(t *testing.T) { 46 ctx, store, _ := testSetup(t) 47 storagetesting.RunTestWatch(ctx, t, store) 48 } 49 50 func TestClusterScopedWatch(t *testing.T) { 51 ctx, store, _ := testSetup(t) 52 storagetesting.RunTestClusterScopedWatch(ctx, t, store) 53 } 54 55 func TestNamespaceScopedWatch(t *testing.T) { 56 ctx, store, _ := testSetup(t) 57 storagetesting.RunTestNamespaceScopedWatch(ctx, t, store) 58 } 59 60 func TestDeleteTriggerWatch(t *testing.T) { 61 ctx, store, _ := testSetup(t) 62 storagetesting.RunTestDeleteTriggerWatch(ctx, t, store) 63 } 64 65 func TestWatchFromZero(t *testing.T) { 66 ctx, store, client := testSetup(t) 67 storagetesting.RunTestWatchFromZero(ctx, t, store, compactStorage(client)) 68 } 69 70 // TestWatchFromNonZero tests that 71 // - watch from non-0 should just watch changes after given version 72 func TestWatchFromNoneZero(t *testing.T) { 73 ctx, store, _ := testSetup(t) 74 storagetesting.RunTestWatchFromNonZero(ctx, t, store) 75 } 76 77 func TestDelayedWatchDelivery(t *testing.T) { 78 ctx, store, _ := testSetup(t) 79 storagetesting.RunTestDelayedWatchDelivery(ctx, t, store) 80 } 81 82 func TestWatchError(t *testing.T) { 83 ctx, store, _ := testSetup(t) 84 storagetesting.RunTestWatchError(ctx, t, &storeWithPrefixTransformer{store}) 85 } 86 87 func TestWatchContextCancel(t *testing.T) { 88 ctx, store, _ := testSetup(t) 89 storagetesting.RunTestWatchContextCancel(ctx, t, store) 90 } 91 92 func TestWatcherTimeout(t *testing.T) { 93 ctx, store, _ := testSetup(t) 94 storagetesting.RunTestWatcherTimeout(ctx, t, store) 95 } 96 97 func TestWatchDeleteEventObjectHaveLatestRV(t *testing.T) { 98 ctx, store, _ := testSetup(t) 99 storagetesting.RunTestWatchDeleteEventObjectHaveLatestRV(ctx, t, store) 100 } 101 102 func TestWatchInitializationSignal(t *testing.T) { 103 ctx, store, _ := testSetup(t) 104 storagetesting.RunTestWatchInitializationSignal(ctx, t, store) 105 } 106 107 func TestProgressNotify(t *testing.T) { 108 clusterConfig := testserver.NewTestConfig(t) 109 clusterConfig.ExperimentalWatchProgressNotifyInterval = time.Second 110 ctx, store, _ := testSetup(t, withClientConfig(clusterConfig)) 111 112 storagetesting.RunOptionalTestProgressNotify(ctx, t, store) 113 } 114 115 // TestWatchDispatchBookmarkEvents makes sure that 116 // setting allowWatchBookmarks query param against 117 // etcd implementation doesn't have any effect. 118 func TestWatchDispatchBookmarkEvents(t *testing.T) { 119 clusterConfig := testserver.NewTestConfig(t) 120 clusterConfig.ExperimentalWatchProgressNotifyInterval = time.Second 121 ctx, store, _ := testSetup(t, withClientConfig(clusterConfig)) 122 123 storagetesting.RunTestWatchDispatchBookmarkEvents(ctx, t, store, false) 124 } 125 126 func TestSendInitialEventsBackwardCompatibility(t *testing.T) { 127 ctx, store, _ := testSetup(t) 128 storagetesting.RunSendInitialEventsBackwardCompatibility(ctx, t, store) 129 } 130 131 func TestEtcdWatchSemantics(t *testing.T) { 132 ctx, store, _ := testSetup(t) 133 storagetesting.RunWatchSemantics(ctx, t, store) 134 } 135 136 func TestEtcdWatchSemanticInitialEventsExtended(t *testing.T) { 137 ctx, store, _ := testSetup(t) 138 storagetesting.RunWatchSemanticInitialEventsExtended(ctx, t, store) 139 } 140 141 // ======================================================================= 142 // Implementation-specific tests are following. 143 // The following tests are exercising the details of the implementation 144 // not the actual user-facing contract of storage interface. 145 // As such, they may focus e.g. on non-functional aspects like performance 146 // impact. 147 // ======================================================================= 148 149 func TestWatchErrResultNotBlockAfterCancel(t *testing.T) { 150 origCtx, store, _ := testSetup(t) 151 ctx, cancel := context.WithCancel(origCtx) 152 w := store.watcher.createWatchChan(ctx, "/abc", 0, false, false, storage.Everything) 153 // make resultChan and errChan blocking to ensure ordering. 154 w.resultChan = make(chan watch.Event) 155 w.errChan = make(chan error) 156 // The event flow goes like: 157 // - first we send an error, it should block on resultChan. 158 // - Then we cancel ctx. The blocking on resultChan should be freed up 159 // and run() goroutine should return. 160 var wg sync.WaitGroup 161 wg.Add(1) 162 go func() { 163 w.run(false, true) 164 wg.Done() 165 }() 166 w.errChan <- fmt.Errorf("some error") 167 cancel() 168 wg.Wait() 169 } 170 171 // TestWatchErrorIncorrectConfiguration checks if an error 172 // will be returned when the storage hasn't been properly 173 // initialised for watch requests 174 func TestWatchErrorIncorrectConfiguration(t *testing.T) { 175 scenarios := []struct { 176 name string 177 setupFn func(opts *setupOptions) 178 requestOpts storage.ListOptions 179 enableWatchList bool 180 expectedErr error 181 }{ 182 { 183 name: "no newFunc provided", 184 setupFn: func(opts *setupOptions) { opts.newFunc = nil }, 185 requestOpts: storage.ListOptions{ProgressNotify: true}, 186 expectedErr: apierrors.NewInternalError(errors.New("progressNotify for watch is unsupported by the etcd storage because no newFunc was provided")), 187 }, 188 } 189 for _, scenario := range scenarios { 190 t.Run(scenario.name, func(t *testing.T) { 191 if scenario.enableWatchList { 192 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.WatchList, true)() 193 } 194 origCtx, store, _ := testSetup(t, scenario.setupFn) 195 ctx, cancel := context.WithCancel(origCtx) 196 defer cancel() 197 198 w, err := store.watcher.Watch(ctx, "/abc", 0, scenario.requestOpts) 199 if err == nil { 200 t.Fatalf("expected an error but got none") 201 } 202 if w != nil { 203 t.Fatalf("didn't expect a watcher because the test assumes incorrect store initialisation") 204 } 205 if err.Error() != scenario.expectedErr.Error() { 206 t.Fatalf("unexpected err = %v, expected = %v", err, scenario.expectedErr) 207 } 208 }) 209 } 210 } 211 212 func TestTooLargeResourceVersionErrorForWatchList(t *testing.T) { 213 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.WatchList, true)() 214 origCtx, store, _ := testSetup(t) 215 ctx, cancel := context.WithCancel(origCtx) 216 defer cancel() 217 requestOpts := storage.ListOptions{ 218 SendInitialEvents: ptr.To(true), 219 Recursive: true, 220 Predicate: storage.SelectionPredicate{ 221 Field: fields.Everything(), 222 Label: labels.Everything(), 223 AllowWatchBookmarks: true, 224 }, 225 } 226 var expectedErr *apierrors.StatusError 227 if !errors.As(storage.NewTooLargeResourceVersionError(uint64(102), 1, 0), &expectedErr) { 228 t.Fatalf("Unable to convert NewTooLargeResourceVersionError to apierrors.StatusError") 229 } 230 231 w, err := store.watcher.Watch(ctx, "/abc", int64(102), requestOpts) 232 if err != nil { 233 t.Fatal(err) 234 } 235 defer w.Stop() 236 237 actualEvent := <-w.ResultChan() 238 if actualEvent.Type != watch.Error { 239 t.Fatalf("Unexpected type of the event: %v, expected: %v", actualEvent.Type, watch.Error) 240 } 241 actualErr, ok := actualEvent.Object.(*metav1.Status) 242 if !ok { 243 t.Fatalf("Expected *apierrors.StatusError, got: %#v", actualEvent.Object) 244 } 245 246 if actualErr.Details.RetryAfterSeconds <= 0 { 247 t.Fatalf("RetryAfterSeconds must be > 0, actual value: %v", actualErr.Details.RetryAfterSeconds) 248 } 249 // rewrite the Details as it contains retry seconds 250 // and validate the whole struct 251 expectedErr.ErrStatus.Details = actualErr.Details 252 if diff := cmp.Diff(*actualErr, expectedErr.ErrStatus); diff != "" { 253 t.Fatalf("Unexpected error returned, diff: %v", diff) 254 } 255 } 256 257 func TestWatchChanSync(t *testing.T) { 258 testCases := []struct { 259 name string 260 watchKey string 261 watcherMaxLimit int64 262 expectEventCount int 263 expectGetCount int 264 }{ 265 { 266 name: "None of the current objects match watchKey: sync with empty page", 267 watchKey: "/pods/test/", 268 watcherMaxLimit: 1, 269 expectGetCount: 1, 270 }, 271 { 272 name: "The number of current objects is less than defaultWatcherMaxLimit: sync with one page", 273 watchKey: "/pods/", 274 watcherMaxLimit: 3, 275 expectEventCount: 2, 276 expectGetCount: 1, 277 }, 278 { 279 name: "a new item added to etcd before returning a second page is not returned: sync with two page", 280 watchKey: "/pods/", 281 watcherMaxLimit: 1, 282 expectEventCount: 2, 283 expectGetCount: 2, 284 }, 285 } 286 287 for _, testCase := range testCases { 288 t.Run(testCase.name, func(t *testing.T) { 289 defaultWatcherMaxLimit = testCase.watcherMaxLimit 290 291 origCtx, store, _ := testSetup(t) 292 initList, err := initStoreData(origCtx, store) 293 if err != nil { 294 t.Fatal(err) 295 } 296 297 kvWrapper := newEtcdClientKVWrapper(store.client.KV) 298 kvWrapper.getReactors = append(kvWrapper.getReactors, func() { 299 barThird := &example.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "third", Name: "bar"}} 300 podKey := fmt.Sprintf("/pods/%s/%s", barThird.Namespace, barThird.Name) 301 storedObj := &example.Pod{} 302 303 err := store.Create(context.Background(), podKey, barThird, storedObj, 0) 304 if err != nil { 305 t.Errorf("failed to create object: %v", err) 306 } 307 }) 308 309 store.client.KV = kvWrapper 310 311 w := store.watcher.createWatchChan( 312 origCtx, 313 testCase.watchKey, 314 0, 315 true, 316 false, 317 storage.Everything) 318 319 err = w.sync() 320 if err != nil { 321 t.Fatal(err) 322 } 323 324 // close incomingEventChan so we can read incomingEventChan non-blocking 325 close(w.incomingEventChan) 326 327 eventsReceived := 0 328 for event := range w.incomingEventChan { 329 eventsReceived++ 330 storagetesting.ExpectContains(t, "incorrect list pods", initList, event.key) 331 } 332 333 if eventsReceived != testCase.expectEventCount { 334 t.Errorf("Unexpected number of events: %v, expected: %v", eventsReceived, testCase.expectEventCount) 335 } 336 337 if kvWrapper.getCallCounter != testCase.expectGetCount { 338 t.Errorf("Unexpected called times of client.KV.Get() : %v, expected: %v", kvWrapper.getCallCounter, testCase.expectGetCount) 339 } 340 }) 341 } 342 } 343 344 // NOTE: it's not thread-safe 345 type etcdClientKVWrapper struct { 346 clientv3.KV 347 // keeps track of the number of times Get method is called 348 getCallCounter int 349 // getReactors is called after the etcd KV's get function is executed. 350 getReactors []func() 351 } 352 353 func newEtcdClientKVWrapper(kv clientv3.KV) *etcdClientKVWrapper { 354 return &etcdClientKVWrapper{ 355 KV: kv, 356 getCallCounter: 0, 357 } 358 } 359 360 func (ecw *etcdClientKVWrapper) Get(ctx context.Context, key string, opts ...clientv3.OpOption) (*clientv3.GetResponse, error) { 361 resp, err := ecw.KV.Get(ctx, key, opts...) 362 ecw.getCallCounter++ 363 if err != nil { 364 return nil, err 365 } 366 367 if len(ecw.getReactors) > 0 { 368 reactor := ecw.getReactors[0] 369 ecw.getReactors = ecw.getReactors[1:] 370 reactor() 371 } 372 373 return resp, nil 374 } 375 376 func initStoreData(ctx context.Context, store storage.Interface) ([]interface{}, error) { 377 barFirst := &example.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "first", Name: "bar"}} 378 barSecond := &example.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "second", Name: "bar"}} 379 380 preset := []struct { 381 key string 382 obj *example.Pod 383 storedObj *example.Pod 384 }{ 385 { 386 key: fmt.Sprintf("/pods/%s/%s", barFirst.Namespace, barFirst.Name), 387 obj: barFirst, 388 }, 389 { 390 key: fmt.Sprintf("/pods/%s/%s", barSecond.Namespace, barSecond.Name), 391 obj: barSecond, 392 }, 393 } 394 395 for i, ps := range preset { 396 preset[i].storedObj = &example.Pod{} 397 err := store.Create(ctx, ps.key, ps.obj, preset[i].storedObj, 0) 398 if err != nil { 399 return nil, fmt.Errorf("failed to create object: %w", err) 400 } 401 } 402 403 var created []interface{} 404 for _, item := range preset { 405 created = append(created, item.key) 406 } 407 return created, nil 408 }