dubbo.apache.org/dubbo-go/v3@v3.1.1/xds/balancer/clusterresolver/clusterresolver.go (about) 1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. 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 /* 19 * 20 * Copyright 2021 gRPC authors. 21 * 22 */ 23 24 // Package clusterresolver contains EDS balancer implementation. 25 package clusterresolver 26 27 import ( 28 "encoding/json" 29 "errors" 30 "fmt" 31 ) 32 33 import ( 34 dubbogoLogger "github.com/dubbogo/gost/log/logger" 35 36 "google.golang.org/grpc/attributes" 37 38 "google.golang.org/grpc/balancer" 39 "google.golang.org/grpc/balancer/base" 40 41 "google.golang.org/grpc/connectivity" 42 43 "google.golang.org/grpc/resolver" 44 45 "google.golang.org/grpc/serviceconfig" 46 ) 47 48 import ( 49 "dubbo.apache.org/dubbo-go/v3/xds/balancer/priority" 50 "dubbo.apache.org/dubbo-go/v3/xds/client" 51 "dubbo.apache.org/dubbo-go/v3/xds/client/resource" 52 "dubbo.apache.org/dubbo-go/v3/xds/utils/buffer" 53 "dubbo.apache.org/dubbo-go/v3/xds/utils/grpcsync" 54 "dubbo.apache.org/dubbo-go/v3/xds/utils/pretty" 55 ) 56 57 // Name is the name of the cluster_resolver balancer. 58 const Name = "cluster_resolver_experimental" 59 60 var ( 61 errBalancerClosed = errors.New("cdsBalancer is closed") 62 newChildBalancer = func(bb balancer.Builder, cc balancer.ClientConn, o balancer.BuildOptions) balancer.Balancer { 63 return bb.Build(cc, o) 64 } 65 ) 66 67 func init() { 68 balancer.Register(bb{}) 69 } 70 71 type bb struct{} 72 73 // Build helps implement the balancer.Builder interface. 74 func (bb) Build(cc balancer.ClientConn, opts balancer.BuildOptions) balancer.Balancer { 75 priorityBuilder := balancer.Get(priority.Name) 76 if priorityBuilder == nil { 77 dubbogoLogger.Errorf("priority balancer is needed but not registered") 78 return nil 79 } 80 priorityConfigParser, ok := priorityBuilder.(balancer.ConfigParser) 81 if !ok { 82 dubbogoLogger.Errorf("priority balancer builder is not a config parser") 83 return nil 84 } 85 86 b := &clusterResolverBalancer{ 87 bOpts: opts, 88 updateCh: buffer.NewUnbounded(), 89 closed: grpcsync.NewEvent(), 90 done: grpcsync.NewEvent(), 91 92 priorityBuilder: priorityBuilder, 93 priorityConfigParser: priorityConfigParser, 94 } 95 b.logger = dubbogoLogger.GetLogger() 96 b.logger.Infof("Created") 97 98 b.resourceWatcher = newResourceResolver(b) 99 b.cc = &ccWrapper{ 100 ClientConn: cc, 101 resourceWatcher: b.resourceWatcher, 102 } 103 104 go b.run() 105 return b 106 } 107 108 func (bb) Name() string { 109 return Name 110 } 111 112 func (bb) ParseConfig(c json.RawMessage) (serviceconfig.LoadBalancingConfig, error) { 113 var cfg LBConfig 114 if err := json.Unmarshal(c, &cfg); err != nil { 115 return nil, fmt.Errorf("unable to unmarshal balancer config %s into cluster-resolver config, error: %v", string(c), err) 116 } 117 return &cfg, nil 118 } 119 120 // ccUpdate wraps a clientConn update received from gRPC (pushed from the 121 // xdsResolver). 122 type ccUpdate struct { 123 state balancer.ClientConnState 124 err error 125 } 126 127 // scUpdate wraps a subConn update received from gRPC. This is directly passed 128 // on to the child balancer. 129 type scUpdate struct { 130 subConn balancer.SubConn 131 state balancer.SubConnState 132 } 133 134 type exitIdle struct{} 135 136 // clusterResolverBalancer manages xdsClient and the actual EDS balancer implementation that 137 // does load balancing. 138 // 139 // It currently has only an clusterResolverBalancer. Later, we may add fallback. 140 type clusterResolverBalancer struct { 141 cc balancer.ClientConn 142 bOpts balancer.BuildOptions 143 updateCh *buffer.Unbounded // Channel for updates from gRPC. 144 resourceWatcher *resourceResolver 145 logger dubbogoLogger.Logger 146 closed *grpcsync.Event 147 done *grpcsync.Event 148 149 priorityBuilder balancer.Builder 150 priorityConfigParser balancer.ConfigParser 151 152 config *LBConfig 153 configRaw *serviceconfig.ParseResult 154 xdsClient client.XDSClient // xDS client to watch EDS resource. 155 attrsWithClient *attributes.Attributes // Attributes with xdsClient attached to be passed to the child policies. 156 157 child balancer.Balancer 158 priorities []priorityConfig 159 watchUpdateReceived bool 160 } 161 162 // handleClientConnUpdate handles a ClientConnUpdate received from gRPC. Good 163 // updates lead to registration of EDS and DNS watches. Updates with error lead 164 // to cancellation of existing watch and propagation of the same error to the 165 // child balancer. 166 func (b *clusterResolverBalancer) handleClientConnUpdate(update *ccUpdate) { 167 // We first handle errors, if any, and then proceed with handling the 168 // update, only if the status quo has changed. 169 if err := update.err; err != nil { 170 b.handleErrorFromUpdate(err, true) 171 return 172 } 173 174 b.logger.Infof("Receive update from resolver, balancer config: %v", pretty.ToJSON(update.state.BalancerConfig)) 175 cfg, _ := update.state.BalancerConfig.(*LBConfig) 176 if cfg == nil { 177 b.logger.Warnf("xds: unexpected LoadBalancingConfig type: %T", update.state.BalancerConfig) 178 return 179 } 180 181 b.config = cfg 182 b.configRaw = update.state.ResolverState.ServiceConfig 183 b.resourceWatcher.updateMechanisms(cfg.DiscoveryMechanisms) 184 185 if !b.watchUpdateReceived { 186 // If update was not received, wait for it. 187 return 188 } 189 // If eds resp was received before this, the child policy was created. We 190 // need to generate a new balancer config and send it to the child, because 191 // certain fields (unrelated to EDS watch) might have changed. 192 if err := b.updateChildConfig(); err != nil { 193 b.logger.Warnf("failed to update child policy config: %v", err) 194 } 195 } 196 197 // handleWatchUpdate handles a watch update from the xDS Client. Good updates 198 // lead to clientConn updates being invoked on the underlying child balancer. 199 func (b *clusterResolverBalancer) handleWatchUpdate(update *resourceUpdate) { 200 if err := update.err; err != nil { 201 b.logger.Warnf("Watch error from xds-client %p: %v", b.xdsClient, err) 202 b.handleErrorFromUpdate(err, false) 203 return 204 } 205 206 b.logger.Infof("resource update: %+v", pretty.ToJSON(update.priorities)) 207 b.watchUpdateReceived = true 208 b.priorities = update.priorities 209 210 // A new EDS update triggers new child configs (e.g. different priorities 211 // for the priority balancer), and new addresses (the endpoints come from 212 // the EDS response). 213 if err := b.updateChildConfig(); err != nil { 214 b.logger.Warnf("failed to update child policy's balancer config: %v", err) 215 } 216 } 217 218 // updateChildConfig builds a balancer config from eb's cached eds resp and 219 // service config, and sends that to the child balancer. Note that it also 220 // generates the addresses, because the endpoints come from the EDS resp. 221 // 222 // If child balancer doesn't already exist, one will be created. 223 func (b *clusterResolverBalancer) updateChildConfig() error { 224 // Child was build when the first EDS resp was received, so we just build 225 // the config and addresses. 226 if b.child == nil { 227 b.child = newChildBalancer(b.priorityBuilder, b.cc, b.bOpts) 228 } 229 230 childCfgBytes, addrs, err := buildPriorityConfigJSON(b.priorities, b.config.XDSLBPolicy) 231 if err != nil { 232 return fmt.Errorf("failed to build priority balancer config: %v", err) 233 } 234 childCfg, err := b.priorityConfigParser.ParseConfig(childCfgBytes) 235 if err != nil { 236 return fmt.Errorf("failed to parse generated priority balancer config, this should never happen because the config is generated: %v", err) 237 } 238 b.logger.Infof("build balancer config: %v", pretty.ToJSON(childCfg)) 239 return b.child.UpdateClientConnState(balancer.ClientConnState{ 240 ResolverState: resolver.State{ 241 Addresses: addrs, 242 ServiceConfig: b.configRaw, 243 Attributes: b.attrsWithClient, 244 }, 245 BalancerConfig: childCfg, 246 }) 247 } 248 249 // handleErrorFromUpdate handles both the error from parent ClientConn (from CDS 250 // balancer) and the error from xds client (from the watcher). fromParent is 251 // true if error is from parent ClientConn. 252 // 253 // If the error is connection error, it should be handled for fallback purposes. 254 // 255 // If the error is resource-not-found: 256 // - If it's from CDS balancer (shows as a resolver error), it means LDS or CDS 257 // resources were removed. The EDS watch should be canceled. 258 // - If it's from xds client, it means EDS resource were removed. The EDS 259 // watcher should keep watching. 260 // In both cases, the sub-balancers will be receive the error. 261 func (b *clusterResolverBalancer) handleErrorFromUpdate(err error, fromParent bool) { 262 b.logger.Warnf("Received error: %v", err) 263 if fromParent && resource.ErrType(err) == resource.ErrorTypeResourceNotFound { 264 // This is an error from the parent ClientConn (can be the parent CDS 265 // balancer), and is a resource-not-found error. This means the resource 266 // (can be either LDS or CDS) was removed. Stop the EDS watch. 267 b.resourceWatcher.stop() 268 } 269 if b.child != nil { 270 b.child.ResolverError(err) 271 } else { 272 // If eds balancer was never created, fail the RPCs with errors. 273 b.cc.UpdateState(balancer.State{ 274 ConnectivityState: connectivity.TransientFailure, 275 Picker: base.NewErrPicker(err), 276 }) 277 } 278 279 } 280 281 // run is a long-running goroutine which handles all updates from gRPC and 282 // client. All methods which are invoked directly by gRPC or xdsClient simply 283 // push an update onto a channel which is read and acted upon right here. 284 func (b *clusterResolverBalancer) run() { 285 for { 286 select { 287 case u := <-b.updateCh.Get(): 288 b.updateCh.Load() 289 switch update := u.(type) { 290 case *ccUpdate: 291 b.handleClientConnUpdate(update) 292 case *scUpdate: 293 // SubConn updates are simply handed over to the underlying 294 // child balancer. 295 if b.child == nil { 296 b.logger.Errorf("xds: received scUpdate {%+v} with no child balancer", update) 297 break 298 } 299 b.child.UpdateSubConnState(update.subConn, update.state) 300 case exitIdle: 301 if b.child == nil { 302 b.logger.Errorf("xds: received ExitIdle with no child balancer") 303 break 304 } 305 // This implementation assumes the child balancer supports 306 // ExitIdle (but still checks for the interface's existence to 307 // avoid a panic if not). If the child does not, no subconns 308 // will be connected. 309 if ei, ok := b.child.(balancer.ExitIdler); ok { 310 ei.ExitIdle() 311 } 312 } 313 case u := <-b.resourceWatcher.updateChannel: 314 b.handleWatchUpdate(u) 315 316 // Close results in cancellation of the EDS watch and closing of the 317 // underlying child policy and is the only way to exit this goroutine. 318 case <-b.closed.Done(): 319 b.resourceWatcher.stop() 320 321 if b.child != nil { 322 b.child.Close() 323 b.child = nil 324 } 325 // This is the *ONLY* point of return from this function. 326 b.logger.Infof("Shutdown") 327 b.done.Fire() 328 return 329 } 330 } 331 } 332 333 // Following are methods to implement the balancer interface. 334 335 // UpdateClientConnState receives the serviceConfig (which contains the 336 // clusterName to watch for in CDS) and the xdsClient object from the 337 // xdsResolver. 338 func (b *clusterResolverBalancer) UpdateClientConnState(state balancer.ClientConnState) error { 339 if b.closed.HasFired() { 340 b.logger.Warnf("xds: received ClientConnState {%+v} after clusterResolverBalancer was closed", state) 341 return errBalancerClosed 342 } 343 344 if b.xdsClient == nil { 345 c := client.FromResolverState(state.ResolverState) 346 if c == nil { 347 return balancer.ErrBadResolverState 348 } 349 b.xdsClient = c 350 b.attrsWithClient = state.ResolverState.Attributes 351 } 352 353 b.updateCh.Put(&ccUpdate{state: state}) 354 return nil 355 } 356 357 // ResolverError handles errors reported by the xdsResolver. 358 func (b *clusterResolverBalancer) ResolverError(err error) { 359 if b.closed.HasFired() { 360 b.logger.Warnf("xds: received resolver error {%v} after clusterResolverBalancer was closed", err) 361 return 362 } 363 b.updateCh.Put(&ccUpdate{err: err}) 364 } 365 366 // UpdateSubConnState handles subConn updates from gRPC. 367 func (b *clusterResolverBalancer) UpdateSubConnState(sc balancer.SubConn, state balancer.SubConnState) { 368 if b.closed.HasFired() { 369 b.logger.Warnf("xds: received subConn update {%v, %v} after clusterResolverBalancer was closed", sc, state) 370 return 371 } 372 b.updateCh.Put(&scUpdate{subConn: sc, state: state}) 373 } 374 375 // Close closes the cdsBalancer and the underlying child balancer. 376 func (b *clusterResolverBalancer) Close() { 377 b.closed.Fire() 378 <-b.done.Done() 379 } 380 381 func (b *clusterResolverBalancer) ExitIdle() { 382 b.updateCh.Put(exitIdle{}) 383 } 384 385 // ccWrapper overrides ResolveNow(), so that re-resolution from the child 386 // policies will trigger the DNS resolver in cluster_resolver balancer. 387 type ccWrapper struct { 388 balancer.ClientConn 389 resourceWatcher *resourceResolver 390 } 391 392 func (c *ccWrapper) ResolveNow(resolver.ResolveNowOptions) { 393 c.resourceWatcher.resolveNow() 394 }