k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/scheduler/util/assumecache/assume_cache.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 "errors" 21 "fmt" 22 "strconv" 23 "sync" 24 25 "k8s.io/klog/v2" 26 27 "k8s.io/apimachinery/pkg/api/meta" 28 "k8s.io/client-go/tools/cache" 29 ) 30 31 // Informer is the subset of [cache.SharedInformer] that NewAssumeCache depends upon. 32 type Informer interface { 33 AddEventHandler(handler cache.ResourceEventHandler) (cache.ResourceEventHandlerRegistration, error) 34 } 35 36 // AddTestObject adds an object to the assume cache. 37 // Only use this for unit testing! 38 func AddTestObject(cache *AssumeCache, obj interface{}) { 39 cache.add(obj) 40 } 41 42 // UpdateTestObject updates an object in the assume cache. 43 // Only use this for unit testing! 44 func UpdateTestObject(cache *AssumeCache, obj interface{}) { 45 cache.update(nil, obj) 46 } 47 48 // DeleteTestObject deletes object in the assume cache. 49 // Only use this for unit testing! 50 func DeleteTestObject(cache *AssumeCache, obj interface{}) { 51 cache.delete(obj) 52 } 53 54 // Sentinel errors that can be checked for with errors.Is. 55 var ( 56 ErrWrongType = errors.New("object has wrong type") 57 ErrNotFound = errors.New("object not found") 58 ErrObjectName = errors.New("cannot determine object name") 59 ) 60 61 type WrongTypeError struct { 62 TypeName string 63 Object interface{} 64 } 65 66 func (e WrongTypeError) Error() string { 67 return fmt.Sprintf("could not convert object to type %v: %+v", e.TypeName, e.Object) 68 } 69 70 func (e WrongTypeError) Is(err error) bool { 71 return err == ErrWrongType 72 } 73 74 type NotFoundError struct { 75 TypeName string 76 ObjectKey string 77 } 78 79 func (e NotFoundError) Error() string { 80 return fmt.Sprintf("could not find %v %q", e.TypeName, e.ObjectKey) 81 } 82 83 func (e NotFoundError) Is(err error) bool { 84 return err == ErrNotFound 85 } 86 87 type ObjectNameError struct { 88 DetailedErr error 89 } 90 91 func (e ObjectNameError) Error() string { 92 return fmt.Sprintf("failed to get object name: %v", e.DetailedErr) 93 } 94 95 func (e ObjectNameError) Is(err error) bool { 96 return err == ErrObjectName 97 } 98 99 // AssumeCache is a cache on top of the informer that allows for updating 100 // objects outside of informer events and also restoring the informer 101 // cache's version of the object. Objects are assumed to be 102 // Kubernetes API objects that are supported by [meta.Accessor]. 103 // 104 // Objects can referenced via their key, with [cache.MetaNamespaceKeyFunc] 105 // as key function. 106 // 107 // AssumeCache stores two pointers to represent a single object: 108 // - The pointer to the informer object. 109 // - The pointer to the latest object, which could be the same as 110 // the informer object, or an in-memory object. 111 // 112 // An informer update always overrides the latest object pointer. 113 // 114 // Assume() only updates the latest object pointer. 115 // Restore() sets the latest object pointer back to the informer object. 116 // Get/List() always returns the latest object pointer. 117 type AssumeCache struct { 118 // The logger that was chosen when setting up the cache. 119 // Will be used for all operations. 120 logger klog.Logger 121 122 // Synchronizes updates to store 123 rwMutex sync.RWMutex 124 125 // describes the object stored 126 description string 127 128 // Stores objInfo pointers 129 store cache.Indexer 130 131 // Index function for object 132 indexFunc cache.IndexFunc 133 indexName string 134 } 135 136 type objInfo struct { 137 // name of the object 138 name string 139 140 // Latest version of object could be cached-only or from informer 141 latestObj interface{} 142 143 // Latest object from informer 144 apiObj interface{} 145 } 146 147 func objInfoKeyFunc(obj interface{}) (string, error) { 148 objInfo, ok := obj.(*objInfo) 149 if !ok { 150 return "", &WrongTypeError{TypeName: "objInfo", Object: obj} 151 } 152 return objInfo.name, nil 153 } 154 155 func (c *AssumeCache) objInfoIndexFunc(obj interface{}) ([]string, error) { 156 objInfo, ok := obj.(*objInfo) 157 if !ok { 158 return []string{""}, &WrongTypeError{TypeName: "objInfo", Object: obj} 159 } 160 return c.indexFunc(objInfo.latestObj) 161 } 162 163 // NewAssumeCache creates an assume cache for general objects. 164 func NewAssumeCache(logger klog.Logger, informer Informer, description, indexName string, indexFunc cache.IndexFunc) *AssumeCache { 165 c := &AssumeCache{ 166 logger: logger, 167 description: description, 168 indexFunc: indexFunc, 169 indexName: indexName, 170 } 171 indexers := cache.Indexers{} 172 if indexName != "" && indexFunc != nil { 173 indexers[indexName] = c.objInfoIndexFunc 174 } 175 c.store = cache.NewIndexer(objInfoKeyFunc, indexers) 176 177 // Unit tests don't use informers 178 if informer != nil { 179 // Cannot fail in practice?! No-one bothers checking the error. 180 _, _ = informer.AddEventHandler( 181 cache.ResourceEventHandlerFuncs{ 182 AddFunc: c.add, 183 UpdateFunc: c.update, 184 DeleteFunc: c.delete, 185 }, 186 ) 187 } 188 return c 189 } 190 191 func (c *AssumeCache) add(obj interface{}) { 192 if obj == nil { 193 return 194 } 195 196 name, err := cache.MetaNamespaceKeyFunc(obj) 197 if err != nil { 198 c.logger.Error(&ObjectNameError{err}, "Add failed") 199 return 200 } 201 202 c.rwMutex.Lock() 203 defer c.rwMutex.Unlock() 204 205 if objInfo, _ := c.getObjInfo(name); objInfo != nil { 206 newVersion, err := c.getObjVersion(name, obj) 207 if err != nil { 208 c.logger.Error(err, "Add failed: couldn't get object version") 209 return 210 } 211 212 storedVersion, err := c.getObjVersion(name, objInfo.latestObj) 213 if err != nil { 214 c.logger.Error(err, "Add failed: couldn't get stored object version") 215 return 216 } 217 218 // Only update object if version is newer. 219 // This is so we don't override assumed objects due to informer resync. 220 if newVersion <= storedVersion { 221 c.logger.V(10).Info("Skip adding object to assume cache because version is not newer than storedVersion", "description", c.description, "cacheKey", name, "newVersion", newVersion, "storedVersion", storedVersion) 222 return 223 } 224 } 225 226 objInfo := &objInfo{name: name, latestObj: obj, apiObj: obj} 227 if err = c.store.Update(objInfo); err != nil { 228 c.logger.Info("Error occurred while updating stored object", "err", err) 229 } else { 230 c.logger.V(10).Info("Adding object to assume cache", "description", c.description, "cacheKey", name, "assumeCache", obj) 231 } 232 } 233 234 func (c *AssumeCache) update(oldObj interface{}, newObj interface{}) { 235 c.add(newObj) 236 } 237 238 func (c *AssumeCache) delete(obj interface{}) { 239 if obj == nil { 240 return 241 } 242 243 name, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj) 244 if err != nil { 245 c.logger.Error(&ObjectNameError{err}, "Failed to delete") 246 return 247 } 248 249 c.rwMutex.Lock() 250 defer c.rwMutex.Unlock() 251 252 objInfo := &objInfo{name: name} 253 err = c.store.Delete(objInfo) 254 if err != nil { 255 c.logger.Error(err, "Failed to delete", "description", c.description, "cacheKey", name) 256 } 257 } 258 259 func (c *AssumeCache) getObjVersion(name string, obj interface{}) (int64, error) { 260 objAccessor, err := meta.Accessor(obj) 261 if err != nil { 262 return -1, err 263 } 264 265 objResourceVersion, err := strconv.ParseInt(objAccessor.GetResourceVersion(), 10, 64) 266 if err != nil { 267 //nolint:errorlint // Intentionally not wrapping the error, the underlying error is an implementation detail. 268 return -1, fmt.Errorf("error parsing ResourceVersion %q for %v %q: %v", objAccessor.GetResourceVersion(), c.description, name, err) 269 } 270 return objResourceVersion, nil 271 } 272 273 func (c *AssumeCache) getObjInfo(key string) (*objInfo, error) { 274 obj, ok, err := c.store.GetByKey(key) 275 if err != nil { 276 return nil, err 277 } 278 if !ok { 279 return nil, &NotFoundError{TypeName: c.description, ObjectKey: key} 280 } 281 282 objInfo, ok := obj.(*objInfo) 283 if !ok { 284 return nil, &WrongTypeError{"objInfo", obj} 285 } 286 return objInfo, nil 287 } 288 289 // Get the object by its key. 290 func (c *AssumeCache) Get(key string) (interface{}, error) { 291 c.rwMutex.RLock() 292 defer c.rwMutex.RUnlock() 293 294 objInfo, err := c.getObjInfo(key) 295 if err != nil { 296 return nil, err 297 } 298 return objInfo.latestObj, nil 299 } 300 301 // GetAPIObj gets the informer cache's version by its key. 302 func (c *AssumeCache) GetAPIObj(key string) (interface{}, error) { 303 c.rwMutex.RLock() 304 defer c.rwMutex.RUnlock() 305 306 objInfo, err := c.getObjInfo(key) 307 if err != nil { 308 return nil, err 309 } 310 return objInfo.apiObj, nil 311 } 312 313 // List all the objects in the cache. 314 func (c *AssumeCache) List(indexObj interface{}) []interface{} { 315 c.rwMutex.RLock() 316 defer c.rwMutex.RUnlock() 317 318 allObjs := []interface{}{} 319 var objs []interface{} 320 if c.indexName != "" { 321 o, err := c.store.Index(c.indexName, &objInfo{latestObj: indexObj}) 322 if err != nil { 323 c.logger.Error(err, "List index error") 324 return nil 325 } 326 objs = o 327 } else { 328 objs = c.store.List() 329 } 330 331 for _, obj := range objs { 332 objInfo, ok := obj.(*objInfo) 333 if !ok { 334 c.logger.Error(&WrongTypeError{TypeName: "objInfo", Object: obj}, "List error") 335 continue 336 } 337 allObjs = append(allObjs, objInfo.latestObj) 338 } 339 return allObjs 340 } 341 342 // Assume updates the object in-memory only. 343 // 344 // The version of the object must be greater or equal to 345 // the current object, otherwise an error is returned. 346 // 347 // Storing an object with the same version is supported 348 // by the assume cache, but suffers from a race: if an 349 // update is received via the informer while such an 350 // object is assumed, it gets dropped in favor of the 351 // newer object from the apiserver. 352 // 353 // Only assuming objects that were returned by an apiserver 354 // operation (Update, Patch) is safe. 355 func (c *AssumeCache) Assume(obj interface{}) error { 356 name, err := cache.MetaNamespaceKeyFunc(obj) 357 if err != nil { 358 return &ObjectNameError{err} 359 } 360 361 c.rwMutex.Lock() 362 defer c.rwMutex.Unlock() 363 364 objInfo, err := c.getObjInfo(name) 365 if err != nil { 366 return err 367 } 368 369 newVersion, err := c.getObjVersion(name, obj) 370 if err != nil { 371 return err 372 } 373 374 storedVersion, err := c.getObjVersion(name, objInfo.latestObj) 375 if err != nil { 376 return err 377 } 378 379 if newVersion < storedVersion { 380 return fmt.Errorf("%v %q is out of sync (stored: %d, assume: %d)", c.description, name, storedVersion, newVersion) 381 } 382 383 // Only update the cached object 384 objInfo.latestObj = obj 385 c.logger.V(4).Info("Assumed object", "description", c.description, "cacheKey", name, "version", newVersion) 386 return nil 387 } 388 389 // Restore the informer cache's version of the object. 390 func (c *AssumeCache) Restore(objName string) { 391 c.rwMutex.Lock() 392 defer c.rwMutex.Unlock() 393 394 objInfo, err := c.getObjInfo(objName) 395 if err != nil { 396 // This could be expected if object got deleted 397 c.logger.V(5).Info("Restore object", "description", c.description, "cacheKey", objName, "err", err) 398 } else { 399 objInfo.latestObj = objInfo.apiObj 400 c.logger.V(4).Info("Restored object", "description", c.description, "cacheKey", objName) 401 } 402 }