k8s.io/apiserver@v0.31.1/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 testCheckNoMoreResults(t *testing.T, w watch.Interface) { 197 select { 198 case e := <-w.ResultChan(): 199 t.Errorf("Unexpected: %#v event received, expected no events", e) 200 // We consciously make the timeout short here to speed up tests. 201 case <-time.After(100 * time.Millisecond): 202 return 203 } 204 } 205 206 func toInterfaceSlice[T any](s []T) []interface{} { 207 result := make([]interface{}, len(s)) 208 for i, v := range s { 209 result[i] = v 210 } 211 return result 212 } 213 214 // resourceVersionNotOlderThan returns a function to validate resource versions. Resource versions 215 // referring to points in logical time before the sentinel generate an error. All logical times as 216 // new as the sentinel or newer generate no error. 217 func resourceVersionNotOlderThan(sentinel string) func(string) error { 218 return func(resourceVersion string) error { 219 objectVersioner := storage.APIObjectVersioner{} 220 actualRV, err := objectVersioner.ParseResourceVersion(resourceVersion) 221 if err != nil { 222 return err 223 } 224 expectedRV, err := objectVersioner.ParseResourceVersion(sentinel) 225 if err != nil { 226 return err 227 } 228 if actualRV < expectedRV { 229 return fmt.Errorf("expected a resourceVersion no smaller than than %d, but got %d", expectedRV, actualRV) 230 } 231 return nil 232 } 233 } 234 235 // StorageInjectingListErrors injects a dummy error for first N GetList calls. 236 type StorageInjectingListErrors struct { 237 storage.Interface 238 239 lock sync.Mutex 240 Errors int 241 } 242 243 func (s *StorageInjectingListErrors) GetList(ctx context.Context, key string, opts storage.ListOptions, listObj runtime.Object) error { 244 err := func() error { 245 s.lock.Lock() 246 defer s.lock.Unlock() 247 if s.Errors > 0 { 248 s.Errors-- 249 return fmt.Errorf("injected error") 250 } 251 return nil 252 }() 253 if err != nil { 254 return err 255 } 256 return s.Interface.GetList(ctx, key, opts, listObj) 257 } 258 259 func (s *StorageInjectingListErrors) ErrorsConsumed() (bool, error) { 260 s.lock.Lock() 261 defer s.lock.Unlock() 262 return s.Errors == 0, nil 263 } 264 265 // PrefixTransformer adds and verifies that all data has the correct prefix on its way in and out. 266 type PrefixTransformer struct { 267 prefix []byte 268 stale bool 269 err error 270 reads uint64 271 } 272 273 func NewPrefixTransformer(prefix []byte, stale bool) *PrefixTransformer { 274 return &PrefixTransformer{ 275 prefix: prefix, 276 stale: stale, 277 } 278 } 279 280 func (p *PrefixTransformer) TransformFromStorage(ctx context.Context, data []byte, dataCtx value.Context) ([]byte, bool, error) { 281 atomic.AddUint64(&p.reads, 1) 282 if dataCtx == nil { 283 panic("no context provided") 284 } 285 if !bytes.HasPrefix(data, p.prefix) { 286 return nil, false, fmt.Errorf("value does not have expected prefix %q: %s,", p.prefix, string(data)) 287 } 288 return bytes.TrimPrefix(data, p.prefix), p.stale, p.err 289 } 290 func (p *PrefixTransformer) TransformToStorage(ctx context.Context, data []byte, dataCtx value.Context) ([]byte, error) { 291 if dataCtx == nil { 292 panic("no context provided") 293 } 294 if len(data) > 0 { 295 return append(append([]byte{}, p.prefix...), data...), p.err 296 } 297 return data, p.err 298 } 299 300 func (p *PrefixTransformer) GetReadsAndReset() uint64 { 301 return atomic.SwapUint64(&p.reads, 0) 302 } 303 304 // reproducingTransformer is a custom test-only transformer used purely 305 // for testing consistency. 306 // It allows for creating predefined objects on TransformFromStorage operations, 307 // which allows for precise in time injection of new objects in the middle of 308 // read operations. 309 type reproducingTransformer struct { 310 wrapped value.Transformer 311 store storage.Interface 312 313 index uint32 314 nextObject func(uint32) (string, *example.Pod) 315 } 316 317 func (rt *reproducingTransformer) TransformFromStorage(ctx context.Context, data []byte, dataCtx value.Context) ([]byte, bool, error) { 318 if err := rt.createObject(ctx); err != nil { 319 return nil, false, err 320 } 321 return rt.wrapped.TransformFromStorage(ctx, data, dataCtx) 322 } 323 324 func (rt *reproducingTransformer) TransformToStorage(ctx context.Context, data []byte, dataCtx value.Context) ([]byte, error) { 325 return rt.wrapped.TransformToStorage(ctx, data, dataCtx) 326 } 327 328 func (rt *reproducingTransformer) createObject(ctx context.Context) error { 329 key, obj := rt.nextObject(atomic.AddUint32(&rt.index, 1)) 330 out := &example.Pod{} 331 return rt.store.Create(ctx, key, obj, out, 0) 332 } 333 334 // failingTransformer is a custom test-only transformer that always returns 335 // an error on transforming data from storage. 336 type failingTransformer struct { 337 } 338 339 func (ft *failingTransformer) TransformFromStorage(ctx context.Context, data []byte, dataCtx value.Context) ([]byte, bool, error) { 340 return nil, false, fmt.Errorf("failed transformation") 341 } 342 343 func (ft *failingTransformer) TransformToStorage(ctx context.Context, data []byte, dataCtx value.Context) ([]byte, error) { 344 return data, nil 345 } 346 347 type sortablePodList []example.Pod 348 349 func (s sortablePodList) Len() int { 350 return len(s) 351 } 352 353 func (s sortablePodList) Less(i, j int) bool { 354 return computePodKey(&s[i]) < computePodKey(&s[j]) 355 } 356 357 func (s sortablePodList) Swap(i, j int) { 358 s[i], s[j] = s[j], s[i] 359 }