google.golang.org/grpc@v1.72.2/balancer/weightedtarget/weightedtarget.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 weightedtarget implements the weighted_target balancer. 20 // 21 // All APIs in this package are experimental. 22 package weightedtarget 23 24 import ( 25 "encoding/json" 26 "fmt" 27 "time" 28 29 "google.golang.org/grpc/balancer" 30 "google.golang.org/grpc/balancer/weightedtarget/weightedaggregator" 31 "google.golang.org/grpc/internal/balancergroup" 32 "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/internal/wrr" 36 "google.golang.org/grpc/resolver" 37 "google.golang.org/grpc/serviceconfig" 38 ) 39 40 // Name is the name of the weighted_target balancer. 41 const Name = "weighted_target_experimental" 42 43 // NewRandomWRR is the WRR constructor used to pick sub-pickers from 44 // sub-balancers. It's to be modified in tests. 45 var NewRandomWRR = wrr.NewRandom 46 47 func init() { 48 balancer.Register(bb{}) 49 } 50 51 type bb struct{} 52 53 func (bb) Build(cc balancer.ClientConn, bOpts balancer.BuildOptions) balancer.Balancer { 54 b := &weightedTargetBalancer{} 55 b.logger = prefixLogger(b) 56 b.stateAggregator = weightedaggregator.New(cc, b.logger, NewRandomWRR) 57 b.stateAggregator.Start() 58 b.bg = balancergroup.New(balancergroup.Options{ 59 CC: cc, 60 BuildOpts: bOpts, 61 StateAggregator: b.stateAggregator, 62 Logger: b.logger, 63 SubBalancerCloseTimeout: time.Duration(0), // Disable caching of removed child policies 64 }) 65 b.logger.Infof("Created") 66 return b 67 } 68 69 func (bb) Name() string { 70 return Name 71 } 72 73 func (bb) ParseConfig(c json.RawMessage) (serviceconfig.LoadBalancingConfig, error) { 74 return parseConfig(c) 75 } 76 77 type weightedTargetBalancer struct { 78 logger *grpclog.PrefixLogger 79 80 bg *balancergroup.BalancerGroup 81 stateAggregator *weightedaggregator.Aggregator 82 83 targets map[string]Target 84 } 85 86 type localityKeyType string 87 88 const localityKey = localityKeyType("locality") 89 90 // LocalityFromResolverState returns the locality from the resolver.State 91 // provided, or an empty string if not present. 92 func LocalityFromResolverState(state resolver.State) string { 93 locality, _ := state.Attributes.Value(localityKey).(string) 94 return locality 95 } 96 97 // UpdateClientConnState takes the new targets in balancer group, 98 // creates/deletes sub-balancers and sends them update. addresses are split into 99 // groups based on hierarchy path. 100 func (b *weightedTargetBalancer) UpdateClientConnState(s balancer.ClientConnState) error { 101 if b.logger.V(2) { 102 b.logger.Infof("Received update from resolver, balancer config: %+v", pretty.ToJSON(s.BalancerConfig)) 103 } 104 105 newConfig, ok := s.BalancerConfig.(*LBConfig) 106 if !ok { 107 return fmt.Errorf("unexpected balancer config with type: %T", s.BalancerConfig) 108 } 109 addressesSplit := hierarchy.Group(s.ResolverState.Addresses) 110 endpointsSplit := hierarchy.GroupEndpoints(s.ResolverState.Endpoints) 111 112 b.stateAggregator.PauseStateUpdates() 113 defer b.stateAggregator.ResumeStateUpdates() 114 115 // Remove sub-pickers and sub-balancers that are not in the new config. 116 for name := range b.targets { 117 if _, ok := newConfig.Targets[name]; !ok { 118 b.stateAggregator.Remove(name) 119 b.bg.Remove(name) 120 } 121 } 122 123 // For sub-balancers in the new config 124 // - if it's new. add to balancer group, 125 // - if it's old, but has a new weight, update weight in balancer group. 126 // 127 // For all sub-balancers, forward the address/balancer config update. 128 for name, newT := range newConfig.Targets { 129 oldT, ok := b.targets[name] 130 if !ok { 131 // If this is a new sub-balancer, add weights to the picker map. 132 b.stateAggregator.Add(name, newT.Weight) 133 // Then add to the balancer group. 134 b.bg.Add(name, balancer.Get(newT.ChildPolicy.Name)) 135 // Not trigger a state/picker update. Wait for the new sub-balancer 136 // to send its updates. 137 } else if newT.ChildPolicy.Name != oldT.ChildPolicy.Name { 138 // If the child policy name is different, remove from balancer group 139 // and re-add. 140 b.stateAggregator.Remove(name) 141 b.bg.Remove(name) 142 b.stateAggregator.Add(name, newT.Weight) 143 b.bg.Add(name, balancer.Get(newT.ChildPolicy.Name)) 144 } else if newT.Weight != oldT.Weight { 145 // If this is an existing sub-balancer, update weight if necessary. 146 b.stateAggregator.UpdateWeight(name, newT.Weight) 147 } 148 149 // Forwards all the update: 150 // - addresses are from the map after splitting with hierarchy path, 151 // - Top level service config and attributes are the same, 152 // - Balancer config comes from the targets map. 153 // 154 // TODO: handle error? How to aggregate errors and return? 155 _ = b.bg.UpdateClientConnState(name, balancer.ClientConnState{ 156 ResolverState: resolver.State{ 157 Addresses: addressesSplit[name], 158 Endpoints: endpointsSplit[name], 159 ServiceConfig: s.ResolverState.ServiceConfig, 160 Attributes: s.ResolverState.Attributes.WithValue(localityKey, name), 161 }, 162 BalancerConfig: newT.ChildPolicy.Config, 163 }) 164 } 165 166 b.targets = newConfig.Targets 167 168 // If the targets length is zero, it means we have removed all child 169 // policies from the balancer group and aggregator. 170 // At the start of this UpdateClientConnState() operation, a call to 171 // b.stateAggregator.ResumeStateUpdates() is deferred. Thus, setting the 172 // needUpdateStateOnResume bool to true here will ensure a new picker is 173 // built as part of that deferred function. Since there are now no child 174 // policies, the aggregated connectivity state reported form the Aggregator 175 // will be TRANSIENT_FAILURE. 176 if len(b.targets) == 0 { 177 b.stateAggregator.NeedUpdateStateOnResume() 178 } 179 180 return nil 181 } 182 183 func (b *weightedTargetBalancer) ResolverError(err error) { 184 b.bg.ResolverError(err) 185 } 186 187 func (b *weightedTargetBalancer) UpdateSubConnState(sc balancer.SubConn, state balancer.SubConnState) { 188 b.logger.Errorf("UpdateSubConnState(%v, %+v) called unexpectedly", sc, state) 189 } 190 191 func (b *weightedTargetBalancer) Close() { 192 b.stateAggregator.Stop() 193 b.bg.Close() 194 } 195 196 func (b *weightedTargetBalancer) ExitIdle() { 197 b.bg.ExitIdle() 198 }