k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/scheduler/util/assumecache/assume_cache_test.go (about)

     1  /*
     2  Copyright 2017 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 assumecache
    18  
    19  import (
    20  	"fmt"
    21  	"slices"
    22  	"testing"
    23  
    24  	"github.com/google/go-cmp/cmp"
    25  	"github.com/google/go-cmp/cmp/cmpopts"
    26  
    27  	"k8s.io/apimachinery/pkg/api/meta"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/client-go/tools/cache"
    30  	"k8s.io/kubernetes/test/utils/ktesting"
    31  )
    32  
    33  // testInformer implements [Informer] and can be used to feed changes into an assume
    34  // cache during unit testing. Only a single event handler is supported, which is
    35  // sufficient for one assume cache.
    36  type testInformer struct {
    37  	handler cache.ResourceEventHandler
    38  }
    39  
    40  func (i *testInformer) AddEventHandler(handler cache.ResourceEventHandler) (cache.ResourceEventHandlerRegistration, error) {
    41  	i.handler = handler
    42  	return nil, nil
    43  }
    44  
    45  func (i *testInformer) add(obj interface{}) {
    46  	if i.handler == nil {
    47  		return
    48  	}
    49  	i.handler.OnAdd(obj, false)
    50  }
    51  
    52  func (i *testInformer) update(obj interface{}) {
    53  	if i.handler == nil {
    54  		return
    55  	}
    56  	i.handler.OnUpdate(nil, obj)
    57  }
    58  
    59  func (i *testInformer) delete(obj interface{}) {
    60  	if i.handler == nil {
    61  		return
    62  	}
    63  	i.handler.OnDelete(obj)
    64  }
    65  
    66  func makeObj(name, version, namespace string) metav1.Object {
    67  	return &metav1.ObjectMeta{
    68  		Name:            name,
    69  		Namespace:       namespace,
    70  		ResourceVersion: version,
    71  	}
    72  }
    73  
    74  func newTest(t *testing.T) (ktesting.TContext, *AssumeCache, *testInformer) {
    75  	return newTestWithIndexer(t, "", nil)
    76  }
    77  
    78  func newTestWithIndexer(t *testing.T, indexName string, indexFunc cache.IndexFunc) (ktesting.TContext, *AssumeCache, *testInformer) {
    79  	tCtx := ktesting.Init(t)
    80  	informer := new(testInformer)
    81  	cache := NewAssumeCache(tCtx.Logger(), informer, "TestObject", indexName, indexFunc)
    82  	return tCtx, cache, informer
    83  }
    84  
    85  func verify(tCtx ktesting.TContext, cache *AssumeCache, key string, expectedObject, expectedAPIObject interface{}) {
    86  	tCtx.Helper()
    87  	actualObject, err := cache.Get(key)
    88  	if err != nil {
    89  		tCtx.Fatalf("unexpected error retrieving object for key %s: %v", key, err)
    90  	}
    91  	if actualObject != expectedObject {
    92  		tCtx.Fatalf("Get() returned %v, expected %v", actualObject, expectedObject)
    93  	}
    94  	actualAPIObject, err := cache.GetAPIObj(key)
    95  	if err != nil {
    96  		tCtx.Fatalf("unexpected error retrieving API object for key %s: %v", key, err)
    97  	}
    98  	if actualAPIObject != expectedAPIObject {
    99  		tCtx.Fatalf("GetAPIObject() returned %v, expected %v", actualAPIObject, expectedAPIObject)
   100  	}
   101  }
   102  
   103  func verifyList(tCtx ktesting.TContext, assumeCache *AssumeCache, expectedObjs []interface{}, indexObj interface{}) {
   104  	actualObjs := assumeCache.List(indexObj)
   105  	diff := cmp.Diff(expectedObjs, actualObjs, cmpopts.SortSlices(func(x, y interface{}) bool {
   106  		xKey, err := cache.MetaNamespaceKeyFunc(x)
   107  		if err != nil {
   108  			tCtx.Fatalf("unexpected error determining key for %v: %v", x, err)
   109  		}
   110  		yKey, err := cache.MetaNamespaceKeyFunc(y)
   111  		if err != nil {
   112  			tCtx.Fatalf("unexpected error determining key for %v: %v", y, err)
   113  		}
   114  		return xKey < yKey
   115  	}))
   116  	if diff != "" {
   117  		tCtx.Fatalf("List() result differs (- expected, + actual):\n%s", diff)
   118  	}
   119  }
   120  
   121  func TestAssume(t *testing.T) {
   122  	scenarios := map[string]struct {
   123  		oldObj    metav1.Object
   124  		newObj    interface{}
   125  		expectErr error
   126  	}{
   127  		"success-same-version": {
   128  			oldObj: makeObj("pvc1", "5", ""),
   129  			newObj: makeObj("pvc1", "5", ""),
   130  		},
   131  		"success-new-higher-version": {
   132  			oldObj: makeObj("pvc1", "5", ""),
   133  			newObj: makeObj("pvc1", "6", ""),
   134  		},
   135  		"fail-old-not-found": {
   136  			oldObj:    makeObj("pvc2", "5", ""),
   137  			newObj:    makeObj("pvc1", "5", ""),
   138  			expectErr: ErrNotFound,
   139  		},
   140  		"fail-new-lower-version": {
   141  			oldObj:    makeObj("pvc1", "5", ""),
   142  			newObj:    makeObj("pvc1", "4", ""),
   143  			expectErr: cmpopts.AnyError,
   144  		},
   145  		"fail-new-bad-version": {
   146  			oldObj:    makeObj("pvc1", "5", ""),
   147  			newObj:    makeObj("pvc1", "a", ""),
   148  			expectErr: cmpopts.AnyError,
   149  		},
   150  		"fail-old-bad-version": {
   151  			oldObj:    makeObj("pvc1", "a", ""),
   152  			newObj:    makeObj("pvc1", "5", ""),
   153  			expectErr: cmpopts.AnyError,
   154  		},
   155  		"fail-new-bad-object": {
   156  			oldObj:    makeObj("pvc1", "5", ""),
   157  			newObj:    1,
   158  			expectErr: ErrObjectName,
   159  		},
   160  	}
   161  
   162  	for name, scenario := range scenarios {
   163  		t.Run(name, func(t *testing.T) {
   164  			tCtx, cache, informer := newTest(t)
   165  
   166  			// Add old object to cache.
   167  			informer.add(scenario.oldObj)
   168  			verify(tCtx, cache, scenario.oldObj.GetName(), scenario.oldObj, scenario.oldObj)
   169  
   170  			// Assume new object.
   171  			err := cache.Assume(scenario.newObj)
   172  			if diff := cmp.Diff(scenario.expectErr, err, cmpopts.EquateErrors()); diff != "" {
   173  				t.Errorf("Assume() returned error: %v\ndiff (- expected, + actual):\n%s", err, diff)
   174  			}
   175  
   176  			// Check that Get returns correct object.
   177  			expectedObj := scenario.newObj
   178  			if scenario.expectErr != nil {
   179  				expectedObj = scenario.oldObj
   180  			}
   181  			verify(tCtx, cache, scenario.oldObj.GetName(), expectedObj, scenario.oldObj)
   182  		})
   183  	}
   184  }
   185  
   186  func TestRestore(t *testing.T) {
   187  	tCtx, cache, informer := newTest(t)
   188  
   189  	// This test assumes an object with the same version as the API object.
   190  	// The assume cache supports that, but doing so in real code suffers from
   191  	// a race: if an unrelated update is received from the apiserver while
   192  	// such an object is assumed, the local modification gets dropped.
   193  	oldObj := makeObj("pvc1", "5", "")
   194  	newObj := makeObj("pvc1", "5", "")
   195  
   196  	// Restore object that doesn't exist
   197  	cache.Restore("nothing")
   198  
   199  	// Add old object to cache.
   200  	informer.add(oldObj)
   201  	verify(ktesting.WithStep(tCtx, "after initial update"), cache, oldObj.GetName(), oldObj, oldObj)
   202  
   203  	// Restore object.
   204  	cache.Restore(oldObj.GetName())
   205  	verify(ktesting.WithStep(tCtx, "after initial Restore"), cache, oldObj.GetName(), oldObj, oldObj)
   206  
   207  	// Assume new object.
   208  	if err := cache.Assume(newObj); err != nil {
   209  		t.Fatalf("Assume() returned error %v", err)
   210  	}
   211  	verify(ktesting.WithStep(tCtx, "after Assume"), cache, oldObj.GetName(), newObj, oldObj)
   212  
   213  	// Restore object.
   214  	cache.Restore(oldObj.GetName())
   215  	verify(ktesting.WithStep(tCtx, "after second Restore"), cache, oldObj.GetName(), oldObj, oldObj)
   216  }
   217  
   218  func TestEvents(t *testing.T) {
   219  	tCtx, cache, informer := newTest(t)
   220  
   221  	oldObj := makeObj("pvc1", "5", "")
   222  	newObj := makeObj("pvc1", "6", "")
   223  	key := oldObj.GetName()
   224  
   225  	// Add old object to cache.
   226  	informer.add(oldObj)
   227  	verify(ktesting.WithStep(tCtx, "after initial update"), cache, key, oldObj, oldObj)
   228  
   229  	// Update object.
   230  	informer.update(newObj)
   231  	verify(ktesting.WithStep(tCtx, "after initial update"), cache, key, newObj, newObj)
   232  
   233  	// Some error cases (don't occur in practice).
   234  	informer.add(1)
   235  	verify(ktesting.WithStep(tCtx, "after nop add"), cache, key, newObj, newObj)
   236  	informer.add(nil)
   237  	verify(ktesting.WithStep(tCtx, "after nil add"), cache, key, newObj, newObj)
   238  	informer.update(oldObj)
   239  	verify(ktesting.WithStep(tCtx, "after nop update"), cache, key, newObj, newObj)
   240  	informer.update(nil)
   241  	verify(ktesting.WithStep(tCtx, "after nil update"), cache, key, newObj, newObj)
   242  	informer.delete(nil)
   243  	verify(ktesting.WithStep(tCtx, "after nop delete"), cache, key, newObj, newObj)
   244  
   245  	// Delete object.
   246  	informer.delete(oldObj)
   247  	_, err := cache.Get(key)
   248  	if diff := cmp.Diff(ErrNotFound, err, cmpopts.EquateErrors()); diff != "" {
   249  		t.Errorf("Get did not return expected error: %v\ndiff (- expected, + actual):\n%s", err, diff)
   250  	}
   251  }
   252  
   253  func TestListNoIndexer(t *testing.T) {
   254  	tCtx, cache, informer := newTest(t)
   255  
   256  	// Add a bunch of objects.
   257  	objs := make([]interface{}, 0, 10)
   258  	for i := 0; i < 10; i++ {
   259  		obj := makeObj(fmt.Sprintf("test-pvc%v", i), "1", "")
   260  		objs = append(objs, obj)
   261  		informer.add(obj)
   262  	}
   263  
   264  	// List them
   265  	verifyList(ktesting.WithStep(tCtx, "after add"), cache, objs, "")
   266  
   267  	// Update an object.
   268  	updatedObj := makeObj("test-pvc3", "2", "")
   269  	objs[3] = updatedObj
   270  	informer.update(updatedObj)
   271  
   272  	// List them
   273  	verifyList(ktesting.WithStep(tCtx, "after update"), cache, objs, "")
   274  
   275  	// Delete a PV
   276  	deletedObj := objs[7]
   277  	objs = slices.Delete(objs, 7, 8)
   278  	informer.delete(deletedObj)
   279  
   280  	// List them
   281  	verifyList(ktesting.WithStep(tCtx, "after delete"), cache, objs, "")
   282  }
   283  
   284  func TestListWithIndexer(t *testing.T) {
   285  	namespaceIndexer := func(obj interface{}) ([]string, error) {
   286  		objAccessor, err := meta.Accessor(obj)
   287  		if err != nil {
   288  			return nil, err
   289  		}
   290  		return []string{objAccessor.GetNamespace()}, nil
   291  	}
   292  	tCtx, cache, informer := newTestWithIndexer(t, "myNamespace", namespaceIndexer)
   293  
   294  	// Add a bunch of objects.
   295  	ns := "ns1"
   296  	objs := make([]interface{}, 0, 10)
   297  	for i := 0; i < 10; i++ {
   298  		obj := makeObj(fmt.Sprintf("test-pvc%v", i), "1", ns)
   299  		objs = append(objs, obj)
   300  		informer.add(obj)
   301  	}
   302  
   303  	// Add a bunch of other objects.
   304  	for i := 0; i < 10; i++ {
   305  		obj := makeObj(fmt.Sprintf("test-pvc%v", i), "1", "ns2")
   306  		informer.add(obj)
   307  	}
   308  
   309  	// List them
   310  	verifyList(ktesting.WithStep(tCtx, "after add"), cache, objs, objs[0])
   311  
   312  	// Update an object.
   313  	updatedObj := makeObj("test-pvc3", "2", ns)
   314  	objs[3] = updatedObj
   315  	informer.update(updatedObj)
   316  
   317  	// List them
   318  	verifyList(ktesting.WithStep(tCtx, "after update"), cache, objs, objs[0])
   319  
   320  	// Delete a PV
   321  	deletedObj := objs[7]
   322  	objs = slices.Delete(objs, 7, 8)
   323  	informer.delete(deletedObj)
   324  
   325  	// List them
   326  	verifyList(ktesting.WithStep(tCtx, "after delete"), cache, objs, objs[0])
   327  }