google.golang.org/grpc@v1.72.2/xds/internal/balancer/clustermanager/clustermanager.go (about) 1 /* 2 * 3 * Copyright 2020 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 19 // Package clustermanager implements the cluster manager LB policy for xds. 20 package clustermanager 21 22 import ( 23 "encoding/json" 24 "fmt" 25 "time" 26 27 "google.golang.org/grpc/balancer" 28 "google.golang.org/grpc/balancer/base" 29 "google.golang.org/grpc/connectivity" 30 "google.golang.org/grpc/grpclog" 31 "google.golang.org/grpc/internal/balancergroup" 32 internalgrpclog "google.golang.org/grpc/internal/grpclog" 33 "google.golang.org/grpc/internal/hierarchy" 34 "google.golang.org/grpc/internal/pretty" 35 "google.golang.org/grpc/resolver" 36 "google.golang.org/grpc/serviceconfig" 37 ) 38 39 const balancerName = "xds_cluster_manager_experimental" 40 41 func init() { 42 balancer.Register(bb{}) 43 } 44 45 type bb struct{} 46 47 func (bb) Build(cc balancer.ClientConn, opts balancer.BuildOptions) balancer.Balancer { 48 b := &bal{} 49 b.logger = prefixLogger(b) 50 b.stateAggregator = newBalancerStateAggregator(cc, b.logger) 51 b.bg = balancergroup.New(balancergroup.Options{ 52 CC: cc, 53 BuildOpts: opts, 54 StateAggregator: b.stateAggregator, 55 Logger: b.logger, 56 SubBalancerCloseTimeout: time.Duration(0), // Disable caching of removed child policies 57 }) 58 b.logger.Infof("Created") 59 return b 60 } 61 62 func (bb) Name() string { 63 return balancerName 64 } 65 66 func (bb) ParseConfig(c json.RawMessage) (serviceconfig.LoadBalancingConfig, error) { 67 return parseConfig(c) 68 } 69 70 type bal struct { 71 logger *internalgrpclog.PrefixLogger 72 bg *balancergroup.BalancerGroup 73 stateAggregator *balancerStateAggregator 74 75 children map[string]childConfig 76 } 77 78 func (b *bal) setErrorPickerForChild(childName string, err error) { 79 b.stateAggregator.UpdateState(childName, balancer.State{ 80 ConnectivityState: connectivity.TransientFailure, 81 Picker: base.NewErrPicker(err), 82 }) 83 } 84 85 func (b *bal) updateChildren(s balancer.ClientConnState, newConfig *lbConfig) error { 86 // TODO: Get rid of handling hierarchy in addresses. This LB policy never 87 // gets addresses from the resolver. 88 addressesSplit := hierarchy.Group(s.ResolverState.Addresses) 89 endpointsSplit := hierarchy.GroupEndpoints(s.ResolverState.Endpoints) 90 91 // Remove sub-balancers that are not in the new list from the aggregator and 92 // balancergroup. 93 for name := range b.children { 94 if _, ok := newConfig.Children[name]; !ok { 95 b.stateAggregator.remove(name) 96 b.bg.Remove(name) 97 } 98 } 99 100 var retErr error 101 for childName, childCfg := range newConfig.Children { 102 lbCfg := childCfg.ChildPolicy.Config 103 if _, ok := b.children[childName]; !ok { 104 // Add new sub-balancers to the aggregator and balancergroup. 105 b.stateAggregator.add(childName) 106 b.bg.Add(childName, balancer.Get(childCfg.ChildPolicy.Name)) 107 } else { 108 // If the child policy type has changed for existing sub-balancers, 109 // parse the new config and send down the config update to the 110 // balancergroup, which will take care of gracefully switching the 111 // child over to the new policy. 112 // 113 // If we run into errors here, we need to ensure that RPCs to this 114 // child fail, while RPCs to other children with good configs 115 // continue to succeed. 116 newPolicyName, oldPolicyName := childCfg.ChildPolicy.Name, b.children[childName].ChildPolicy.Name 117 if newPolicyName != oldPolicyName { 118 var err error 119 var cfgJSON []byte 120 cfgJSON, err = childCfg.ChildPolicy.MarshalJSON() 121 if err != nil { 122 retErr = fmt.Errorf("failed to JSON marshal load balancing policy for child %q: %v", childName, err) 123 b.setErrorPickerForChild(childName, retErr) 124 continue 125 } 126 // This overwrites lbCfg to be in the format expected by the 127 // gracefulswitch balancer. So, when this config is pushed to 128 // the child (below), it will result in a graceful switch to the 129 // new child policy. 130 lbCfg, err = balancergroup.ParseConfig(cfgJSON) 131 if err != nil { 132 retErr = fmt.Errorf("failed to parse load balancing policy for child %q: %v", childName, err) 133 b.setErrorPickerForChild(childName, retErr) 134 continue 135 } 136 } 137 } 138 139 if err := b.bg.UpdateClientConnState(childName, balancer.ClientConnState{ 140 ResolverState: resolver.State{ 141 Addresses: addressesSplit[childName], 142 Endpoints: endpointsSplit[childName], 143 ServiceConfig: s.ResolverState.ServiceConfig, 144 Attributes: s.ResolverState.Attributes, 145 }, 146 BalancerConfig: lbCfg, 147 }); err != nil { 148 retErr = fmt.Errorf("failed to push new configuration %v to child %q", childCfg.ChildPolicy.Config, childName) 149 b.setErrorPickerForChild(childName, retErr) 150 } 151 152 // Picker update is sent to the parent ClientConn only after the 153 // new child policy returns a picker. So, there is no need to 154 // set needUpdateStateOnResume to true here. 155 } 156 157 b.children = newConfig.Children 158 159 // If multiple sub-balancers run into errors, we will return only the last 160 // one, which is still good enough, since the grpc channel will anyways 161 // return this error as balancer.ErrBadResolver to the name resolver, 162 // resulting in re-resolution attempts. 163 return retErr 164 165 // Adding or removing a sub-balancer will result in the 166 // needUpdateStateOnResume bit to true which results in a picker update once 167 // resumeStateUpdates() is called. 168 } 169 170 func (b *bal) UpdateClientConnState(s balancer.ClientConnState) error { 171 if b.logger.V(2) { 172 b.logger.Infof("Received update from resolver, balancer config: %+v", pretty.ToJSON(s.BalancerConfig)) 173 } 174 175 newConfig, ok := s.BalancerConfig.(*lbConfig) 176 if !ok { 177 return fmt.Errorf("unexpected balancer config with type: %T", s.BalancerConfig) 178 } 179 180 b.stateAggregator.pauseStateUpdates() 181 defer b.stateAggregator.resumeStateUpdates() 182 return b.updateChildren(s, newConfig) 183 } 184 185 func (b *bal) ResolverError(err error) { 186 b.bg.ResolverError(err) 187 } 188 189 func (b *bal) UpdateSubConnState(sc balancer.SubConn, state balancer.SubConnState) { 190 b.logger.Errorf("UpdateSubConnState(%v, %+v) called unexpectedly", sc, state) 191 } 192 193 func (b *bal) Close() { 194 b.stateAggregator.close() 195 b.bg.Close() 196 b.logger.Infof("Shutdown") 197 } 198 199 func (b *bal) ExitIdle() { 200 b.bg.ExitIdle() 201 } 202 203 const prefix = "[xds-cluster-manager-lb %p] " 204 205 var logger = grpclog.Component("xds") 206 207 func prefixLogger(p *bal) *internalgrpclog.PrefixLogger { 208 return internalgrpclog.NewPrefixLogger(logger, fmt.Sprintf(prefix, p)) 209 }