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 }