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  }