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 }