github.com/imran-kn/cilium-fork@v1.6.9/pkg/envoy/xds/cache.go (about)

     1  // Copyright 2018 Authors of Cilium
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package xds
    16  
    17  import (
    18  	"context"
    19  	"sort"
    20  
    21  	"github.com/cilium/cilium/pkg/logging/logfields"
    22  
    23  	"github.com/golang/protobuf/proto"
    24  	"github.com/sirupsen/logrus"
    25  )
    26  
    27  // Cache is a key-value container which allows atomically updating entries and
    28  // incrementing a version number and notifying observers if the cache is actually
    29  // modified.
    30  // Cache implements the ObservableResourceSet interface.
    31  // This cache implementation ignores the proxy node identifiers, i.e. the same
    32  // resources are available under the same names to all nodes.
    33  type Cache struct {
    34  	*BaseObservableResourceSource
    35  
    36  	// resources is the map of cached resource name to resource entry.
    37  	resources map[cacheKey]cacheValue
    38  
    39  	// version is the current version of the resources in the cache.
    40  	// valid version numbers start at 1, which is the version of a cache
    41  	// before any modifications have been made
    42  	version uint64
    43  }
    44  
    45  // cacheKey uniquely identifies a resource.
    46  type cacheKey struct {
    47  	// typeURL is the URL that uniquely identifies the resource's type.
    48  	typeURL string
    49  
    50  	// resourceName is the name of the resource, unique among all the resources
    51  	// of this type.
    52  	resourceName string
    53  }
    54  
    55  // cacheValue is a cached resource.
    56  type cacheValue struct {
    57  	// resource is the resource in this cache entry.
    58  	resource proto.Message
    59  
    60  	// lastModifiedVersion is the version when this resource entry was last
    61  	// modified.
    62  	lastModifiedVersion uint64
    63  }
    64  
    65  // NewCache creates a new, empty cache with 0 as its current version.
    66  func NewCache() *Cache {
    67  	return &Cache{
    68  		BaseObservableResourceSource: NewBaseObservableResourceSource(),
    69  		resources:                    make(map[cacheKey]cacheValue),
    70  		version:                      1,
    71  	}
    72  }
    73  
    74  // tx inserts/updates a set of resources, then deletes a set of resources, then
    75  // increases the cache's version number atomically if the cache is actually
    76  // changed.
    77  // The version after updating the set is returned.
    78  func (c *Cache) tx(typeURL string, upsertedResources map[string]proto.Message, deletedNames []string) (version uint64, updated bool, revert ResourceMutatorRevertFunc) {
    79  	c.locker.Lock()
    80  	defer c.locker.Unlock()
    81  
    82  	cacheIsUpdated := false
    83  	newVersion := c.version + 1
    84  
    85  	cacheLog := log.WithFields(logrus.Fields{
    86  		logfields.XDSTypeURL:       typeURL,
    87  		logfields.XDSCachedVersion: newVersion,
    88  	})
    89  
    90  	cacheLog.Debugf("preparing new cache transaction: upserting %d entries, deleting %d entries",
    91  		len(upsertedResources), len(deletedNames))
    92  
    93  	// The parameters to pass to tx in revertFunc.
    94  	var revertUpsertedResources map[string]proto.Message
    95  	var revertDeletedNames []string
    96  
    97  	k := cacheKey{
    98  		typeURL: typeURL,
    99  	}
   100  
   101  	v := cacheValue{
   102  		lastModifiedVersion: newVersion,
   103  	}
   104  
   105  	for name, value := range upsertedResources {
   106  		k.resourceName = name
   107  		oldV, found := c.resources[k]
   108  		// If the value is unchanged, don't update the entry, to preserve its
   109  		// lastModifiedVersion. This allows minimizing the frequency of
   110  		// responses in GetResources.
   111  		if !found || !proto.Equal(oldV.resource, value) {
   112  			if found {
   113  				cacheLog.WithField(logfields.XDSResourceName, name).Debug("updating resource in cache")
   114  
   115  				if revertUpsertedResources == nil {
   116  					revertUpsertedResources = make(map[string]proto.Message, len(upsertedResources)+len(deletedNames))
   117  				}
   118  				revertUpsertedResources[name] = oldV.resource
   119  			} else {
   120  				cacheLog.WithField(logfields.XDSResourceName, name).Debug("inserting resource into cache")
   121  
   122  				if revertDeletedNames == nil {
   123  					revertDeletedNames = make([]string, 0, len(upsertedResources))
   124  				}
   125  				revertDeletedNames = append(revertDeletedNames, name)
   126  			}
   127  			cacheIsUpdated = true
   128  			v.resource = value
   129  			c.resources[k] = v
   130  		}
   131  	}
   132  
   133  	for _, name := range deletedNames {
   134  		k.resourceName = name
   135  		oldV, found := c.resources[k]
   136  		if found {
   137  			cacheLog.WithField(logfields.XDSResourceName, name).
   138  				Debug("deleting resource from cache")
   139  
   140  			if revertUpsertedResources == nil {
   141  				revertUpsertedResources = make(map[string]proto.Message, len(upsertedResources)+len(deletedNames))
   142  			}
   143  			revertUpsertedResources[name] = oldV.resource
   144  
   145  			cacheIsUpdated = true
   146  			delete(c.resources, k)
   147  		}
   148  	}
   149  
   150  	if cacheIsUpdated {
   151  		cacheLog.Debug("committing cache transaction and notifying of new version")
   152  		c.version = newVersion
   153  		c.NotifyNewResourceVersionRLocked(typeURL, c.version)
   154  
   155  		revert = func() (version uint64, updated bool) {
   156  			version, updated, _ = c.tx(typeURL, revertUpsertedResources, revertDeletedNames)
   157  			return
   158  		}
   159  	} else {
   160  		cacheLog.Debug("cache unmodified by transaction; aborting")
   161  	}
   162  
   163  	return c.version, cacheIsUpdated, revert
   164  }
   165  
   166  func (c *Cache) Upsert(typeURL string, resourceName string, resource proto.Message) (version uint64, updated bool, revert ResourceMutatorRevertFunc) {
   167  	return c.tx(typeURL, map[string]proto.Message{resourceName: resource}, nil)
   168  }
   169  
   170  func (c *Cache) Delete(typeURL string, resourceName string) (version uint64, updated bool, revert ResourceMutatorRevertFunc) {
   171  	return c.tx(typeURL, nil, []string{resourceName})
   172  }
   173  
   174  func (c *Cache) Clear(typeURL string) (version uint64, updated bool) {
   175  	c.locker.Lock()
   176  	defer c.locker.Unlock()
   177  
   178  	cacheIsUpdated := false
   179  	newVersion := c.version + 1
   180  
   181  	cacheLog := log.WithFields(logrus.Fields{
   182  		logfields.XDSTypeURL:       typeURL,
   183  		logfields.XDSCachedVersion: newVersion,
   184  	})
   185  
   186  	cacheLog.Debug("preparing new cache transaction: deleting all entries")
   187  
   188  	for k := range c.resources {
   189  		if k.typeURL == typeURL {
   190  			cacheLog.WithField(logfields.XDSResourceName, k.resourceName).
   191  				Debug("deleting resource from cache")
   192  			cacheIsUpdated = true
   193  			delete(c.resources, k)
   194  		}
   195  	}
   196  
   197  	if cacheIsUpdated {
   198  		cacheLog.Debug("committing cache transaction and notifying of new version")
   199  		c.version = newVersion
   200  		c.NotifyNewResourceVersionRLocked(typeURL, c.version)
   201  	} else {
   202  		cacheLog.Debug("cache unmodified by transaction; aborting")
   203  	}
   204  
   205  	return c.version, cacheIsUpdated
   206  }
   207  
   208  func (c *Cache) GetResources(ctx context.Context, typeURL string, lastVersion uint64, nodeIP string, resourceNames []string) (*VersionedResources, error) {
   209  	c.locker.RLock()
   210  	defer c.locker.RUnlock()
   211  
   212  	cacheLog := log.WithFields(logrus.Fields{
   213  		logfields.XDSAckedVersion: lastVersion,
   214  		logfields.XDSClientNode:   nodeIP,
   215  		logfields.XDSTypeURL:      typeURL,
   216  	})
   217  
   218  	res := &VersionedResources{
   219  		Version: c.version,
   220  		Canary:  false,
   221  	}
   222  
   223  	// Return all resources.
   224  	// TODO: return nil if no changes since the last version?
   225  	if len(resourceNames) == 0 {
   226  		res.ResourceNames = make([]string, 0, len(c.resources))
   227  		res.Resources = make([]proto.Message, 0, len(c.resources))
   228  		cacheLog.Debugf("no resource names requested, returning all %d resources", len(c.resources))
   229  		for k, v := range c.resources {
   230  			res.ResourceNames = append(res.ResourceNames, k.resourceName)
   231  			res.Resources = append(res.Resources, v.resource)
   232  		}
   233  		return res, nil
   234  	}
   235  
   236  	// Return only the resources with the requested names.
   237  
   238  	// As an optimization, if all the requested resources are found but none of
   239  	// them has been modified since the lastVersion, return no response.
   240  	// If at least one resource is not found, return all the found resources
   241  	// anyway, because we don't know whether the missing resource was deleted
   242  	// after the lastVersion, so we can't optimize in this case.
   243  
   244  	res.ResourceNames = make([]string, 0, len(resourceNames))
   245  	res.Resources = make([]proto.Message, 0, len(resourceNames))
   246  
   247  	k := cacheKey{typeURL: typeURL}
   248  
   249  	allResourcesFound := true
   250  	updatedSinceLastVersion := false
   251  
   252  	cacheLog.Debugf("%d resource names requested, filtering resources", len(resourceNames))
   253  
   254  	for _, name := range resourceNames {
   255  		k.resourceName = name
   256  		v, found := c.resources[k]
   257  		if found {
   258  			cacheLog.WithField(logfields.XDSResourceName, name).
   259  				Debugf("resource found, last modified in version %d", v.lastModifiedVersion)
   260  			if lastVersion == 0 || (lastVersion < v.lastModifiedVersion) {
   261  				updatedSinceLastVersion = true
   262  			}
   263  			res.ResourceNames = append(res.ResourceNames, name)
   264  			res.Resources = append(res.Resources, v.resource)
   265  		} else {
   266  			cacheLog.WithField(logfields.XDSResourceName, name).Debug("resource not found")
   267  			allResourcesFound = false
   268  		}
   269  	}
   270  
   271  	if allResourcesFound && !updatedSinceLastVersion {
   272  		cacheLog.Debug("all requested resources found but not updated since last version, returning no response")
   273  		return nil, nil
   274  	}
   275  
   276  	sort.Strings(res.ResourceNames)
   277  
   278  	cacheLog.Debugf("returning %d resources out of %d requested", len(res.Resources), len(resourceNames))
   279  	return res, nil
   280  }
   281  
   282  func (c *Cache) EnsureVersion(typeURL string, version uint64) {
   283  	c.locker.Lock()
   284  	defer c.locker.Unlock()
   285  
   286  	if c.version < version {
   287  		cacheLog := log.WithFields(logrus.Fields{
   288  			logfields.XDSTypeURL:      typeURL,
   289  			logfields.XDSAckedVersion: version,
   290  		})
   291  		cacheLog.Debug("increasing version to match client and notifying of new version")
   292  
   293  		c.version = version
   294  		c.NotifyNewResourceVersionRLocked(typeURL, c.version)
   295  	}
   296  }
   297  
   298  // Lookup finds the resource corresponding to the specified typeURL and resourceName,
   299  // if available, and returns it. Otherwise, returns nil. If an error occurs while
   300  // fetching the resource, also returns the error.
   301  func (c *Cache) Lookup(typeURL string, resourceName string) (proto.Message, error) {
   302  	res, err := c.GetResources(context.Background(), typeURL, 0, "", []string{resourceName})
   303  	if err != nil || res == nil || len(res.Resources) == 0 {
   304  		return nil, err
   305  	}
   306  	return res.Resources[0], nil
   307  }