-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Expand file tree
/
Copy pathcdsbalancer.go
More file actions
488 lines (441 loc) · 18.3 KB
/
Copy pathcdsbalancer.go
File metadata and controls
488 lines (441 loc) · 18.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
/*
* Copyright 2019 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package cdsbalancer implements a balancer to handle CDS responses.
package cdsbalancer
import (
"encoding/json"
"fmt"
"google.golang.org/grpc/attributes"
"google.golang.org/grpc/balancer"
"google.golang.org/grpc/balancer/base"
"google.golang.org/grpc/connectivity"
"google.golang.org/grpc/internal/grpclog"
"google.golang.org/grpc/internal/pretty"
internalserviceconfig "google.golang.org/grpc/internal/serviceconfig"
"google.golang.org/grpc/internal/xds/balancer/outlierdetection"
"google.golang.org/grpc/internal/xds/balancer/priority"
"google.golang.org/grpc/internal/xds/xdsclient"
"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
"google.golang.org/grpc/internal/xds/xdsdepmgr"
"google.golang.org/grpc/resolver"
"google.golang.org/grpc/serviceconfig"
)
const cdsName = "cds_experimental"
var (
// newChildBalancer is a helper function to build a new child balancer
// and its config parser, and will be overridden in unittests.
newChildBalancer = func(name string, cc balancer.ClientConn, opts balancer.BuildOptions) (balancer.Balancer, balancer.ConfigParser, error) {
builder := balancer.Get(name)
if builder == nil {
return nil, nil, fmt.Errorf("xds: no balancer builder with name %v", name)
}
parser, ok := builder.(balancer.ConfigParser)
if !ok {
return nil, nil, fmt.Errorf("xds: balancer builder for %v does not implement ConfigParser", name)
}
// We directly pass the parent clientConn to the underlying child
// balancer because the cdsBalancer does not deal with subConns.
return builder.Build(cc, opts), parser, nil
}
)
func init() {
balancer.Register(bb{})
}
// bb implements the balancer.Builder interface to help build a cdsBalancer.
// It also implements the balancer.ConfigParser interface to help parse the
// JSON service config, to be passed to the cdsBalancer.
type bb struct{}
// Build creates a new CDS balancer with the ClientConn.
func (bb) Build(cc balancer.ClientConn, opts balancer.BuildOptions) balancer.Balancer {
b := &cdsBalancer{
bOpts: opts,
clusterConfigs: make(map[string]*xdsresource.ClusterResult),
priorityConfigs: make(map[string]*priorityConfig),
cc: cc,
}
b.logger = prefixLogger(b)
b.logger.Infof("Created")
return b
}
// Name returns the name of balancers built by this builder.
func (bb) Name() string {
return cdsName
}
// lbConfig represents the loadBalancingConfig section of the service config
// for the cdsBalancer.
type lbConfig struct {
serviceconfig.LoadBalancingConfig
ClusterName string `json:"cluster"`
IsDynamic bool `json:"isDynamic"`
}
// ParseConfig parses the JSON load balancer config provided into an
// internal form or returns an error if the config is invalid.
func (bb) ParseConfig(c json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
var cfg lbConfig
if err := json.Unmarshal(c, &cfg); err != nil {
return nil, fmt.Errorf("xds: unable to unmarshal lbconfig: %s, error: %v", string(c), err)
}
return &cfg, nil
}
// cdsBalancer implements a CDS based LB policy. It instantiates a
// cluster_resolver balancer to further resolve the serviceName received from
// CDS, into localities and endpoints. Implements the balancer.Balancer
// interface which is exposed to gRPC and implements the balancer.ClientConn
// interface which is exposed to the cluster_resolver balancer.
type cdsBalancer struct {
// The following fields are initialized at build time and are either
// read-only after that or provide their own synchronization, and therefore
// do not need to be guarded by a mutex.
cc balancer.ClientConn // ClientConn interface passed to child LB.
bOpts balancer.BuildOptions // BuildOptions passed to child LB.
childConfigParser balancer.ConfigParser // Config parser for cluster_resolver LB policy.
logger *grpclog.PrefixLogger // Prefix logger for all logging.
// All fields below are accessed only from methods implementing the
// balancer.Balancer interface. Since gRPC guarantees that these methods are
// never invoked concurrently, no additional synchronization is required to
// protect access to these fields.
xdsClient xdsclient.XDSClient
childLB balancer.Balancer // Child policy, built upon resolution of the cluster graph.
childLBName string // Name of the child policy.
clusterConfigs map[string]*xdsresource.ClusterResult // Cluster name to the last received result for that cluster.
priorityConfigs map[string]*priorityConfig // Hostname to priority config for that leaf cluster.
lbCfg *lbConfig // Current load balancing configuration.
priorities []*priorityConfig // List of priorities in the order.
unsubscribe func() // For dynamic cluster unsubscription.
isSubscribed bool // True if a dynamic cluster has been subscribed to.
clusterSubscriber xdsdepmgr.ClusterSubscriber // To subscribe to dynamic cluster resource.
xdsLBPolicy internalserviceconfig.BalancerConfig // Stores the locality and endpoint picking policy.
attributes *attributes.Attributes // Attributes from resolver state.
serviceConfig *serviceconfig.ParseResult
// Each new leaf cluster needs a child name generator to reuse child policy
// names. But to make sure the names across leaf clusters doesn't conflict,
// we need a seq ID. This ID is incremented for each new cluster.
childNameGeneratorSeqID uint64
}
// UpdateClientConnState receives the serviceConfig, xdsConfig,
// ClusterSubscriber and the xdsClient object from the xdsResolver. If an error
// is encountered, the parent (clustermanager) sets the corresponding cluster’s
// picker to transient_failure. Otherwise, the received configuration is
// processed and forwarded to the appropriate child policy.
func (b *cdsBalancer) UpdateClientConnState(state balancer.ClientConnState) error {
if b.xdsClient == nil {
c := xdsclient.FromResolverState(state.ResolverState)
if c == nil {
b.logger.Warningf("Received balancer config with no xDS client")
return balancer.ErrBadResolverState
}
b.xdsClient = c
}
b.logger.Infof("Received balancer config update: %s", pretty.ToJSON(state.BalancerConfig))
xdsConfig := xdsresource.XDSConfigFromResolverState(state.ResolverState)
if xdsConfig == nil {
b.logger.Warningf("Received balancer config with no xDS config")
return balancer.ErrBadResolverState
}
b.clusterConfigs = xdsConfig.Clusters
b.clusterSubscriber = xdsdepmgr.XDSClusterSubscriberFromResolverState(state.ResolverState)
if b.clusterSubscriber == nil {
b.logger.Warningf("Received balancer config with no cluster subscriber")
return balancer.ErrBadResolverState
}
// The errors checked here should ideally never happen because the
// ServiceConfig in this case is prepared by the xdsResolver and is not
// something that is received on the wire.
lbCfg, ok := state.BalancerConfig.(*lbConfig)
if !ok {
b.logger.Warningf("Received unexpected balancer config type: %T", state.BalancerConfig)
return balancer.ErrBadResolverState
}
if lbCfg.ClusterName == "" {
b.logger.Warningf("Received balancer config with no cluster name")
return balancer.ErrBadResolverState
}
b.lbCfg = lbCfg
b.serviceConfig = state.ResolverState.ServiceConfig
b.attributes = state.ResolverState.Attributes
return b.handleXDSConfigUpdate()
}
// handleXDSConfigUpdate processes the XDSConfig update from the xDS resolver.
func (b *cdsBalancer) handleXDSConfigUpdate() error {
clusterName := b.lbCfg.ClusterName
// If the cluster is dynamic and we dont have a subscription yet, create
// one.
if b.lbCfg.IsDynamic && !b.isSubscribed {
b.unsubscribe = b.clusterSubscriber.SubscribeToCluster(clusterName)
b.isSubscribed = true
return nil
}
clusterUpdate, ok := b.clusterConfigs[clusterName]
if !ok {
// If the cluster is missing from the config, check if it is dynamic.
// For dynamic clusters, the xDS config may be updated before the
// corresponding cluster resource is received. This should never occur
// for static clusters.
if b.lbCfg.IsDynamic {
return nil
}
return b.annotateErrorWithNodeID(fmt.Errorf("did not find the cluster %q in XDSConfig", clusterName))
}
// If the cluster resource has an error, return the error.
if clusterUpdate.Err != nil {
return clusterUpdate.Err
}
return b.handleClusterUpdate()
}
// handleClusterUpdate handles a good XDSConfig update from the xDS resolver.
// Builds the child policy config and pushes it down.
func (b *cdsBalancer) handleClusterUpdate() error {
clusterName := b.lbCfg.ClusterName
clusterConfig := b.clusterConfigs[clusterName].Config
var newPriorities []*priorityConfig
switch clusterConfig.Cluster.ClusterType {
case xdsresource.ClusterTypeEDS, xdsresource.ClusterTypeLogicalDNS:
p := b.updatePriorityConfig(clusterName, &clusterConfig)
newPriorities = append(newPriorities, p)
case xdsresource.ClusterTypeAggregate:
for _, leaf := range clusterConfig.AggregateConfig.LeafClusters {
leafCluster := b.clusterConfigs[leaf]
// Update priority config for leaf clusters.
p := b.updatePriorityConfig(leaf, &leafCluster.Config)
newPriorities = append(newPriorities, p)
}
}
b.priorities = newPriorities
if err := b.updateOutlierDetection(); err != nil {
return b.annotateErrorWithNodeID(fmt.Errorf("failed to correctly update Outlier Detection config %v", err))
}
// The LB policy is configured by the root cluster.
if err := json.Unmarshal(clusterConfig.Cluster.LBPolicy, &b.xdsLBPolicy); err != nil {
return b.annotateErrorWithNodeID(fmt.Errorf("error unmarshalling xDS LB Policy: %v", err))
}
if err := b.updateChildConfig(); err != nil {
return b.annotateErrorWithNodeID(err)
}
return nil
}
// updateChildConfig builds child policy configuration using endpoint addresses
// returned from the XDSConfig and child policy configuration.
//
// A child policy is created if one doesn't already exist. The newly built
// configuration is then pushed to the child policy.
func (b *cdsBalancer) updateChildConfig() error {
clusterName := b.lbCfg.ClusterName
clusterConfig := b.clusterConfigs[clusterName].Config
isAggregate := clusterConfig.Cluster.ClusterType == xdsresource.ClusterTypeAggregate
var topLBName string
if isAggregate {
topLBName = priority.Name
} else {
topLBName = outlierdetection.Name
}
if b.childLB != nil && b.childLBName != topLBName {
b.childLB.Close()
b.childLB = nil
}
if b.childLB == nil {
childLB, parser, err := newChildBalancer(topLBName, b.cc, b.bOpts)
if err != nil {
return fmt.Errorf("failed to create child policy of type %s: %v", topLBName, err)
}
b.childLB = childLB
b.childLBName = topLBName
b.childConfigParser = parser
}
var childCfgBytes []byte
var endpoints []resolver.Endpoint
var err error
if isAggregate {
childCfgBytes, endpoints, err = buildAggregateClusterConfigJSON(b.priorities, &b.xdsLBPolicy)
} else {
childCfgBytes, endpoints, err = buildLeafClusterConfigJSON(b.priorities, &b.xdsLBPolicy)
}
if err != nil {
return fmt.Errorf("failed to build child policy config: %v", err)
}
childCfg, err := b.childConfigParser.ParseConfig(childCfgBytes)
if err != nil {
return fmt.Errorf("failed to parse child policy config. This should never happen because the config was generated: %v", err)
}
if b.logger.V(2) {
b.logger.Infof("Built child policy config: %s", pretty.ToJSON(childCfg))
}
for i := range endpoints {
for j := range endpoints[i].Addresses {
addr := endpoints[i].Addresses[j]
addr.BalancerAttributes = endpoints[i].Attributes
// BalancerAttributes are used for the following:
// * Authority Override.
// * grpc.lb.backend_service metric label propagation.
// See https://github.com/grpc/grpc-go/issues/6472
endpoints[i].Addresses[j] = addr
}
}
if err := b.childLB.UpdateClientConnState(balancer.ClientConnState{
ResolverState: resolver.State{
Endpoints: endpoints,
ServiceConfig: b.serviceConfig,
Attributes: b.attributes,
},
BalancerConfig: childCfg,
}); err != nil {
return fmt.Errorf("failed to push config to child policy: %v", err)
}
return nil
}
// updatePriorityConfig updates the priority configuration for the specified EDS
// or DNS cluster, creating it if it does not already exist.
func (b *cdsBalancer) updatePriorityConfig(clusterName string, clusterConfig *xdsresource.ClusterConfig) *priorityConfig {
name := hostName(clusterName, *clusterConfig.Cluster)
pc, ok := b.priorityConfigs[name]
if !ok {
pc = &priorityConfig{
childNameGen: newNameGenerator(b.childNameGeneratorSeqID),
}
b.priorityConfigs[name] = pc
// Increment the seq ID for the next new cluster. This is done to make
// sure that the child policy names generated for different clusters
// don't conflict with each other.
b.childNameGeneratorSeqID++
}
pc.clusterConfig = clusterConfig
return pc
}
// updateOutlierDetection updates Outlier Detection config for all priorities.
func (b *cdsBalancer) updateOutlierDetection() error {
odBuilder := balancer.Get(outlierdetection.Name)
if odBuilder == nil {
// Shouldn't happen, registered through imported Outlier Detection,
// defensive programming.
return fmt.Errorf("%q LB policy is needed but not registered", outlierdetection.Name)
}
odParser, ok := odBuilder.(balancer.ConfigParser)
if !ok {
// Shouldn't happen, imported Outlier Detection builder has this method.
return fmt.Errorf("%q LB policy does not implement a config parser", outlierdetection.Name)
}
for _, p := range b.priorities {
// Update Outlier Detection Config.
odJSON := p.clusterConfig.Cluster.OutlierDetection
if odJSON == nil {
odJSON = json.RawMessage(`{}`)
}
lbCfg, err := odParser.ParseConfig(odJSON)
if err != nil {
return fmt.Errorf("error parsing Outlier Detection config %v: %v", odJSON, err)
}
odCfg, ok := lbCfg.(*outlierdetection.LBConfig)
if !ok {
// Shouldn't happen, Parser built at build time with Outlier
// Detection builder pulled from gRPC LB Registry.
return fmt.Errorf("config parser for Outlier Detection returned config with unexpected type %T: %v", lbCfg, lbCfg)
}
p.outlierDetection = *odCfg
}
return nil
}
// ResolverError handles errors reported by the xdsResolver.
func (b *cdsBalancer) ResolverError(err error) {
// Missing Listener or RouteConfiguration on the management server
// results in a 'resource not found' error from the xDS resolver. In
// these cases, we should report transient failure.
if xdsresource.ErrType(err) == xdsresource.ErrorTypeResourceNotFound {
b.closeChildPolicyAndReportTF(err)
return
}
var root string
if b.lbCfg != nil {
root = b.lbCfg.ClusterName
}
b.onClusterError(root, err)
}
// UpdateSubConnState handles subConn updates from gRPC.
func (b *cdsBalancer) UpdateSubConnState(sc balancer.SubConn, state balancer.SubConnState) {
b.logger.Errorf("UpdateSubConnState(%v, %+v) called unexpectedly", sc, state)
}
// closeChildPolicyAndReportTF closes the child policy, if it exists, and
// updates the connectivity state of the channel to TransientFailure with an
// error picker.
func (b *cdsBalancer) closeChildPolicyAndReportTF(err error) {
if b.childLB != nil {
b.childLB.Close()
b.childLB = nil
}
b.cc.UpdateState(balancer.State{
ConnectivityState: connectivity.TransientFailure,
Picker: base.NewErrPicker(err),
})
}
// Close closes the child policy, unsubscribes to the dynamic cluster, and
// closes the cdsBalancer.
func (b *cdsBalancer) Close() {
if b.childLB != nil {
b.childLB.Close()
b.childLB = nil
}
if b.unsubscribe != nil {
b.unsubscribe()
}
b.logger.Infof("Shutdown")
}
func (b *cdsBalancer) ExitIdle() {
if b.childLB == nil {
b.logger.Warningf("Received ExitIdle with no child policy")
return
}
// This implementation assumes the child balancer supports
// ExitIdle (but still checks for the interface's existence to
// avoid a panic if not). If the child does not, no subconns
// will be connected.
b.childLB.ExitIdle()
}
// Node ID needs to be manually added to errors generated in the following
// scenarios:
// - resource-does-not-exist: since the xDS watch API uses a separate callback
// instead of returning an error value. TODO(gRFC A88): Once A88 is
// implemented, the xDS client will be able to add the node ID to
// resource-does-not-exist errors as well, and we can get rid of this
// special handling.
// - received a good update from the xDS client, but the update either contains
// an invalid security configuration or contains invalid aggragate cluster
// config.
func (b *cdsBalancer) annotateErrorWithNodeID(err error) error {
nodeID := b.xdsClient.BootstrapConfig().Node().GetId()
return fmt.Errorf("[xDS node id: %v]: %w", nodeID, err)
}
// onClusterAmbientError handles an ambient error, if a childLB already has a
// good update, it should continue using that.
func (b *cdsBalancer) onClusterAmbientError(name string, err error) {
b.logger.Warningf("Cluster resource %q received ambient error update: %v", name, err)
if xdsresource.ErrType(err) != xdsresource.ErrorTypeConnection && b.childLB != nil {
// Connection errors will be sent to the child balancers directly.
// There's no need to forward them.
b.childLB.ResolverError(err)
}
}
// onClusterResourceError handles errors to stop using the previously seen
// resource. Propagates the error down to the child policy if one exists, and
// puts the channel in TRANSIENT_FAILURE.
func (b *cdsBalancer) onClusterResourceError(name string, err error) {
b.logger.Warningf("CDS watch for resource %q reported resource error", name)
b.closeChildPolicyAndReportTF(err)
}
func (b *cdsBalancer) onClusterError(name string, err error) {
if b.childLB != nil {
b.onClusterAmbientError(name, err)
} else {
b.onClusterResourceError(name, err)
}
}