gitee.com/ks-custle/core-gm@v0.0.0-20230922171213-b83bdd97b62c/go-control-plane/pkg/cache/v3/linear.go (about)

     1  // Copyright 2020 Envoyproxy Authors
     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 cache
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"gitee.com/ks-custle/core-gm/go-control-plane/pkg/server/stream/v3"
    21  	"strconv"
    22  	"strings"
    23  	"sync"
    24  	"sync/atomic"
    25  
    26  	"gitee.com/ks-custle/core-gm/go-control-plane/pkg/cache/types"
    27  	"gitee.com/ks-custle/core-gm/go-control-plane/pkg/log"
    28  )
    29  
    30  type watches = map[chan Response]struct{}
    31  
    32  // LinearCache supports collections of opaque resources. This cache has a
    33  // single collection indexed by resource names and manages resource versions
    34  // internally. It implements the cache interface for a single type URL and
    35  // should be combined with other caches via type URL muxing. It can be used to
    36  // supply EDS entries, for example, uniformly across a fleet of proxies.
    37  type LinearCache struct {
    38  	// Type URL specific to the cache.
    39  	typeURL string
    40  	// Collection of resources indexed by name.
    41  	resources map[string]types.Resource
    42  	// Watches open by clients, indexed by resource name. Whenever resources
    43  	// are changed, the watch is triggered.
    44  	watches map[string]watches
    45  	// Set of watches for all resources in the collection
    46  	watchAll watches
    47  	// Set of delta watches. A delta watch always contain the list of subscribed resources
    48  	// together with its current version
    49  	// version and versionPrefix fields are ignored for delta watches, because we always generate the resource version.
    50  	deltaWatches map[int64]DeltaResponseWatch
    51  	// Continously incremented counter used to index delta watches.
    52  	deltaWatchCount int64
    53  	// versionMap holds the current hash map of all resources in the cache.
    54  	// versionMap is only to be used with delta xDS.
    55  	versionMap map[string]string
    56  	// Continuously incremented version.
    57  	version uint64
    58  	// Version prefix to be sent to the clients
    59  	versionPrefix string
    60  	// Versions for each resource by name.
    61  	versionVector map[string]uint64
    62  
    63  	log log.Logger
    64  
    65  	mu sync.RWMutex
    66  }
    67  
    68  var _ Cache = &LinearCache{}
    69  
    70  // LinearCacheOption Options for modifying the behavior of the linear cache.
    71  type LinearCacheOption func(*LinearCache)
    72  
    73  // WithVersionPrefix sets a version prefix of the form "prefixN" in the version info.
    74  // Version prefix can be used to distinguish replicated instances of the cache, in case
    75  // a client re-connects to another instance.
    76  func WithVersionPrefix(prefix string) LinearCacheOption {
    77  	return func(cache *LinearCache) {
    78  		cache.versionPrefix = prefix
    79  	}
    80  }
    81  
    82  // WithInitialResources initializes the initial set of resources.
    83  func WithInitialResources(resources map[string]types.Resource) LinearCacheOption {
    84  	return func(cache *LinearCache) {
    85  		cache.resources = resources
    86  		for name := range resources {
    87  			cache.versionVector[name] = 0
    88  		}
    89  	}
    90  }
    91  
    92  //goland:noinspection GoUnusedExportedFunction
    93  func WithLogger(log log.Logger) LinearCacheOption {
    94  	return func(cache *LinearCache) {
    95  		cache.log = log
    96  	}
    97  }
    98  
    99  // NewLinearCache creates a new cache. See the comments on the struct definition.
   100  func NewLinearCache(typeURL string, opts ...LinearCacheOption) *LinearCache {
   101  	out := &LinearCache{
   102  		typeURL:       typeURL,
   103  		resources:     make(map[string]types.Resource),
   104  		watches:       make(map[string]watches),
   105  		watchAll:      make(watches),
   106  		deltaWatches:  make(map[int64]DeltaResponseWatch),
   107  		versionMap:    make(map[string]string),
   108  		version:       0,
   109  		versionVector: make(map[string]uint64),
   110  	}
   111  	for _, opt := range opts {
   112  		opt(out)
   113  	}
   114  	return out
   115  }
   116  
   117  func (cache *LinearCache) respond(value chan Response, staleResources []string) {
   118  	var resources []types.ResourceWithTTL
   119  	// TODO: optimize the resources slice creations across different clients
   120  	if len(staleResources) == 0 {
   121  		resources = make([]types.ResourceWithTTL, 0, len(cache.resources))
   122  		for _, resource := range cache.resources {
   123  			resources = append(resources, types.ResourceWithTTL{Resource: resource})
   124  		}
   125  	} else {
   126  		resources = make([]types.ResourceWithTTL, 0, len(staleResources))
   127  		for _, name := range staleResources {
   128  			resource := cache.resources[name]
   129  			if resource != nil {
   130  				resources = append(resources, types.ResourceWithTTL{Resource: resource})
   131  			}
   132  		}
   133  	}
   134  	value <- &RawResponse{
   135  		Request:   &Request{TypeUrl: cache.typeURL},
   136  		Resources: resources,
   137  		Version:   cache.getVersion(),
   138  	}
   139  }
   140  
   141  func (cache *LinearCache) notifyAll(modified map[string]struct{}) {
   142  	// de-duplicate watches that need to be responded
   143  	notifyList := make(map[chan Response][]string)
   144  	for name := range modified {
   145  		for watch := range cache.watches[name] {
   146  			notifyList[watch] = append(notifyList[watch], name)
   147  		}
   148  		delete(cache.watches, name)
   149  	}
   150  	for value, stale := range notifyList {
   151  		cache.respond(value, stale)
   152  	}
   153  	for value := range cache.watchAll {
   154  		cache.respond(value, nil)
   155  	}
   156  	cache.watchAll = make(watches)
   157  
   158  	_ = cache.updateVersionMap(modified)
   159  
   160  	for id, watch := range cache.deltaWatches {
   161  		res := cache.respondDelta(watch.Request, watch.Response, watch.StreamState)
   162  		if res != nil {
   163  			delete(cache.deltaWatches, id)
   164  		}
   165  	}
   166  }
   167  
   168  func (cache *LinearCache) respondDelta(request *DeltaRequest, value chan DeltaResponse, state stream.StreamState) *RawDeltaResponse {
   169  	resp := createDeltaResponse(context.Background(), request, state, resourceContainer{
   170  		resourceMap:   cache.resources,
   171  		versionMap:    cache.versionMap,
   172  		systemVersion: cache.getVersion(),
   173  	})
   174  
   175  	// Only send a response if there were changes
   176  	if len(resp.Resources) > 0 || len(resp.RemovedResources) > 0 {
   177  		if cache.log != nil {
   178  			cache.log.Debugf("[linear cache] node: %s, sending delta response with resources: %v removed resources %v wildcard: %t",
   179  				request.GetNode().GetId(), resp.Resources, resp.RemovedResources, state.IsWildcard())
   180  		}
   181  		value <- resp
   182  		return resp
   183  	}
   184  	return nil
   185  }
   186  
   187  // UpdateResource updates a resource in the collection.
   188  func (cache *LinearCache) UpdateResource(name string, res types.Resource) error {
   189  	if res == nil {
   190  		return errors.New("nil resource")
   191  	}
   192  	cache.mu.Lock()
   193  	defer cache.mu.Unlock()
   194  
   195  	cache.version++
   196  	cache.versionVector[name] = cache.version
   197  	cache.resources[name] = res
   198  
   199  	// TODO: batch watch closures to prevent rapid updates
   200  	cache.notifyAll(map[string]struct{}{name: {}})
   201  
   202  	return nil
   203  }
   204  
   205  // DeleteResource removes a resource in the collection.
   206  func (cache *LinearCache) DeleteResource(name string) error {
   207  	cache.mu.Lock()
   208  	defer cache.mu.Unlock()
   209  
   210  	cache.version++
   211  	delete(cache.versionVector, name)
   212  	delete(cache.resources, name)
   213  
   214  	// TODO: batch watch closures to prevent rapid updates
   215  	cache.notifyAll(map[string]struct{}{name: {}})
   216  	return nil
   217  }
   218  
   219  // SetResources replaces current resources with a new set of resources.
   220  // This function is useful for wildcard xDS subscriptions.
   221  // This way watches that are subscribed to all resources are triggered only once regardless of how many resources are changed.
   222  func (cache *LinearCache) SetResources(resources map[string]types.Resource) {
   223  	cache.mu.Lock()
   224  	defer cache.mu.Unlock()
   225  
   226  	cache.version++
   227  
   228  	modified := map[string]struct{}{}
   229  	// Collect deleted resource names.
   230  	for name := range cache.resources {
   231  		if _, found := resources[name]; !found {
   232  			delete(cache.versionVector, name)
   233  			modified[name] = struct{}{}
   234  		}
   235  	}
   236  
   237  	cache.resources = resources
   238  
   239  	// Collect changed resource names.
   240  	// We assume all resources passed to SetResources are changed.
   241  	// Otherwise we would have to do proto.Equal on resources which is pretty expensive operation
   242  	for name := range resources {
   243  		cache.versionVector[name] = cache.version
   244  		modified[name] = struct{}{}
   245  	}
   246  
   247  	cache.notifyAll(modified)
   248  }
   249  
   250  // GetResources returns current resources stored in the cache
   251  func (cache *LinearCache) GetResources() map[string]types.Resource {
   252  	cache.mu.RLock()
   253  	defer cache.mu.RUnlock()
   254  	return cache.resources
   255  }
   256  
   257  func (cache *LinearCache) CreateWatch(request *Request, value chan Response) func() {
   258  	if request.TypeUrl != cache.typeURL {
   259  		value <- nil
   260  		return nil
   261  	}
   262  	// If the version is not up to date, check whether any requested resource has
   263  	// been updated between the last version and the current version. This avoids the problem
   264  	// of sending empty updates whenever an irrelevant resource changes.
   265  	stale := false
   266  	var staleResources []string // empty means all
   267  
   268  	// strip version prefix if it is present
   269  	var lastVersion uint64
   270  	var err error
   271  	if strings.HasPrefix(request.VersionInfo, cache.versionPrefix) {
   272  		lastVersion, err = strconv.ParseUint(request.VersionInfo[len(cache.versionPrefix):], 0, 64)
   273  	} else {
   274  		err = errors.New("mis-matched version prefix")
   275  	}
   276  
   277  	cache.mu.Lock()
   278  	defer cache.mu.Unlock()
   279  
   280  	if err != nil {
   281  		stale = true
   282  		staleResources = request.ResourceNames
   283  	} else if len(request.ResourceNames) == 0 {
   284  		stale = lastVersion != cache.version
   285  	} else {
   286  		for _, name := range request.ResourceNames {
   287  			// When a resource is removed, its version defaults 0 and it is not considered stale.
   288  			if lastVersion < cache.versionVector[name] {
   289  				stale = true
   290  				staleResources = append(staleResources, name)
   291  			}
   292  		}
   293  	}
   294  	if stale {
   295  		cache.respond(value, staleResources)
   296  		return nil
   297  	}
   298  	// Create open watches since versions are up to date.
   299  	if len(request.ResourceNames) == 0 {
   300  		cache.watchAll[value] = struct{}{}
   301  		return func() {
   302  			cache.mu.Lock()
   303  			defer cache.mu.Unlock()
   304  			delete(cache.watchAll, value)
   305  		}
   306  	}
   307  	for _, name := range request.ResourceNames {
   308  		set, exists := cache.watches[name]
   309  		if !exists {
   310  			set = make(watches)
   311  			cache.watches[name] = set
   312  		}
   313  		set[value] = struct{}{}
   314  	}
   315  	return func() {
   316  		cache.mu.Lock()
   317  		defer cache.mu.Unlock()
   318  		for _, name := range request.ResourceNames {
   319  			set, exists := cache.watches[name]
   320  			if exists {
   321  				delete(set, value)
   322  			}
   323  			if len(set) == 0 {
   324  				delete(cache.watches, name)
   325  			}
   326  		}
   327  	}
   328  }
   329  
   330  func (cache *LinearCache) CreateDeltaWatch(request *DeltaRequest, state stream.StreamState, value chan DeltaResponse) func() {
   331  	cache.mu.Lock()
   332  	defer cache.mu.Unlock()
   333  
   334  	response := cache.respondDelta(request, value, state)
   335  
   336  	// if respondDelta returns nil this means that there is no change in any resource version
   337  	// create a new watch accordingly
   338  	if response == nil {
   339  		watchID := cache.nextDeltaWatchID()
   340  		if cache.log != nil {
   341  			cache.log.Infof("[linear cache] open delta watch ID:%d for %s Resources:%v, system version %q", watchID,
   342  				cache.typeURL, state.GetResourceVersions(), cache.getVersion())
   343  		}
   344  
   345  		cache.deltaWatches[watchID] = DeltaResponseWatch{Request: request, Response: value, StreamState: state}
   346  
   347  		return cache.cancelDeltaWatch(watchID)
   348  	}
   349  
   350  	return nil
   351  }
   352  
   353  func (cache *LinearCache) updateVersionMap(modified map[string]struct{}) error {
   354  	for name, r := range cache.resources {
   355  		// skip recalculating hash for the resoces that weren't modified
   356  		if _, ok := modified[name]; !ok {
   357  			continue
   358  		}
   359  		// hash our verison in here and build the version map
   360  		marshaledResource, err := MarshalResource(r)
   361  		if err != nil {
   362  			return err
   363  		}
   364  		v := HashResource(marshaledResource)
   365  		if v == "" {
   366  			return errors.New("failed to build resource version")
   367  		}
   368  
   369  		cache.versionMap[GetResourceName(r)] = v
   370  	}
   371  	for name := range modified {
   372  		if r, ok := cache.resources[name]; !ok {
   373  			delete(cache.versionMap, GetResourceName(r))
   374  		}
   375  	}
   376  	return nil
   377  }
   378  
   379  func (cache *LinearCache) getVersion() string {
   380  	return cache.versionPrefix + strconv.FormatUint(cache.version, 10)
   381  }
   382  
   383  // cancellation function for cleaning stale watches
   384  func (cache *LinearCache) cancelDeltaWatch(watchID int64) func() {
   385  	return func() {
   386  		cache.mu.Lock()
   387  		defer cache.mu.Unlock()
   388  		delete(cache.deltaWatches, watchID)
   389  	}
   390  }
   391  
   392  func (cache *LinearCache) nextDeltaWatchID() int64 {
   393  	return atomic.AddInt64(&cache.deltaWatchCount, 1)
   394  }
   395  
   396  //goland:noinspection GoUnusedParameter
   397  func (cache *LinearCache) Fetch(ctx context.Context, request *Request) (Response, error) {
   398  	return nil, errors.New("not implemented")
   399  }
   400  
   401  // NumWatches Number of active watches for a resource name.
   402  func (cache *LinearCache) NumWatches(name string) int {
   403  	cache.mu.RLock()
   404  	defer cache.mu.RUnlock()
   405  	return len(cache.watches[name]) + len(cache.watchAll)
   406  }
   407  
   408  // NumDeltaWatches Number of active delta watches.
   409  func (cache *LinearCache) NumDeltaWatches() int {
   410  	cache.mu.Lock()
   411  	defer cache.mu.Unlock()
   412  	return len(cache.deltaWatches)
   413  }