github.com/imran-kn/cilium-fork@v1.6.9/pkg/envoy/xds/watcher.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 "fmt" 20 "sync" 21 "time" 22 23 "github.com/cilium/cilium/pkg/lock" 24 "github.com/cilium/cilium/pkg/logging/logfields" 25 26 "github.com/sirupsen/logrus" 27 ) 28 29 // ResourceWatcher watches and retrieves new versions of resources from a 30 // resource set. 31 // ResourceWatcher implements ResourceVersionObserver to get notified when new 32 // resource versions are available in the set. 33 type ResourceWatcher struct { 34 // typeURL is the URL that uniquely identifies the resource type. 35 typeURL string 36 37 // resourceSet is the set of resources to watch. 38 resourceSet ResourceSource 39 40 // version is the current version of the resources. Updated in calls to 41 // NotifyNewVersion. 42 // Versioning starts at 1. 43 version uint64 44 45 // versionLocker is used to lock all accesses to version. 46 versionLocker lock.Mutex 47 48 // versionCond is a condition that is broadcast whenever the source's 49 // current version is increased. 50 // versionCond is associated with versionLocker. 51 versionCond *sync.Cond 52 53 // resourceAccessTimeout is the timeout to use for any access to the 54 // resource set. 55 resourceAccessTimeout time.Duration 56 } 57 58 // NewResourceWatcher creates a new ResourceWatcher backed by the given 59 // resource set. 60 func NewResourceWatcher(typeURL string, resourceSet ResourceSource, resourceAccessTimeout time.Duration) *ResourceWatcher { 61 w := &ResourceWatcher{ 62 version: 1, 63 typeURL: typeURL, 64 resourceSet: resourceSet, 65 resourceAccessTimeout: resourceAccessTimeout, 66 } 67 w.versionCond = sync.NewCond(&w.versionLocker) 68 return w 69 } 70 71 func (w *ResourceWatcher) HandleNewResourceVersion(typeURL string, version uint64) { 72 w.versionLocker.Lock() 73 defer w.versionLocker.Unlock() 74 75 if typeURL != w.typeURL { 76 return 77 } 78 79 if version < w.version { 80 log.WithFields(logrus.Fields{ 81 logfields.XDSCachedVersion: version, 82 logfields.XDSTypeURL: typeURL, 83 }).Panicf(fmt.Sprintf("decreasing version number found for resources of type %s: %d < %d", 84 typeURL, version, w.version)) 85 } 86 w.version = version 87 88 w.versionCond.Broadcast() 89 } 90 91 // WatchResources watches for new versions of specific resources and sends them 92 // into the given out channel. 93 // 94 // A call to this method blocks until a version greater than lastVersion is 95 // available. Therefore, every call must be done in a separate goroutine. 96 // A watch can be canceled by canceling the given context. 97 // 98 // lastVersion is the last version successfully applied by the 99 // client; nil if this is the first request for resources. 100 // This method call must always close the out channel. 101 func (w *ResourceWatcher) WatchResources(ctx context.Context, typeURL string, lastVersion uint64, nodeIP string, 102 resourceNames []string, out chan<- *VersionedResources) { 103 defer close(out) 104 105 watchLog := log.WithFields(logrus.Fields{ 106 logfields.XDSAckedVersion: lastVersion, 107 logfields.XDSClientNode: nodeIP, 108 logfields.XDSTypeURL: typeURL, 109 }) 110 111 var res *VersionedResources 112 113 var waitVersion uint64 114 var waitForVersion bool 115 if lastVersion != 0 { 116 waitForVersion = true 117 waitVersion = lastVersion 118 } 119 120 for ctx.Err() == nil && res == nil { 121 w.versionLocker.Lock() 122 // If the client ACKed a version that we have never sent back, this 123 // indicates that this server restarted but the client survived and had 124 // received a higher version number from the previous server instance. 125 // Bump the resource set's version number to match the client's and 126 // send a response immediately. 127 if waitForVersion && w.version < waitVersion { 128 w.versionLocker.Unlock() 129 // Calling EnsureVersion will increase the version of the resource 130 // set, which in turn will callback w.HandleNewResourceVersion with 131 // that new version number. In order for that callback to not 132 // deadlock, temporarily unlock w.versionLocker. 133 // The w.HandleNewResourceVersion callback will update w.version to 134 // the new resource set version. 135 w.resourceSet.EnsureVersion(typeURL, waitVersion+1) 136 w.versionLocker.Lock() 137 } 138 139 // Re-check w.version, since it may have been modified by calling 140 // EnsureVersion above. 141 for ctx.Err() == nil && waitForVersion && w.version <= waitVersion { 142 watchLog.Debugf("current resource version is %d, waiting for it to become > %d", w.version, waitVersion) 143 w.versionCond.Wait() 144 } 145 // In case we need to loop again, wait for any version more recent than 146 // the current one. 147 waitForVersion = true 148 waitVersion = w.version 149 w.versionLocker.Unlock() 150 151 if ctx.Err() != nil { 152 break 153 } 154 155 subCtx, cancel := context.WithTimeout(ctx, w.resourceAccessTimeout) 156 var err error 157 watchLog.Debugf("getting %d resources from set", len(resourceNames)) 158 res, err = w.resourceSet.GetResources(subCtx, typeURL, lastVersion, nodeIP, resourceNames) 159 cancel() 160 161 if err != nil { 162 watchLog.WithError(err).Errorf("failed to query resources named: %v; terminating resource watch", resourceNames) 163 return 164 } 165 } 166 167 if res != nil { 168 // Resources have changed since the last version returned to the 169 // client. Send out the new version. 170 select { 171 case <-ctx.Done(): 172 case out <- res: 173 return 174 } 175 } 176 177 err := ctx.Err() 178 if err != nil { 179 switch err { 180 case context.Canceled: 181 watchLog.Debug("context canceled, terminating resource watch") 182 default: 183 watchLog.WithError(err).Error("context error, terminating resource watch") 184 } 185 } 186 }