github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/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 "strconv" 21 "strings" 22 "sync" 23 "sync/atomic" 24 25 "github.com/hxx258456/ccgo/go-control-plane/pkg/cache/types" 26 "github.com/hxx258456/ccgo/go-control-plane/pkg/log" 27 "github.com/hxx258456/ccgo/go-control-plane/pkg/server/stream/v3" 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 // 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 func WithLogger(log log.Logger) LinearCacheOption { 93 return func(cache *LinearCache) { 94 cache.log = log 95 } 96 } 97 98 // NewLinearCache creates a new cache. See the comments on the struct definition. 99 func NewLinearCache(typeURL string, opts ...LinearCacheOption) *LinearCache { 100 out := &LinearCache{ 101 typeURL: typeURL, 102 resources: make(map[string]types.Resource), 103 watches: make(map[string]watches), 104 watchAll: make(watches), 105 deltaWatches: make(map[int64]DeltaResponseWatch), 106 versionMap: make(map[string]string), 107 version: 0, 108 versionVector: make(map[string]uint64), 109 } 110 for _, opt := range opts { 111 opt(out) 112 } 113 return out 114 } 115 116 func (cache *LinearCache) respond(value chan Response, staleResources []string) { 117 var resources []types.ResourceWithTTL 118 // TODO: optimize the resources slice creations across different clients 119 if len(staleResources) == 0 { 120 resources = make([]types.ResourceWithTTL, 0, len(cache.resources)) 121 for _, resource := range cache.resources { 122 resources = append(resources, types.ResourceWithTTL{Resource: resource}) 123 } 124 } else { 125 resources = make([]types.ResourceWithTTL, 0, len(staleResources)) 126 for _, name := range staleResources { 127 resource := cache.resources[name] 128 if resource != nil { 129 resources = append(resources, types.ResourceWithTTL{Resource: resource}) 130 } 131 } 132 } 133 value <- &RawResponse{ 134 Request: &Request{TypeUrl: cache.typeURL}, 135 Resources: resources, 136 Version: cache.getVersion(), 137 } 138 } 139 140 func (cache *LinearCache) notifyAll(modified map[string]struct{}) { 141 // de-duplicate watches that need to be responded 142 notifyList := make(map[chan Response][]string) 143 for name := range modified { 144 for watch := range cache.watches[name] { 145 notifyList[watch] = append(notifyList[watch], name) 146 } 147 delete(cache.watches, name) 148 } 149 for value, stale := range notifyList { 150 cache.respond(value, stale) 151 } 152 for value := range cache.watchAll { 153 cache.respond(value, nil) 154 } 155 cache.watchAll = make(watches) 156 157 cache.updateVersionMap(modified) 158 159 for id, watch := range cache.deltaWatches { 160 res := cache.respondDelta(watch.Request, watch.Response, watch.StreamState) 161 if res != nil { 162 delete(cache.deltaWatches, id) 163 } 164 } 165 } 166 167 func (cache *LinearCache) respondDelta(request *DeltaRequest, value chan DeltaResponse, state stream.StreamState) *RawDeltaResponse { 168 resp := createDeltaResponse(context.Background(), request, state, resourceContainer{ 169 resourceMap: cache.resources, 170 versionMap: cache.versionMap, 171 systemVersion: cache.getVersion(), 172 }) 173 174 // Only send a response if there were changes 175 if len(resp.Resources) > 0 || len(resp.RemovedResources) > 0 { 176 if cache.log != nil { 177 cache.log.Debugf("[linear cache] node: %s, sending delta response with resources: %v removed resources %v wildcard: %t", 178 request.GetNode().GetId(), resp.Resources, resp.RemovedResources, state.IsWildcard()) 179 } 180 value <- resp 181 return resp 182 } 183 return nil 184 } 185 186 // UpdateResource updates a resource in the collection. 187 func (cache *LinearCache) UpdateResource(name string, res types.Resource) error { 188 if res == nil { 189 return errors.New("nil resource") 190 } 191 cache.mu.Lock() 192 defer cache.mu.Unlock() 193 194 cache.version++ 195 cache.versionVector[name] = cache.version 196 cache.resources[name] = res 197 198 // TODO: batch watch closures to prevent rapid updates 199 cache.notifyAll(map[string]struct{}{name: {}}) 200 201 return nil 202 } 203 204 // DeleteResource removes a resource in the collection. 205 func (cache *LinearCache) DeleteResource(name string) error { 206 cache.mu.Lock() 207 defer cache.mu.Unlock() 208 209 cache.version++ 210 delete(cache.versionVector, name) 211 delete(cache.resources, name) 212 213 // TODO: batch watch closures to prevent rapid updates 214 cache.notifyAll(map[string]struct{}{name: {}}) 215 return nil 216 } 217 218 // SetResources replaces current resources with a new set of resources. 219 // This function is useful for wildcard xDS subscriptions. 220 // This way watches that are subscribed to all resources are triggered only once regardless of how many resources are changed. 221 func (cache *LinearCache) SetResources(resources map[string]types.Resource) { 222 cache.mu.Lock() 223 defer cache.mu.Unlock() 224 225 cache.version++ 226 227 modified := map[string]struct{}{} 228 // Collect deleted resource names. 229 for name := range cache.resources { 230 if _, found := resources[name]; !found { 231 delete(cache.versionVector, name) 232 modified[name] = struct{}{} 233 } 234 } 235 236 cache.resources = resources 237 238 // Collect changed resource names. 239 // We assume all resources passed to SetResources are changed. 240 // Otherwise we would have to do proto.Equal on resources which is pretty expensive operation 241 for name := range resources { 242 cache.versionVector[name] = cache.version 243 modified[name] = struct{}{} 244 } 245 246 cache.notifyAll(modified) 247 } 248 249 // GetResources returns current resources stored in the cache 250 func (cache *LinearCache) GetResources() map[string]types.Resource { 251 cache.mu.RLock() 252 defer cache.mu.RUnlock() 253 return cache.resources 254 } 255 256 func (cache *LinearCache) CreateWatch(request *Request, value chan Response) func() { 257 if request.TypeUrl != cache.typeURL { 258 value <- nil 259 return nil 260 } 261 // If the version is not up to date, check whether any requested resource has 262 // been updated between the last version and the current version. This avoids the problem 263 // of sending empty updates whenever an irrelevant resource changes. 264 stale := false 265 staleResources := []string{} // empty means all 266 267 // strip version prefix if it is present 268 var lastVersion uint64 269 var err error 270 if strings.HasPrefix(request.VersionInfo, cache.versionPrefix) { 271 lastVersion, err = strconv.ParseUint(request.VersionInfo[len(cache.versionPrefix):], 0, 64) 272 } else { 273 err = errors.New("mis-matched version prefix") 274 } 275 276 cache.mu.Lock() 277 defer cache.mu.Unlock() 278 279 if err != nil { 280 stale = true 281 staleResources = request.ResourceNames 282 } else if len(request.ResourceNames) == 0 { 283 stale = lastVersion != cache.version 284 } else { 285 for _, name := range request.ResourceNames { 286 // When a resource is removed, its version defaults 0 and it is not considered stale. 287 if lastVersion < cache.versionVector[name] { 288 stale = true 289 staleResources = append(staleResources, name) 290 } 291 } 292 } 293 if stale { 294 cache.respond(value, staleResources) 295 return nil 296 } 297 // Create open watches since versions are up to date. 298 if len(request.ResourceNames) == 0 { 299 cache.watchAll[value] = struct{}{} 300 return func() { 301 cache.mu.Lock() 302 defer cache.mu.Unlock() 303 delete(cache.watchAll, value) 304 } 305 } 306 for _, name := range request.ResourceNames { 307 set, exists := cache.watches[name] 308 if !exists { 309 set = make(watches) 310 cache.watches[name] = set 311 } 312 set[value] = struct{}{} 313 } 314 return func() { 315 cache.mu.Lock() 316 defer cache.mu.Unlock() 317 for _, name := range request.ResourceNames { 318 set, exists := cache.watches[name] 319 if exists { 320 delete(set, value) 321 } 322 if len(set) == 0 { 323 delete(cache.watches, name) 324 } 325 } 326 } 327 } 328 329 func (cache *LinearCache) CreateDeltaWatch(request *DeltaRequest, state stream.StreamState, value chan DeltaResponse) func() { 330 cache.mu.Lock() 331 defer cache.mu.Unlock() 332 333 response := cache.respondDelta(request, value, state) 334 335 // if respondDelta returns nil this means that there is no change in any resource version 336 // create a new watch accordingly 337 if response == nil { 338 watchID := cache.nextDeltaWatchID() 339 if cache.log != nil { 340 cache.log.Infof("[linear cache] open delta watch ID:%d for %s Resources:%v, system version %q", watchID, 341 cache.typeURL, state.GetResourceVersions(), cache.getVersion()) 342 } 343 344 cache.deltaWatches[watchID] = DeltaResponseWatch{Request: request, Response: value, StreamState: state} 345 346 return cache.cancelDeltaWatch(watchID) 347 } 348 349 return nil 350 } 351 352 func (cache *LinearCache) updateVersionMap(modified map[string]struct{}) error { 353 for name, r := range cache.resources { 354 // skip recalculating hash for the resoces that weren't modified 355 if _, ok := modified[name]; !ok { 356 continue 357 } 358 // hash our verison in here and build the version map 359 marshaledResource, err := MarshalResource(r) 360 if err != nil { 361 return err 362 } 363 v := HashResource(marshaledResource) 364 if v == "" { 365 return errors.New("failed to build resource version") 366 } 367 368 cache.versionMap[GetResourceName(r)] = v 369 } 370 for name := range modified { 371 if r, ok := cache.resources[name]; !ok { 372 delete(cache.versionMap, GetResourceName(r)) 373 } 374 } 375 return nil 376 } 377 378 func (cache *LinearCache) getVersion() string { 379 return cache.versionPrefix + strconv.FormatUint(cache.version, 10) 380 } 381 382 // cancellation function for cleaning stale watches 383 func (cache *LinearCache) cancelDeltaWatch(watchID int64) func() { 384 return func() { 385 cache.mu.Lock() 386 defer cache.mu.Unlock() 387 delete(cache.deltaWatches, watchID) 388 } 389 } 390 391 func (cache *LinearCache) nextDeltaWatchID() int64 { 392 return atomic.AddInt64(&cache.deltaWatchCount, 1) 393 } 394 395 func (cache *LinearCache) Fetch(ctx context.Context, request *Request) (Response, error) { 396 return nil, errors.New("not implemented") 397 } 398 399 // Number of active watches for a resource name. 400 func (cache *LinearCache) NumWatches(name string) int { 401 cache.mu.RLock() 402 defer cache.mu.RUnlock() 403 return len(cache.watches[name]) + len(cache.watchAll) 404 } 405 406 // Number of active delta watches. 407 func (cache *LinearCache) NumDeltaWatches() int { 408 cache.mu.Lock() 409 defer cache.mu.Unlock() 410 return len(cache.deltaWatches) 411 }