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  }