google.golang.org/grpc@v1.72.2/xds/internal/xdsclient/authority.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 xdsclient 19 20 import ( 21 "context" 22 "fmt" 23 "sync" 24 "sync/atomic" 25 26 "google.golang.org/grpc/experimental/stats" 27 "google.golang.org/grpc/grpclog" 28 igrpclog "google.golang.org/grpc/internal/grpclog" 29 "google.golang.org/grpc/internal/grpcsync" 30 "google.golang.org/grpc/internal/xds/bootstrap" 31 "google.golang.org/grpc/xds/internal/xdsclient/transport/ads" 32 "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" 33 "google.golang.org/protobuf/types/known/anypb" 34 "google.golang.org/protobuf/types/known/timestamppb" 35 36 v3adminpb "github.com/envoyproxy/go-control-plane/envoy/admin/v3" 37 v3statuspb "github.com/envoyproxy/go-control-plane/envoy/service/status/v3" 38 ) 39 40 type resourceState struct { 41 watchers map[xdsresource.ResourceWatcher]bool // Set of watchers for this resource. 42 cache xdsresource.ResourceData // Most recent ACKed update for this resource. 43 md xdsresource.UpdateMetadata // Metadata for the most recent update. 44 deletionIgnored bool // True, if resource deletion was ignored for a prior update. 45 xdsChannelConfigs map[*xdsChannelWithConfig]bool // Set of xdsChannels where this resource is subscribed. 46 } 47 48 // xdsChannelForADS is used to acquire a reference to an xdsChannel. This 49 // functionality is provided by the xdsClient. 50 // 51 // The arguments to the function are as follows: 52 // - the server config for the xdsChannel 53 // - the calling authority on which a set of callbacks are invoked by the 54 // xdsChannel on ADS stream events 55 // 56 // Returns a reference to the xdsChannel and a function to release the same. A 57 // non-nil error is returned if the channel creation fails and the first two 58 // return values are meaningless in this case. 59 type xdsChannelForADS func(*bootstrap.ServerConfig, *authority) (*xdsChannel, func(), error) 60 61 // xdsChannelWithConfig is a struct that holds an xdsChannel and its associated 62 // ServerConfig, along with a cleanup function to release the xdsChannel. 63 type xdsChannelWithConfig struct { 64 channel *xdsChannel 65 serverConfig *bootstrap.ServerConfig 66 cleanup func() 67 } 68 69 // authority provides the functionality required to communicate with a 70 // management server corresponding to an authority name specified in the 71 // bootstrap configuration. 72 // 73 // It holds references to one or more xdsChannels, one for each server 74 // configuration in the bootstrap, to allow fallback from a primary management 75 // server to a secondary management server. Authorities that contain similar 76 // server configuration entries will end up sharing the xdsChannel for that 77 // server configuration. The xdsChannels are owned and managed by the xdsClient. 78 // 79 // It also contains a cache of resource state for resources requested from 80 // management server(s). This cache contains the list of registered watchers and 81 // the most recent resource configuration received from the management server. 82 type authority struct { 83 // The following fields are initialized at creation time and are read-only 84 // afterwards, and therefore don't need to be protected with a mutex. 85 name string // Name of the authority from bootstrap configuration. 86 watcherCallbackSerializer *grpcsync.CallbackSerializer // Serializer to run watcher callbacks, owned by the xDS client implementation. 87 getChannelForADS xdsChannelForADS // Function to get an xdsChannel for ADS, provided by the xDS client implementation. 88 xdsClientSerializer *grpcsync.CallbackSerializer // Serializer to run call ins from the xDS client, owned by this authority. 89 xdsClientSerializerClose func() // Function to close the above serializer. 90 logger *igrpclog.PrefixLogger // Logger for this authority. 91 target string // The gRPC Channel target. 92 metricsRecorder stats.MetricsRecorder // The metrics recorder used for emitting metrics. 93 94 // The below defined fields must only be accessed in the context of the 95 // serializer callback, owned by this authority. 96 97 // A two level map containing the state of all the resources being watched. 98 // 99 // The first level map key is the ResourceType (Listener, Route etc). This 100 // allows us to have a single map for all resources instead of having per 101 // resource-type maps. 102 // 103 // The second level map key is the resource name, with the value being the 104 // actual state of the resource. 105 resources map[xdsresource.Type]map[string]*resourceState 106 107 // An ordered list of xdsChannels corresponding to the list of server 108 // configurations specified for this authority in the bootstrap. The 109 // ordering specifies the order in which these channels are preferred for 110 // fallback. 111 xdsChannelConfigs []*xdsChannelWithConfig 112 113 // The current active xdsChannel. Here, active does not mean that the 114 // channel has a working connection to the server. It simply points to the 115 // channel that we are trying to work with, based on fallback logic. 116 activeXDSChannel *xdsChannelWithConfig 117 } 118 119 // authorityBuildOptions wraps arguments required to create a new authority. 120 type authorityBuildOptions struct { 121 serverConfigs bootstrap.ServerConfigs // Server configs for the authority 122 name string // Name of the authority 123 serializer *grpcsync.CallbackSerializer // Callback serializer for invoking watch callbacks 124 getChannelForADS xdsChannelForADS // Function to acquire a reference to an xdsChannel 125 logPrefix string // Prefix for logging 126 target string // Target for the gRPC Channel that owns xDS Client/Authority 127 metricsRecorder stats.MetricsRecorder // metricsRecorder to emit metrics 128 } 129 130 // newAuthority creates a new authority instance with the provided 131 // configuration. The authority is responsible for managing the state of 132 // resources requested from the management server, as well as acquiring and 133 // releasing references to channels used to communicate with the management 134 // server. 135 // 136 // Note that no channels to management servers are created at this time. Instead 137 // a channel to the first server configuration is created when the first watch 138 // is registered, and more channels are created as needed by the fallback logic. 139 func newAuthority(args authorityBuildOptions) *authority { 140 ctx, cancel := context.WithCancel(context.Background()) 141 l := grpclog.Component("xds") 142 logPrefix := args.logPrefix + fmt.Sprintf("[authority %q] ", args.name) 143 ret := &authority{ 144 name: args.name, 145 watcherCallbackSerializer: args.serializer, 146 getChannelForADS: args.getChannelForADS, 147 xdsClientSerializer: grpcsync.NewCallbackSerializer(ctx), 148 xdsClientSerializerClose: cancel, 149 logger: igrpclog.NewPrefixLogger(l, logPrefix), 150 resources: make(map[xdsresource.Type]map[string]*resourceState), 151 target: args.target, 152 metricsRecorder: args.metricsRecorder, 153 } 154 155 // Create an ordered list of xdsChannels with their server configs. The 156 // actual channel to the first server configuration is created when the 157 // first watch is registered, and channels to other server configurations 158 // are created as needed to support fallback. 159 for _, sc := range args.serverConfigs { 160 ret.xdsChannelConfigs = append(ret.xdsChannelConfigs, &xdsChannelWithConfig{serverConfig: sc}) 161 } 162 return ret 163 } 164 165 // adsStreamFailure is called to notify the authority about an ADS stream 166 // failure on an xdsChannel to the management server identified by the provided 167 // server config. The error is forwarded to all the resource watchers. 168 // 169 // This method is called by the xDS client implementation (on all interested 170 // authorities) when a stream error is reported by an xdsChannel. 171 // 172 // Errors of type xdsresource.ErrTypeStreamFailedAfterRecv are ignored. 173 func (a *authority) adsStreamFailure(serverConfig *bootstrap.ServerConfig, err error) { 174 a.xdsClientSerializer.TrySchedule(func(context.Context) { 175 a.handleADSStreamFailure(serverConfig, err) 176 }) 177 } 178 179 // Handles ADS stream failure by invoking watch callbacks and triggering 180 // fallback if the associated conditions are met. 181 // 182 // Only executed in the context of a serializer callback. 183 func (a *authority) handleADSStreamFailure(serverConfig *bootstrap.ServerConfig, err error) { 184 if a.logger.V(2) { 185 a.logger.Infof("Connection to server %s failed with error: %v", serverConfig, err) 186 } 187 188 // We do not consider it an error if the ADS stream was closed after having 189 // received a response on the stream. This is because there are legitimate 190 // reasons why the server may need to close the stream during normal 191 // operations, such as needing to rebalance load or the underlying 192 // connection hitting its max connection age limit. See gRFC A57 for more 193 // details. 194 if xdsresource.ErrType(err) == xdsresource.ErrTypeStreamFailedAfterRecv { 195 a.logger.Warningf("Watchers not notified since ADS stream failed after having received at least one response: %v", err) 196 return 197 } 198 199 // Two conditions need to be met for fallback to be triggered: 200 // 1. There is a connectivity failure on the ADS stream, as described in 201 // gRFC A57. For us, this means that the ADS stream was closed before the 202 // first server response was received. We already checked that condition 203 // earlier in this method. 204 // 2. There is at least one watcher for a resource that is not cached. 205 // Cached resources include ones that 206 // - have been successfully received and can be used. 207 // - are considered non-existent according to xDS Protocol Specification. 208 if !a.watcherExistsForUncachedResource() { 209 if a.logger.V(2) { 210 a.logger.Infof("No watchers for uncached resources. Not triggering fallback") 211 } 212 // Since we are not triggering fallback, propagate the connectivity 213 // error to all watchers and return early. 214 a.propagateConnectivityErrorToAllWatchers(err) 215 return 216 } 217 218 // Attempt to fallback to servers with lower priority than the failing one. 219 currentServerIdx := a.serverIndexForConfig(serverConfig) 220 for i := currentServerIdx + 1; i < len(a.xdsChannelConfigs); i++ { 221 if a.fallbackToServer(a.xdsChannelConfigs[i]) { 222 // Since we have successfully triggered fallback, we don't have to 223 // notify watchers about the connectivity error. 224 return 225 } 226 } 227 228 // Having exhausted all available servers, we must notify watchers of the 229 // connectivity error - A71. 230 a.propagateConnectivityErrorToAllWatchers(err) 231 } 232 233 // propagateConnectivityErrorToAllWatchers propagates the given connection error 234 // to all watchers of all resources. 235 // 236 // Only executed in the context of a serializer callback. 237 func (a *authority) propagateConnectivityErrorToAllWatchers(err error) { 238 for _, rType := range a.resources { 239 for _, state := range rType { 240 for watcher := range state.watchers { 241 a.watcherCallbackSerializer.TrySchedule(func(context.Context) { 242 watcher.OnError(xdsresource.NewErrorf(xdsresource.ErrorTypeConnection, "xds: error received from xDS stream: %v", err), func() {}) 243 }) 244 } 245 } 246 } 247 } 248 249 // serverIndexForConfig returns the index of the xdsChannelConfig matching the 250 // provided server config, panicking if no match is found (which indicates a 251 // programming error). 252 func (a *authority) serverIndexForConfig(sc *bootstrap.ServerConfig) int { 253 for i, cfg := range a.xdsChannelConfigs { 254 if cfg.serverConfig.Equal(sc) { 255 return i 256 } 257 } 258 panic(fmt.Sprintf("no server config matching %v found", sc)) 259 } 260 261 // Determines the server to fallback to and triggers fallback to the same. If 262 // required, creates an xdsChannel to that server, and re-subscribes to all 263 // existing resources. 264 // 265 // Only executed in the context of a serializer callback. 266 func (a *authority) fallbackToServer(xc *xdsChannelWithConfig) bool { 267 if a.logger.V(2) { 268 a.logger.Infof("Attempting to initiate fallback to server %q", xc.serverConfig) 269 } 270 271 if xc.channel != nil { 272 if a.logger.V(2) { 273 a.logger.Infof("Channel to the next server in the list %q already exists", xc.serverConfig) 274 } 275 return false 276 } 277 278 channel, cleanup, err := a.getChannelForADS(xc.serverConfig, a) 279 if err != nil { 280 a.logger.Errorf("Failed to create xDS channel: %v", err) 281 return false 282 } 283 xc.channel = channel 284 xc.cleanup = cleanup 285 a.activeXDSChannel = xc 286 287 // Subscribe to all existing resources from the new management server. 288 for typ, resources := range a.resources { 289 for name, state := range resources { 290 if a.logger.V(2) { 291 a.logger.Infof("Resubscribing to resource of type %q and name %q", typ.TypeName(), name) 292 } 293 xc.channel.subscribe(typ, name) 294 295 // Add the new channel to the list of xdsChannels from which this 296 // resource has been requested from. Retain the cached resource and 297 // the set of existing watchers (and other metadata fields) in the 298 // resource state. 299 state.xdsChannelConfigs[xc] = true 300 } 301 } 302 return true 303 } 304 305 // adsResourceUpdate is called to notify the authority about a resource update 306 // received on the ADS stream. 307 // 308 // This method is called by the xDS client implementation (on all interested 309 // authorities) when a stream error is reported by an xdsChannel. 310 func (a *authority) adsResourceUpdate(serverConfig *bootstrap.ServerConfig, rType xdsresource.Type, updates map[string]ads.DataAndErrTuple, md xdsresource.UpdateMetadata, onDone func()) { 311 a.xdsClientSerializer.TrySchedule(func(context.Context) { 312 a.handleADSResourceUpdate(serverConfig, rType, updates, md, onDone) 313 }) 314 } 315 316 // handleADSResourceUpdate processes an update from the xDS client, updating the 317 // resource cache and notifying any registered watchers of the update. 318 // 319 // If the update is received from a higher priority xdsChannel that was 320 // previously down, we revert to it and close all lower priority xdsChannels. 321 // 322 // Once the update has been processed by all watchers, the authority is expected 323 // to invoke the onDone callback. 324 // 325 // Only executed in the context of a serializer callback. 326 func (a *authority) handleADSResourceUpdate(serverConfig *bootstrap.ServerConfig, rType xdsresource.Type, updates map[string]ads.DataAndErrTuple, md xdsresource.UpdateMetadata, onDone func()) { 327 a.handleRevertingToPrimaryOnUpdate(serverConfig) 328 329 // We build a list of callback funcs to invoke, and invoke them at the end 330 // of this method instead of inline (when handling the update for a 331 // particular resource), because we want to make sure that all calls to 332 // increment watcherCnt happen before any callbacks are invoked. This will 333 // ensure that the onDone callback is never invoked before all watcher 334 // callbacks are invoked, and the watchers have processed the update. 335 watcherCnt := new(atomic.Int64) 336 done := func() { 337 if watcherCnt.Add(-1) == 0 { 338 onDone() 339 } 340 } 341 funcsToSchedule := []func(context.Context){} 342 defer func() { 343 if len(funcsToSchedule) == 0 { 344 // When there are no watchers for the resources received as part of 345 // this update, invoke onDone explicitly to unblock the next read on 346 // the ADS stream. 347 onDone() 348 return 349 } 350 for _, f := range funcsToSchedule { 351 a.watcherCallbackSerializer.ScheduleOr(f, onDone) 352 } 353 }() 354 355 resourceStates := a.resources[rType] 356 for name, uErr := range updates { 357 state, ok := resourceStates[name] 358 if !ok { 359 continue 360 } 361 362 // On error, keep previous version of the resource. But update status 363 // and error. 364 if uErr.Err != nil { 365 xdsClientResourceUpdatesInvalidMetric.Record(a.metricsRecorder, 1, a.target, serverConfig.ServerURI(), rType.TypeName()) 366 state.md.ErrState = md.ErrState 367 state.md.Status = md.Status 368 for watcher := range state.watchers { 369 watcher := watcher 370 err := uErr.Err 371 watcherCnt.Add(1) 372 funcsToSchedule = append(funcsToSchedule, func(context.Context) { watcher.OnError(err, done) }) 373 } 374 continue 375 } 376 377 xdsClientResourceUpdatesValidMetric.Record(a.metricsRecorder, 1, a.target, serverConfig.ServerURI(), rType.TypeName()) 378 379 if state.deletionIgnored { 380 state.deletionIgnored = false 381 a.logger.Infof("A valid update was received for resource %q of type %q after previously ignoring a deletion", name, rType.TypeName()) 382 } 383 // Notify watchers if any of these conditions are met: 384 // - this is the first update for this resource 385 // - this update is different from the one currently cached 386 // - the previous update for this resource was NACKed, but the update 387 // before that was the same as this update. 388 if state.cache == nil || !state.cache.RawEqual(uErr.Resource) || state.md.ErrState != nil { 389 // Update the resource cache. 390 if a.logger.V(2) { 391 a.logger.Infof("Resource type %q with name %q added to cache", rType.TypeName(), name) 392 } 393 state.cache = uErr.Resource 394 395 for watcher := range state.watchers { 396 watcher := watcher 397 resource := uErr.Resource 398 watcherCnt.Add(1) 399 funcsToSchedule = append(funcsToSchedule, func(context.Context) { watcher.OnUpdate(resource, done) }) 400 } 401 } 402 403 // Set status to ACK, and clear error state. The metadata might be a 404 // NACK metadata because some other resources in the same response 405 // are invalid. 406 state.md = md 407 state.md.ErrState = nil 408 state.md.Status = xdsresource.ServiceStatusACKed 409 if md.ErrState != nil { 410 state.md.Version = md.ErrState.Version 411 } 412 } 413 414 // If this resource type requires that all resources be present in every 415 // SotW response from the server, a response that does not include a 416 // previously seen resource will be interpreted as a deletion of that 417 // resource unless ignore_resource_deletion option was set in the server 418 // config. 419 if !rType.AllResourcesRequiredInSotW() { 420 return 421 } 422 for name, state := range resourceStates { 423 if state.cache == nil { 424 // If the resource state does not contain a cached update, which can 425 // happen when: 426 // - resource was newly requested but has not yet been received, or, 427 // - resource was removed as part of a previous update, 428 // we don't want to generate an error for the watchers. 429 // 430 // For the first of the above two conditions, this ADS response may 431 // be in reaction to an earlier request that did not yet request the 432 // new resource, so its absence from the response does not 433 // necessarily indicate that the resource does not exist. For that 434 // case, we rely on the request timeout instead. 435 // 436 // For the second of the above two conditions, we already generated 437 // an error when we received the first response which removed this 438 // resource. So, there is no need to generate another one. 439 continue 440 } 441 if _, ok := updates[name]; ok { 442 // If the resource was present in the response, move on. 443 continue 444 } 445 if state.md.Status == xdsresource.ServiceStatusNotExist { 446 // The metadata status is set to "ServiceStatusNotExist" if a 447 // previous update deleted this resource, in which case we do not 448 // want to repeatedly call the watch callbacks with a 449 // "resource-not-found" error. 450 continue 451 } 452 if serverConfig.ServerFeaturesIgnoreResourceDeletion() { 453 // Per A53, resource deletions are ignored if the 454 // `ignore_resource_deletion` server feature is enabled through the 455 // bootstrap configuration. If the resource deletion is to be 456 // ignored, the resource is not removed from the cache and the 457 // corresponding OnResourceDoesNotExist() callback is not invoked on 458 // the watchers. 459 if !state.deletionIgnored { 460 state.deletionIgnored = true 461 a.logger.Warningf("Ignoring resource deletion for resource %q of type %q", name, rType.TypeName()) 462 } 463 continue 464 } 465 466 // If we get here, it means that the resource exists in cache, but not 467 // in the new update. Delete the resource from cache, and send a 468 // resource not found error to indicate that the resource has been 469 // removed. Metadata for the resource is still maintained, as this is 470 // required by CSDS. 471 state.cache = nil 472 state.md = xdsresource.UpdateMetadata{Status: xdsresource.ServiceStatusNotExist} 473 for watcher := range state.watchers { 474 watcher := watcher 475 watcherCnt.Add(1) 476 funcsToSchedule = append(funcsToSchedule, func(context.Context) { watcher.OnResourceDoesNotExist(done) }) 477 } 478 } 479 } 480 481 // adsResourceDoesNotExist is called by the xDS client implementation (on all 482 // interested authorities) to notify the authority that a subscribed resource 483 // does not exist. 484 func (a *authority) adsResourceDoesNotExist(rType xdsresource.Type, resourceName string) { 485 a.xdsClientSerializer.TrySchedule(func(context.Context) { 486 a.handleADSResourceDoesNotExist(rType, resourceName) 487 }) 488 } 489 490 // handleADSResourceDoesNotExist is called when a subscribed resource does not 491 // exist. It removes the resource from the cache, updates the metadata status 492 // to ServiceStatusNotExist, and notifies all watchers that the resource does 493 // not exist. 494 func (a *authority) handleADSResourceDoesNotExist(rType xdsresource.Type, resourceName string) { 495 if a.logger.V(2) { 496 a.logger.Infof("Watch for resource %q of type %s timed out", resourceName, rType.TypeName()) 497 } 498 499 resourceStates := a.resources[rType] 500 if resourceStates == nil { 501 if a.logger.V(2) { 502 a.logger.Infof("Resource %q of type %s currently not being watched", resourceName, rType.TypeName()) 503 } 504 return 505 } 506 state, ok := resourceStates[resourceName] 507 if !ok { 508 if a.logger.V(2) { 509 a.logger.Infof("Resource %q of type %s currently not being watched", resourceName, rType.TypeName()) 510 } 511 return 512 } 513 514 state.cache = nil 515 state.md = xdsresource.UpdateMetadata{Status: xdsresource.ServiceStatusNotExist} 516 for watcher := range state.watchers { 517 watcher := watcher 518 a.watcherCallbackSerializer.TrySchedule(func(context.Context) { watcher.OnResourceDoesNotExist(func() {}) }) 519 } 520 } 521 522 // handleRevertingToPrimaryOnUpdate is called when a resource update is received 523 // from the xDS client. 524 // 525 // If the update is from the currently active server, nothing is done. Else, all 526 // lower priority servers are closed and the active server is reverted to the 527 // highest priority server that sent the update. 528 // 529 // This method is only executed in the context of a serializer callback. 530 func (a *authority) handleRevertingToPrimaryOnUpdate(serverConfig *bootstrap.ServerConfig) { 531 if a.activeXDSChannel != nil && a.activeXDSChannel.serverConfig.Equal(serverConfig) { 532 // If the resource update is from the current active server, nothing 533 // needs to be done from fallback point of view. 534 return 535 } 536 537 if a.logger.V(2) { 538 a.logger.Infof("Received update from non-active server %q", serverConfig) 539 } 540 541 // If the resource update is not from the current active server, it means 542 // that we have received an update from a higher priority server and we need 543 // to revert back to it. This method guarantees that when an update is 544 // received from a server, all lower priority servers are closed. 545 serverIdx := a.serverIndexForConfig(serverConfig) 546 a.activeXDSChannel = a.xdsChannelConfigs[serverIdx] 547 548 // Close all lower priority channels. 549 // 550 // But before closing any channel, we need to unsubscribe from any resources 551 // that were subscribed to on this channel. Resources could be subscribed to 552 // from multiple channels as we fallback to lower priority servers. But when 553 // a higher priority one comes back up, we need to unsubscribe from all 554 // lower priority ones before releasing the reference to them. 555 for i := serverIdx + 1; i < len(a.xdsChannelConfigs); i++ { 556 cfg := a.xdsChannelConfigs[i] 557 558 for rType, rState := range a.resources { 559 for resourceName, state := range rState { 560 for xcc := range state.xdsChannelConfigs { 561 if xcc != cfg { 562 continue 563 } 564 // If the current resource is subscribed to on this channel, 565 // unsubscribe, and remove the channel from the list of 566 // channels that this resource is subscribed to. 567 xcc.channel.unsubscribe(rType, resourceName) 568 delete(state.xdsChannelConfigs, xcc) 569 } 570 } 571 } 572 573 // Release the reference to the channel. 574 if cfg.cleanup != nil { 575 if a.logger.V(2) { 576 a.logger.Infof("Closing lower priority server %q", cfg.serverConfig) 577 } 578 cfg.cleanup() 579 cfg.cleanup = nil 580 } 581 cfg.channel = nil 582 } 583 } 584 585 // watchResource registers a new watcher for the specified resource type and 586 // name. It returns a function that can be called to cancel the watch. 587 // 588 // If this is the first watch for any resource on this authority, an xdsChannel 589 // to the first management server (from the list of server configurations) will 590 // be created. 591 // 592 // If this is the first watch for the given resource name, it will subscribe to 593 // the resource with the xdsChannel. If a cached copy of the resource exists, it 594 // will immediately notify the new watcher. When the last watcher for a resource 595 // is removed, it will unsubscribe the resource from the xdsChannel. 596 func (a *authority) watchResource(rType xdsresource.Type, resourceName string, watcher xdsresource.ResourceWatcher) func() { 597 cleanup := func() {} 598 done := make(chan struct{}) 599 600 a.xdsClientSerializer.ScheduleOr(func(context.Context) { 601 defer close(done) 602 603 if a.logger.V(2) { 604 a.logger.Infof("New watch for type %q, resource name %q", rType.TypeName(), resourceName) 605 } 606 607 xdsChannel, err := a.xdsChannelToUse() 608 if err != nil { 609 a.watcherCallbackSerializer.TrySchedule(func(context.Context) { watcher.OnError(err, func() {}) }) 610 return 611 } 612 613 // Lookup the entry for the resource type in the top-level map. If there is 614 // no entry for this resource type, create one. 615 resources := a.resources[rType] 616 if resources == nil { 617 resources = make(map[string]*resourceState) 618 a.resources[rType] = resources 619 } 620 621 // Lookup the resource state for the particular resource name that the watch 622 // is being registered for. If this is the first watch for this resource 623 // name, request it from the management server. 624 state := resources[resourceName] 625 if state == nil { 626 if a.logger.V(2) { 627 a.logger.Infof("First watch for type %q, resource name %q", rType.TypeName(), resourceName) 628 } 629 state = &resourceState{ 630 watchers: make(map[xdsresource.ResourceWatcher]bool), 631 md: xdsresource.UpdateMetadata{Status: xdsresource.ServiceStatusRequested}, 632 xdsChannelConfigs: map[*xdsChannelWithConfig]bool{xdsChannel: true}, 633 } 634 resources[resourceName] = state 635 xdsChannel.channel.subscribe(rType, resourceName) 636 } 637 // Always add the new watcher to the set of watchers. 638 state.watchers[watcher] = true 639 640 // If we have a cached copy of the resource, notify the new watcher 641 // immediately. 642 if state.cache != nil { 643 if a.logger.V(2) { 644 a.logger.Infof("Resource type %q with resource name %q found in cache: %s", rType.TypeName(), resourceName, state.cache.ToJSON()) 645 } 646 // state can only be accessed in the context of an 647 // xdsClientSerializer callback. Hence making a copy of the cached 648 // resource here for watchCallbackSerializer. 649 resource := state.cache 650 a.watcherCallbackSerializer.TrySchedule(func(context.Context) { watcher.OnUpdate(resource, func() {}) }) 651 } 652 // If last update was NACK'd, notify the new watcher of error 653 // immediately as well. 654 if state.md.Status == xdsresource.ServiceStatusNACKed { 655 if a.logger.V(2) { 656 a.logger.Infof("Resource type %q with resource name %q was NACKed", rType.TypeName(), resourceName) 657 } 658 // state can only be accessed in the context of an 659 // xdsClientSerializer callback. Hence making a copy of the error 660 // here for watchCallbackSerializer. 661 err := state.md.ErrState.Err 662 a.watcherCallbackSerializer.TrySchedule(func(context.Context) { watcher.OnError(err, func() {}) }) 663 } 664 // If the metadata field is updated to indicate that the management 665 // server does not have this resource, notify the new watcher. 666 if state.md.Status == xdsresource.ServiceStatusNotExist { 667 a.watcherCallbackSerializer.TrySchedule(func(context.Context) { watcher.OnResourceDoesNotExist(func() {}) }) 668 } 669 cleanup = a.unwatchResource(rType, resourceName, watcher) 670 }, func() { 671 if a.logger.V(2) { 672 a.logger.Infof("Failed to schedule a watch for type %q, resource name %q, because the xDS client is closed", rType.TypeName(), resourceName) 673 } 674 close(done) 675 }) 676 <-done 677 return cleanup 678 } 679 680 func (a *authority) unwatchResource(rType xdsresource.Type, resourceName string, watcher xdsresource.ResourceWatcher) func() { 681 return sync.OnceFunc(func() { 682 done := make(chan struct{}) 683 a.xdsClientSerializer.ScheduleOr(func(context.Context) { 684 defer close(done) 685 686 if a.logger.V(2) { 687 a.logger.Infof("Canceling a watch for type %q, resource name %q", rType.TypeName(), resourceName) 688 } 689 690 // Lookup the resource type from the resource cache. The entry is 691 // guaranteed to be present, since *we* were the ones who added it in 692 // there when the watch was registered. 693 resources := a.resources[rType] 694 state := resources[resourceName] 695 696 // Delete this particular watcher from the list of watchers, so that its 697 // callback will not be invoked in the future. 698 delete(state.watchers, watcher) 699 if len(state.watchers) > 0 { 700 if a.logger.V(2) { 701 a.logger.Infof("Other watchers exist for type %q, resource name %q", rType.TypeName(), resourceName) 702 } 703 return 704 } 705 706 // There are no more watchers for this resource. Unsubscribe this 707 // resource from all channels where it was subscribed to and delete 708 // the state associated with it. 709 if a.logger.V(2) { 710 a.logger.Infof("Removing last watch for resource name %q", resourceName) 711 } 712 for xcc := range state.xdsChannelConfigs { 713 xcc.channel.unsubscribe(rType, resourceName) 714 } 715 delete(resources, resourceName) 716 717 // If there are no more watchers for this resource type, delete the 718 // resource type from the top-level map. 719 if len(resources) == 0 { 720 if a.logger.V(2) { 721 a.logger.Infof("Removing last watch for resource type %q", rType.TypeName()) 722 } 723 delete(a.resources, rType) 724 } 725 // If there are no more watchers for any resource type, release the 726 // reference to the xdsChannels. 727 if len(a.resources) == 0 { 728 if a.logger.V(2) { 729 a.logger.Infof("Removing last watch for for any resource type, releasing reference to the xdsChannel") 730 } 731 a.closeXDSChannels() 732 } 733 }, func() { close(done) }) 734 <-done 735 }) 736 } 737 738 // xdsChannelToUse returns the xdsChannel to use for communicating with the 739 // management server. If an active channel is available, it returns that. 740 // Otherwise, it creates a new channel using the first server configuration in 741 // the list of configurations, and returns that. 742 // 743 // A non-nil error is returned if the channel creation fails. 744 // 745 // Only executed in the context of a serializer callback. 746 func (a *authority) xdsChannelToUse() (*xdsChannelWithConfig, error) { 747 if a.activeXDSChannel != nil { 748 return a.activeXDSChannel, nil 749 } 750 751 sc := a.xdsChannelConfigs[0].serverConfig 752 xc, cleanup, err := a.getChannelForADS(sc, a) 753 if err != nil { 754 return nil, err 755 } 756 a.xdsChannelConfigs[0].channel = xc 757 a.xdsChannelConfigs[0].cleanup = cleanup 758 a.activeXDSChannel = a.xdsChannelConfigs[0] 759 return a.activeXDSChannel, nil 760 } 761 762 // closeXDSChannels closes all the xDS channels associated with this authority, 763 // when there are no more watchers for any resource type. 764 // 765 // Only executed in the context of a serializer callback. 766 func (a *authority) closeXDSChannels() { 767 for _, xcc := range a.xdsChannelConfigs { 768 if xcc.cleanup != nil { 769 xcc.cleanup() 770 xcc.cleanup = nil 771 } 772 xcc.channel = nil 773 } 774 a.activeXDSChannel = nil 775 } 776 777 // watcherExistsForUncachedResource returns true if there is at least one 778 // watcher for a resource that has not yet been cached. 779 // 780 // Only executed in the context of a serializer callback. 781 func (a *authority) watcherExistsForUncachedResource() bool { 782 for _, resourceStates := range a.resources { 783 for _, state := range resourceStates { 784 if state.md.Status == xdsresource.ServiceStatusRequested { 785 return true 786 } 787 } 788 } 789 return false 790 } 791 792 // dumpResources returns a dump of the resource configuration cached by this 793 // authority, for CSDS purposes. 794 func (a *authority) dumpResources() []*v3statuspb.ClientConfig_GenericXdsConfig { 795 var ret []*v3statuspb.ClientConfig_GenericXdsConfig 796 done := make(chan struct{}) 797 798 a.xdsClientSerializer.ScheduleOr(func(context.Context) { 799 defer close(done) 800 ret = a.resourceConfig() 801 }, func() { close(done) }) 802 <-done 803 return ret 804 } 805 806 // resourceConfig returns a slice of GenericXdsConfig objects representing the 807 // current state of all resources managed by this authority. This is used for 808 // reporting the current state of the xDS client. 809 // 810 // Only executed in the context of a serializer callback. 811 func (a *authority) resourceConfig() []*v3statuspb.ClientConfig_GenericXdsConfig { 812 var ret []*v3statuspb.ClientConfig_GenericXdsConfig 813 for rType, resourceStates := range a.resources { 814 typeURL := rType.TypeURL() 815 for name, state := range resourceStates { 816 var raw *anypb.Any 817 if state.cache != nil { 818 raw = state.cache.Raw() 819 } 820 config := &v3statuspb.ClientConfig_GenericXdsConfig{ 821 TypeUrl: typeURL, 822 Name: name, 823 VersionInfo: state.md.Version, 824 XdsConfig: raw, 825 LastUpdated: timestamppb.New(state.md.Timestamp), 826 ClientStatus: serviceStatusToProto(state.md.Status), 827 } 828 if errState := state.md.ErrState; errState != nil { 829 config.ErrorState = &v3adminpb.UpdateFailureState{ 830 LastUpdateAttempt: timestamppb.New(errState.Timestamp), 831 Details: errState.Err.Error(), 832 VersionInfo: errState.Version, 833 } 834 } 835 ret = append(ret, config) 836 } 837 } 838 return ret 839 } 840 841 func (a *authority) close() { 842 a.xdsClientSerializerClose() 843 <-a.xdsClientSerializer.Done() 844 if a.logger.V(2) { 845 a.logger.Infof("Closed") 846 } 847 } 848 849 func serviceStatusToProto(serviceStatus xdsresource.ServiceStatus) v3adminpb.ClientResourceStatus { 850 switch serviceStatus { 851 case xdsresource.ServiceStatusUnknown: 852 return v3adminpb.ClientResourceStatus_UNKNOWN 853 case xdsresource.ServiceStatusRequested: 854 return v3adminpb.ClientResourceStatus_REQUESTED 855 case xdsresource.ServiceStatusNotExist: 856 return v3adminpb.ClientResourceStatus_DOES_NOT_EXIST 857 case xdsresource.ServiceStatusACKed: 858 return v3adminpb.ClientResourceStatus_ACKED 859 case xdsresource.ServiceStatusNACKed: 860 return v3adminpb.ClientResourceStatus_NACKED 861 default: 862 return v3adminpb.ClientResourceStatus_UNKNOWN 863 } 864 }