github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/grpc/xds/internal/xdsclient/pubsub/update.go (about) 1 /* 2 * 3 * Copyright 2021 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package pubsub 19 20 import ( 21 "github.com/golang/protobuf/proto" 22 "github.com/hxx258456/ccgo/grpc/internal/pretty" 23 "github.com/hxx258456/ccgo/grpc/xds/internal/xdsclient/xdsresource" 24 ) 25 26 type watcherInfoWithUpdate struct { 27 wi *watchInfo 28 update interface{} 29 err error 30 } 31 32 // scheduleCallback should only be called by methods of watchInfo, which checks 33 // for watcher states and maintain consistency. 34 func (pb *Pubsub) scheduleCallback(wi *watchInfo, update interface{}, err error) { 35 pb.updateCh.Put(&watcherInfoWithUpdate{ 36 wi: wi, 37 update: update, 38 err: err, 39 }) 40 } 41 42 func (pb *Pubsub) callCallback(wiu *watcherInfoWithUpdate) { 43 pb.mu.Lock() 44 // Use a closure to capture the callback and type assertion, to save one 45 // more switch case. 46 // 47 // The callback must be called without pb.mu. Otherwise if the callback calls 48 // another watch() inline, it will cause a deadlock. This leaves a small 49 // window that a watcher's callback could be called after the watcher is 50 // canceled, and the user needs to take care of it. 51 var ccb func() 52 switch wiu.wi.rType { 53 case xdsresource.ListenerResource: 54 if s, ok := pb.ldsWatchers[wiu.wi.target]; ok && s[wiu.wi] { 55 ccb = func() { wiu.wi.ldsCallback(wiu.update.(xdsresource.ListenerUpdate), wiu.err) } 56 } 57 case xdsresource.RouteConfigResource: 58 if s, ok := pb.rdsWatchers[wiu.wi.target]; ok && s[wiu.wi] { 59 ccb = func() { wiu.wi.rdsCallback(wiu.update.(xdsresource.RouteConfigUpdate), wiu.err) } 60 } 61 case xdsresource.ClusterResource: 62 if s, ok := pb.cdsWatchers[wiu.wi.target]; ok && s[wiu.wi] { 63 ccb = func() { wiu.wi.cdsCallback(wiu.update.(xdsresource.ClusterUpdate), wiu.err) } 64 } 65 case xdsresource.EndpointsResource: 66 if s, ok := pb.edsWatchers[wiu.wi.target]; ok && s[wiu.wi] { 67 ccb = func() { wiu.wi.edsCallback(wiu.update.(xdsresource.EndpointsUpdate), wiu.err) } 68 } 69 } 70 pb.mu.Unlock() 71 72 if ccb != nil { 73 ccb() 74 } 75 } 76 77 // NewListeners is called when there's a new LDS update. 78 func (pb *Pubsub) NewListeners(updates map[string]xdsresource.ListenerUpdateErrTuple, metadata xdsresource.UpdateMetadata) { 79 pb.mu.Lock() 80 defer pb.mu.Unlock() 81 82 for name, uErr := range updates { 83 if s, ok := pb.ldsWatchers[name]; ok { 84 if uErr.Err != nil { 85 // On error, keep previous version for each resource. But update 86 // status and error. 87 mdCopy := pb.ldsMD[name] 88 mdCopy.ErrState = metadata.ErrState 89 mdCopy.Status = metadata.Status 90 pb.ldsMD[name] = mdCopy 91 for wi := range s { 92 wi.newError(uErr.Err) 93 } 94 continue 95 } 96 // If we get here, it means that the update is a valid one. Notify 97 // watchers only if this is a first time update or it is different 98 // from the one currently cached. 99 if cur, ok := pb.ldsCache[name]; !ok || !proto.Equal(cur.Raw, uErr.Update.Raw) { 100 for wi := range s { 101 wi.newUpdate(uErr.Update) 102 } 103 } 104 // Sync cache. 105 pb.logger.Debugf("LDS resource with name %v, value %+v added to cache", name, pretty.ToJSON(uErr)) 106 pb.ldsCache[name] = uErr.Update 107 // Set status to ACK, and clear error state. The metadata might be a 108 // NACK metadata because some other resources in the same response 109 // are invalid. 110 mdCopy := metadata 111 mdCopy.Status = xdsresource.ServiceStatusACKed 112 mdCopy.ErrState = nil 113 if metadata.ErrState != nil { 114 mdCopy.Version = metadata.ErrState.Version 115 } 116 pb.ldsMD[name] = mdCopy 117 } 118 } 119 // Resources not in the new update were removed by the server, so delete 120 // them. 121 for name := range pb.ldsCache { 122 if _, ok := updates[name]; !ok { 123 // If resource exists in cache, but not in the new update, delete 124 // the resource from cache, and also send an resource not found 125 // error to indicate resource removed. 126 delete(pb.ldsCache, name) 127 pb.ldsMD[name] = xdsresource.UpdateMetadata{Status: xdsresource.ServiceStatusNotExist} 128 for wi := range pb.ldsWatchers[name] { 129 wi.resourceNotFound() 130 } 131 } 132 } 133 // When LDS resource is removed, we don't delete corresponding RDS cached 134 // data. The RDS watch will be canceled, and cache entry is removed when the 135 // last watch is canceled. 136 } 137 138 // NewRouteConfigs is called when there's a new RDS update. 139 func (pb *Pubsub) NewRouteConfigs(updates map[string]xdsresource.RouteConfigUpdateErrTuple, metadata xdsresource.UpdateMetadata) { 140 pb.mu.Lock() 141 defer pb.mu.Unlock() 142 143 // If no error received, the status is ACK. 144 for name, uErr := range updates { 145 if s, ok := pb.rdsWatchers[name]; ok { 146 if uErr.Err != nil { 147 // On error, keep previous version for each resource. But update 148 // status and error. 149 mdCopy := pb.rdsMD[name] 150 mdCopy.ErrState = metadata.ErrState 151 mdCopy.Status = metadata.Status 152 pb.rdsMD[name] = mdCopy 153 for wi := range s { 154 wi.newError(uErr.Err) 155 } 156 continue 157 } 158 // If we get here, it means that the update is a valid one. Notify 159 // watchers only if this is a first time update or it is different 160 // from the one currently cached. 161 if cur, ok := pb.rdsCache[name]; !ok || !proto.Equal(cur.Raw, uErr.Update.Raw) { 162 for wi := range s { 163 wi.newUpdate(uErr.Update) 164 } 165 } 166 // Sync cache. 167 pb.logger.Debugf("RDS resource with name %v, value %+v added to cache", name, pretty.ToJSON(uErr)) 168 pb.rdsCache[name] = uErr.Update 169 // Set status to ACK, and clear error state. The metadata might be a 170 // NACK metadata because some other resources in the same response 171 // are invalid. 172 mdCopy := metadata 173 mdCopy.Status = xdsresource.ServiceStatusACKed 174 mdCopy.ErrState = nil 175 if metadata.ErrState != nil { 176 mdCopy.Version = metadata.ErrState.Version 177 } 178 pb.rdsMD[name] = mdCopy 179 } 180 } 181 } 182 183 // NewClusters is called when there's a new CDS update. 184 func (pb *Pubsub) NewClusters(updates map[string]xdsresource.ClusterUpdateErrTuple, metadata xdsresource.UpdateMetadata) { 185 pb.mu.Lock() 186 defer pb.mu.Unlock() 187 188 for name, uErr := range updates { 189 if s, ok := pb.cdsWatchers[name]; ok { 190 if uErr.Err != nil { 191 // On error, keep previous version for each resource. But update 192 // status and error. 193 mdCopy := pb.cdsMD[name] 194 mdCopy.ErrState = metadata.ErrState 195 mdCopy.Status = metadata.Status 196 pb.cdsMD[name] = mdCopy 197 for wi := range s { 198 // Send the watcher the individual error, instead of the 199 // overall combined error from the metadata.ErrState. 200 wi.newError(uErr.Err) 201 } 202 continue 203 } 204 // If we get here, it means that the update is a valid one. Notify 205 // watchers only if this is a first time update or it is different 206 // from the one currently cached. 207 if cur, ok := pb.cdsCache[name]; !ok || !proto.Equal(cur.Raw, uErr.Update.Raw) { 208 for wi := range s { 209 wi.newUpdate(uErr.Update) 210 } 211 } 212 // Sync cache. 213 pb.logger.Debugf("CDS resource with name %v, value %+v added to cache", name, pretty.ToJSON(uErr)) 214 pb.cdsCache[name] = uErr.Update 215 // Set status to ACK, and clear error state. The metadata might be a 216 // NACK metadata because some other resources in the same response 217 // are invalid. 218 mdCopy := metadata 219 mdCopy.Status = xdsresource.ServiceStatusACKed 220 mdCopy.ErrState = nil 221 if metadata.ErrState != nil { 222 mdCopy.Version = metadata.ErrState.Version 223 } 224 pb.cdsMD[name] = mdCopy 225 } 226 } 227 // Resources not in the new update were removed by the server, so delete 228 // them. 229 for name := range pb.cdsCache { 230 if _, ok := updates[name]; !ok { 231 // If resource exists in cache, but not in the new update, delete it 232 // from cache, and also send an resource not found error to indicate 233 // resource removed. 234 delete(pb.cdsCache, name) 235 pb.ldsMD[name] = xdsresource.UpdateMetadata{Status: xdsresource.ServiceStatusNotExist} 236 for wi := range pb.cdsWatchers[name] { 237 wi.resourceNotFound() 238 } 239 } 240 } 241 // When CDS resource is removed, we don't delete corresponding EDS cached 242 // data. The EDS watch will be canceled, and cache entry is removed when the 243 // last watch is canceled. 244 } 245 246 // NewEndpoints is called when there's anew EDS update. 247 func (pb *Pubsub) NewEndpoints(updates map[string]xdsresource.EndpointsUpdateErrTuple, metadata xdsresource.UpdateMetadata) { 248 pb.mu.Lock() 249 defer pb.mu.Unlock() 250 251 for name, uErr := range updates { 252 if s, ok := pb.edsWatchers[name]; ok { 253 if uErr.Err != nil { 254 // On error, keep previous version for each resource. But update 255 // status and error. 256 mdCopy := pb.edsMD[name] 257 mdCopy.ErrState = metadata.ErrState 258 mdCopy.Status = metadata.Status 259 pb.edsMD[name] = mdCopy 260 for wi := range s { 261 // Send the watcher the individual error, instead of the 262 // overall combined error from the metadata.ErrState. 263 wi.newError(uErr.Err) 264 } 265 continue 266 } 267 // If we get here, it means that the update is a valid one. Notify 268 // watchers only if this is a first time update or it is different 269 // from the one currently cached. 270 if cur, ok := pb.edsCache[name]; !ok || !proto.Equal(cur.Raw, uErr.Update.Raw) { 271 for wi := range s { 272 wi.newUpdate(uErr.Update) 273 } 274 } 275 // Sync cache. 276 pb.logger.Debugf("EDS resource with name %v, value %+v added to cache", name, pretty.ToJSON(uErr)) 277 pb.edsCache[name] = uErr.Update 278 // Set status to ACK, and clear error state. The metadata might be a 279 // NACK metadata because some other resources in the same response 280 // are invalid. 281 mdCopy := metadata 282 mdCopy.Status = xdsresource.ServiceStatusACKed 283 mdCopy.ErrState = nil 284 if metadata.ErrState != nil { 285 mdCopy.Version = metadata.ErrState.Version 286 } 287 pb.edsMD[name] = mdCopy 288 } 289 } 290 } 291 292 // NewConnectionError is called by the underlying xdsAPIClient when it receives 293 // a connection error. The error will be forwarded to all the resource watchers. 294 func (pb *Pubsub) NewConnectionError(err error) { 295 pb.mu.Lock() 296 defer pb.mu.Unlock() 297 298 for _, s := range pb.ldsWatchers { 299 for wi := range s { 300 wi.newError(xdsresource.NewErrorf(xdsresource.ErrorTypeConnection, "xds: error received from xDS stream: %v", err)) 301 } 302 } 303 for _, s := range pb.rdsWatchers { 304 for wi := range s { 305 wi.newError(xdsresource.NewErrorf(xdsresource.ErrorTypeConnection, "xds: error received from xDS stream: %v", err)) 306 } 307 } 308 for _, s := range pb.cdsWatchers { 309 for wi := range s { 310 wi.newError(xdsresource.NewErrorf(xdsresource.ErrorTypeConnection, "xds: error received from xDS stream: %v", err)) 311 } 312 } 313 for _, s := range pb.edsWatchers { 314 for wi := range s { 315 wi.newError(xdsresource.NewErrorf(xdsresource.ErrorTypeConnection, "xds: error received from xDS stream: %v", err)) 316 } 317 } 318 }