k8s.io/apiserver@v0.29.3/pkg/storage/testing/utils.go (about) 1 /* 2 Copyright 2015 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 "bytes" 21 "context" 22 "fmt" 23 "path" 24 "reflect" 25 "sync" 26 "sync/atomic" 27 "testing" 28 "time" 29 30 "github.com/google/go-cmp/cmp" 31 "k8s.io/apimachinery/pkg/api/meta" 32 v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 33 "k8s.io/apimachinery/pkg/runtime" 34 "k8s.io/apimachinery/pkg/util/wait" 35 "k8s.io/apimachinery/pkg/watch" 36 "k8s.io/apiserver/pkg/apis/example" 37 "k8s.io/apiserver/pkg/storage" 38 "k8s.io/apiserver/pkg/storage/value" 39 ) 40 41 // CreateObjList will create a list from the array of objects. 42 func CreateObjList(prefix string, helper storage.Interface, items []runtime.Object) error { 43 for i := range items { 44 obj := items[i] 45 meta, err := meta.Accessor(obj) 46 if err != nil { 47 return err 48 } 49 err = helper.Create(context.Background(), path.Join(prefix, meta.GetName()), obj, obj, 0) 50 if err != nil { 51 return err 52 } 53 items[i] = obj 54 } 55 return nil 56 } 57 58 // CreateList will properly create a list using the storage interface. 59 func CreateList(prefix string, helper storage.Interface, list runtime.Object) error { 60 items, err := meta.ExtractList(list) 61 if err != nil { 62 return err 63 } 64 err = CreateObjList(prefix, helper, items) 65 if err != nil { 66 return err 67 } 68 return meta.SetList(list, items) 69 } 70 71 // DeepEqualSafePodSpec returns an example.PodSpec safe for deep-equal operations. 72 func DeepEqualSafePodSpec() example.PodSpec { 73 grace := int64(30) 74 return example.PodSpec{ 75 RestartPolicy: "Always", 76 TerminationGracePeriodSeconds: &grace, 77 SchedulerName: "default-scheduler", 78 } 79 } 80 81 func computePodKey(obj *example.Pod) string { 82 return fmt.Sprintf("/pods/%s/%s", obj.Namespace, obj.Name) 83 } 84 85 // testPropagateStore helps propagates store with objects, automates key generation, and returns 86 // keys and stored objects. 87 func testPropagateStore(ctx context.Context, t *testing.T, store storage.Interface, obj *example.Pod) (string, *example.Pod) { 88 // Setup store with a key and grab the output for returning. 89 key := computePodKey(obj) 90 91 // Setup store with the specified key and grab the output for returning. 92 err := store.Delete(ctx, key, &example.Pod{}, nil, storage.ValidateAllObjectFunc, nil) 93 if err != nil && !storage.IsNotFound(err) { 94 t.Fatalf("Cleanup failed: %v", err) 95 } 96 setOutput := &example.Pod{} 97 if err := store.Create(ctx, key, obj, setOutput, 0); err != nil { 98 t.Fatalf("Set failed: %v", err) 99 } 100 return key, setOutput 101 } 102 103 func expectNoDiff(t *testing.T, msg string, expected, actual interface{}) { 104 t.Helper() 105 if !reflect.DeepEqual(expected, actual) { 106 if diff := cmp.Diff(expected, actual); diff != "" { 107 t.Errorf("%s: %s", msg, diff) 108 } else { 109 t.Errorf("%s:\nexpected: %#v\ngot: %#v", msg, expected, actual) 110 } 111 } 112 } 113 114 func ExpectContains(t *testing.T, msg string, expectedList []interface{}, got interface{}) { 115 t.Helper() 116 for _, expected := range expectedList { 117 if reflect.DeepEqual(expected, got) { 118 return 119 } 120 } 121 if len(expectedList) == 0 { 122 t.Errorf("%s: empty expectedList", msg) 123 return 124 } 125 if diff := cmp.Diff(expectedList[0], got); diff != "" { 126 t.Errorf("%s: differs from all items, with first: %s", msg, diff) 127 } else { 128 t.Errorf("%s: differs from all items, first: %#v\ngot: %#v", msg, expectedList[0], got) 129 } 130 } 131 132 const dummyPrefix = "adapter" 133 134 func encodeContinueOrDie(key string, resourceVersion int64) string { 135 token, err := storage.EncodeContinue(dummyPrefix+key, dummyPrefix, resourceVersion) 136 if err != nil { 137 panic(err) 138 } 139 return token 140 } 141 142 func testCheckEventType(t *testing.T, w watch.Interface, expectEventType watch.EventType) { 143 select { 144 case res := <-w.ResultChan(): 145 if res.Type != expectEventType { 146 t.Errorf("event type want=%v, get=%v", expectEventType, res.Type) 147 } 148 case <-time.After(wait.ForeverTestTimeout): 149 t.Errorf("time out after waiting %v on ResultChan", wait.ForeverTestTimeout) 150 } 151 } 152 153 func testCheckResult(t *testing.T, w watch.Interface, expectEvent watch.Event) { 154 testCheckResultFunc(t, w, func(actualEvent watch.Event) { 155 expectNoDiff(t, "incorrect event", expectEvent, actualEvent) 156 }) 157 } 158 159 func testCheckResultFunc(t *testing.T, w watch.Interface, check func(actualEvent watch.Event)) { 160 select { 161 case res := <-w.ResultChan(): 162 obj := res.Object 163 if co, ok := obj.(runtime.CacheableObject); ok { 164 res.Object = co.GetObject() 165 } 166 check(res) 167 case <-time.After(wait.ForeverTestTimeout): 168 t.Errorf("time out after waiting %v on ResultChan", wait.ForeverTestTimeout) 169 } 170 } 171 172 func testCheckStop(t *testing.T, w watch.Interface) { 173 select { 174 case e, ok := <-w.ResultChan(): 175 if ok { 176 var obj string 177 switch e.Object.(type) { 178 case *example.Pod: 179 obj = e.Object.(*example.Pod).Name 180 case *v1.Status: 181 obj = e.Object.(*v1.Status).Message 182 } 183 t.Errorf("ResultChan should have been closed. Event: %s. Object: %s", e.Type, obj) 184 } 185 case <-time.After(wait.ForeverTestTimeout): 186 t.Errorf("time out after waiting 1s on ResultChan") 187 } 188 } 189 190 func testCheckResultsInStrictOrder(t *testing.T, w watch.Interface, expectedEvents []watch.Event) { 191 for _, expectedEvent := range expectedEvents { 192 testCheckResult(t, w, expectedEvent) 193 } 194 } 195 196 func testCheckResultsInRandomOrder(t *testing.T, w watch.Interface, expectedEvents []watch.Event) { 197 for range expectedEvents { 198 testCheckResultFunc(t, w, func(actualEvent watch.Event) { 199 ExpectContains(t, "unexpected event", toInterfaceSlice(expectedEvents), actualEvent) 200 }) 201 } 202 } 203 204 func testCheckNoMoreResults(t *testing.T, w watch.Interface) { 205 select { 206 case e := <-w.ResultChan(): 207 t.Errorf("Unexpected: %#v event received, expected no events", e) 208 case <-time.After(time.Second): 209 return 210 } 211 } 212 213 func toInterfaceSlice[T any](s []T) []interface{} { 214 result := make([]interface{}, len(s)) 215 for i, v := range s { 216 result[i] = v 217 } 218 return result 219 } 220 221 // resourceVersionNotOlderThan returns a function to validate resource versions. Resource versions 222 // referring to points in logical time before the sentinel generate an error. All logical times as 223 // new as the sentinel or newer generate no error. 224 func resourceVersionNotOlderThan(sentinel string) func(string) error { 225 return func(resourceVersion string) error { 226 objectVersioner := storage.APIObjectVersioner{} 227 actualRV, err := objectVersioner.ParseResourceVersion(resourceVersion) 228 if err != nil { 229 return err 230 } 231 expectedRV, err := objectVersioner.ParseResourceVersion(sentinel) 232 if err != nil { 233 return err 234 } 235 if actualRV < expectedRV { 236 return fmt.Errorf("expected a resourceVersion no smaller than than %d, but got %d", expectedRV, actualRV) 237 } 238 return nil 239 } 240 } 241 242 // StorageInjectingListErrors injects a dummy error for first N GetList calls. 243 type StorageInjectingListErrors struct { 244 storage.Interface 245 246 lock sync.Mutex 247 Errors int 248 } 249 250 func (s *StorageInjectingListErrors) GetList(ctx context.Context, key string, opts storage.ListOptions, listObj runtime.Object) error { 251 err := func() error { 252 s.lock.Lock() 253 defer s.lock.Unlock() 254 if s.Errors > 0 { 255 s.Errors-- 256 return fmt.Errorf("injected error") 257 } 258 return nil 259 }() 260 if err != nil { 261 return err 262 } 263 return s.Interface.GetList(ctx, key, opts, listObj) 264 } 265 266 func (s *StorageInjectingListErrors) ErrorsConsumed() (bool, error) { 267 s.lock.Lock() 268 defer s.lock.Unlock() 269 return s.Errors == 0, nil 270 } 271 272 // PrefixTransformer adds and verifies that all data has the correct prefix on its way in and out. 273 type PrefixTransformer struct { 274 prefix []byte 275 stale bool 276 err error 277 reads uint64 278 } 279 280 func NewPrefixTransformer(prefix []byte, stale bool) *PrefixTransformer { 281 return &PrefixTransformer{ 282 prefix: prefix, 283 stale: stale, 284 } 285 } 286 287 func (p *PrefixTransformer) TransformFromStorage(ctx context.Context, data []byte, dataCtx value.Context) ([]byte, bool, error) { 288 atomic.AddUint64(&p.reads, 1) 289 if dataCtx == nil { 290 panic("no context provided") 291 } 292 if !bytes.HasPrefix(data, p.prefix) { 293 return nil, false, fmt.Errorf("value does not have expected prefix %q: %s,", p.prefix, string(data)) 294 } 295 return bytes.TrimPrefix(data, p.prefix), p.stale, p.err 296 } 297 func (p *PrefixTransformer) TransformToStorage(ctx context.Context, data []byte, dataCtx value.Context) ([]byte, error) { 298 if dataCtx == nil { 299 panic("no context provided") 300 } 301 if len(data) > 0 { 302 return append(append([]byte{}, p.prefix...), data...), p.err 303 } 304 return data, p.err 305 } 306 307 func (p *PrefixTransformer) GetReadsAndReset() uint64 { 308 return atomic.SwapUint64(&p.reads, 0) 309 } 310 311 // reproducingTransformer is a custom test-only transformer used purely 312 // for testing consistency. 313 // It allows for creating predefined objects on TransformFromStorage operations, 314 // which allows for precise in time injection of new objects in the middle of 315 // read operations. 316 type reproducingTransformer struct { 317 wrapped value.Transformer 318 store storage.Interface 319 320 index uint32 321 nextObject func(uint32) (string, *example.Pod) 322 } 323 324 func (rt *reproducingTransformer) TransformFromStorage(ctx context.Context, data []byte, dataCtx value.Context) ([]byte, bool, error) { 325 if err := rt.createObject(ctx); err != nil { 326 return nil, false, err 327 } 328 return rt.wrapped.TransformFromStorage(ctx, data, dataCtx) 329 } 330 331 func (rt *reproducingTransformer) TransformToStorage(ctx context.Context, data []byte, dataCtx value.Context) ([]byte, error) { 332 return rt.wrapped.TransformToStorage(ctx, data, dataCtx) 333 } 334 335 func (rt *reproducingTransformer) createObject(ctx context.Context) error { 336 key, obj := rt.nextObject(atomic.AddUint32(&rt.index, 1)) 337 out := &example.Pod{} 338 return rt.store.Create(ctx, key, obj, out, 0) 339 } 340 341 // failingTransformer is a custom test-only transformer that always returns 342 // an error on transforming data from storage. 343 type failingTransformer struct { 344 } 345 346 func (ft *failingTransformer) TransformFromStorage(ctx context.Context, data []byte, dataCtx value.Context) ([]byte, bool, error) { 347 return nil, false, fmt.Errorf("failed transformation") 348 } 349 350 func (ft *failingTransformer) TransformToStorage(ctx context.Context, data []byte, dataCtx value.Context) ([]byte, error) { 351 return data, nil 352 } 353 354 type sortablePodList []example.Pod 355 356 func (s sortablePodList) Len() int { 357 return len(s) 358 } 359 360 func (s sortablePodList) Less(i, j int) bool { 361 return computePodKey(&s[i]) < computePodKey(&s[j]) 362 } 363 364 func (s sortablePodList) Swap(i, j int) { 365 s[i], s[j] = s[j], s[i] 366 }