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 }