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  }