google.golang.org/grpc@v1.72.2/xds/internal/balancer/wrrlocality/balancer.go (about) 1 /* 2 * 3 * Copyright 2023 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 wrrlocality provides an implementation of the wrr locality LB policy, 20 // as defined in [A52 - xDS Custom LB Policies]. 21 // 22 // [A52 - xDS Custom LB Policies]: https://github.com/grpc/proposal/blob/master/A52-xds-custom-lb-policies.md 23 package wrrlocality 24 25 import ( 26 "encoding/json" 27 "errors" 28 "fmt" 29 30 "google.golang.org/grpc/balancer" 31 "google.golang.org/grpc/balancer/weightedtarget" 32 "google.golang.org/grpc/internal/grpclog" 33 internalserviceconfig "google.golang.org/grpc/internal/serviceconfig" 34 "google.golang.org/grpc/resolver" 35 "google.golang.org/grpc/serviceconfig" 36 "google.golang.org/grpc/xds/internal" 37 ) 38 39 // Name is the name of wrr_locality balancer. 40 const Name = "xds_wrr_locality_experimental" 41 42 func init() { 43 balancer.Register(bb{}) 44 } 45 46 type bb struct{} 47 48 func (bb) Name() string { 49 return Name 50 } 51 52 // LBConfig is the config for the wrr locality balancer. 53 type LBConfig struct { 54 serviceconfig.LoadBalancingConfig `json:"-"` 55 // ChildPolicy is the config for the child policy. 56 ChildPolicy *internalserviceconfig.BalancerConfig `json:"childPolicy,omitempty"` 57 } 58 59 // To plumb in a different child in tests. 60 var weightedTargetName = weightedtarget.Name 61 62 func (bb) Build(cc balancer.ClientConn, bOpts balancer.BuildOptions) balancer.Balancer { 63 builder := balancer.Get(weightedTargetName) 64 if builder == nil { 65 // Shouldn't happen, registered through imported weighted target, 66 // defensive programming. 67 return nil 68 } 69 70 // Doesn't need to intercept any balancer.ClientConn operations; pass 71 // through by just giving cc to child balancer. 72 wtb := builder.Build(cc, bOpts) 73 if wtb == nil { 74 // shouldn't happen, defensive programming. 75 return nil 76 } 77 wtbCfgParser, ok := builder.(balancer.ConfigParser) 78 if !ok { 79 // Shouldn't happen, imported weighted target builder has this method. 80 return nil 81 } 82 wrrL := &wrrLocalityBalancer{ 83 child: wtb, 84 childParser: wtbCfgParser, 85 } 86 87 wrrL.logger = prefixLogger(wrrL) 88 wrrL.logger.Infof("Created") 89 return wrrL 90 } 91 92 func (bb) ParseConfig(s json.RawMessage) (serviceconfig.LoadBalancingConfig, error) { 93 var lbCfg *LBConfig 94 if err := json.Unmarshal(s, &lbCfg); err != nil { 95 return nil, fmt.Errorf("xds_wrr_locality: invalid LBConfig: %s, error: %v", string(s), err) 96 } 97 if lbCfg == nil || lbCfg.ChildPolicy == nil { 98 return nil, errors.New("xds_wrr_locality: invalid LBConfig: child policy field must be set") 99 } 100 return lbCfg, nil 101 } 102 103 type attributeKey struct{} 104 105 // Equal allows the values to be compared by Attributes.Equal. 106 func (a AddrInfo) Equal(o any) bool { 107 oa, ok := o.(AddrInfo) 108 return ok && oa.LocalityWeight == a.LocalityWeight 109 } 110 111 // AddrInfo is the locality weight of the locality an address is a part of. 112 type AddrInfo struct { 113 LocalityWeight uint32 114 } 115 116 // SetAddrInfo returns a copy of addr in which the BalancerAttributes field is 117 // updated with AddrInfo. 118 func SetAddrInfo(addr resolver.Address, addrInfo AddrInfo) resolver.Address { 119 addr.BalancerAttributes = addr.BalancerAttributes.WithValue(attributeKey{}, addrInfo) 120 return addr 121 } 122 123 // SetAddrInfoInEndpoint returns a copy of endpoint in which the Attributes 124 // field is updated with AddrInfo. 125 func SetAddrInfoInEndpoint(endpoint resolver.Endpoint, addrInfo AddrInfo) resolver.Endpoint { 126 endpoint.Attributes = endpoint.Attributes.WithValue(attributeKey{}, addrInfo) 127 return endpoint 128 } 129 130 func (a AddrInfo) String() string { 131 return fmt.Sprintf("Locality Weight: %d", a.LocalityWeight) 132 } 133 134 // getAddrInfo returns the AddrInfo stored in the BalancerAttributes field of 135 // addr. Returns false if no AddrInfo found. 136 func getAddrInfo(addr resolver.Address) (AddrInfo, bool) { 137 v := addr.BalancerAttributes.Value(attributeKey{}) 138 ai, ok := v.(AddrInfo) 139 return ai, ok 140 } 141 142 // wrrLocalityBalancer wraps a weighted target balancer, and builds 143 // configuration for the weighted target once it receives configuration 144 // specifying the weighted target child balancer and locality weight 145 // information. 146 type wrrLocalityBalancer struct { 147 // child will be a weighted target balancer, and will be built it at 148 // wrrLocalityBalancer build time. Other than preparing configuration, other 149 // balancer operations are simply pass through. 150 child balancer.Balancer 151 152 childParser balancer.ConfigParser 153 154 logger *grpclog.PrefixLogger 155 } 156 157 func (b *wrrLocalityBalancer) UpdateClientConnState(s balancer.ClientConnState) error { 158 lbCfg, ok := s.BalancerConfig.(*LBConfig) 159 if !ok { 160 b.logger.Errorf("Received config with unexpected type %T: %v", s.BalancerConfig, s.BalancerConfig) 161 return balancer.ErrBadResolverState 162 } 163 164 weightedTargets := make(map[string]weightedtarget.Target) 165 for _, addr := range s.ResolverState.Addresses { 166 // This get of LocalityID could potentially return a zero value. This 167 // shouldn't happen though (this attribute that is set actually gets 168 // used to build localities in the first place), and thus don't error 169 // out, and just build a weighted target with undefined behavior. 170 locality, err := internal.GetLocalityID(addr).ToString() 171 if err != nil { 172 // Should never happen. 173 logger.Errorf("Failed to marshal LocalityID: %v, skipping this locality in weighted target") 174 } 175 ai, ok := getAddrInfo(addr) 176 if !ok { 177 return fmt.Errorf("xds_wrr_locality: missing locality weight information in address %q", addr) 178 } 179 weightedTargets[locality] = weightedtarget.Target{Weight: ai.LocalityWeight, ChildPolicy: lbCfg.ChildPolicy} 180 } 181 wtCfg := &weightedtarget.LBConfig{Targets: weightedTargets} 182 wtCfgJSON, err := json.Marshal(wtCfg) 183 if err != nil { 184 // Shouldn't happen. 185 return fmt.Errorf("xds_wrr_locality: error marshalling prepared config: %v", wtCfg) 186 } 187 var sc serviceconfig.LoadBalancingConfig 188 if sc, err = b.childParser.ParseConfig(wtCfgJSON); err != nil { 189 return fmt.Errorf("xds_wrr_locality: config generated %v is invalid: %v", wtCfgJSON, err) 190 } 191 192 return b.child.UpdateClientConnState(balancer.ClientConnState{ 193 ResolverState: s.ResolverState, 194 BalancerConfig: sc, 195 }) 196 } 197 198 func (b *wrrLocalityBalancer) ResolverError(err error) { 199 b.child.ResolverError(err) 200 } 201 202 func (b *wrrLocalityBalancer) UpdateSubConnState(sc balancer.SubConn, state balancer.SubConnState) { 203 b.logger.Errorf("UpdateSubConnState(%v, %+v) called unexpectedly", sc, state) 204 } 205 206 func (b *wrrLocalityBalancer) Close() { 207 b.child.Close() 208 }