You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

308 lines
9.9 KiB

  1. /*
  2. * Copyright 2019 gRPC authors.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. // Package edsbalancer implements a balancer to handle EDS responses.
  17. package edsbalancer
  18. import (
  19. "context"
  20. "encoding/json"
  21. "fmt"
  22. "net"
  23. "reflect"
  24. "strconv"
  25. "sync"
  26. xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2"
  27. xdstypepb "github.com/envoyproxy/go-control-plane/envoy/type"
  28. "google.golang.org/grpc/balancer"
  29. "google.golang.org/grpc/balancer/roundrobin"
  30. "google.golang.org/grpc/codes"
  31. "google.golang.org/grpc/connectivity"
  32. "google.golang.org/grpc/grpclog"
  33. "google.golang.org/grpc/resolver"
  34. "google.golang.org/grpc/status"
  35. )
  36. type localityConfig struct {
  37. weight uint32
  38. addrs []resolver.Address
  39. }
  40. // EDSBalancer does load balancing based on the EDS responses. Note that it
  41. // doesn't implement the balancer interface. It's intended to be used by a high
  42. // level balancer implementation.
  43. //
  44. // The localities are picked as weighted round robin. A configurable child
  45. // policy is used to manage endpoints in each locality.
  46. type EDSBalancer struct {
  47. balancer.ClientConn
  48. bg *balancerGroup
  49. subBalancerBuilder balancer.Builder
  50. lidToConfig map[string]*localityConfig
  51. pickerMu sync.Mutex
  52. drops []*dropper
  53. innerPicker balancer.Picker // The picker without drop support.
  54. innerState connectivity.State // The state of the picker.
  55. }
  56. // NewXDSBalancer create a new EDSBalancer.
  57. func NewXDSBalancer(cc balancer.ClientConn) *EDSBalancer {
  58. xdsB := &EDSBalancer{
  59. ClientConn: cc,
  60. subBalancerBuilder: balancer.Get(roundrobin.Name),
  61. lidToConfig: make(map[string]*localityConfig),
  62. }
  63. // Don't start balancer group here. Start it when handling the first EDS
  64. // response. Otherwise the balancer group will be started with round-robin,
  65. // and if users specify a different sub-balancer, all balancers in balancer
  66. // group will be closed and recreated when sub-balancer update happens.
  67. return xdsB
  68. }
  69. // HandleChildPolicy updates the child balancers handling endpoints. Child
  70. // policy is roundrobin by default. If the specified balancer is not installed,
  71. // the old child balancer will be used.
  72. //
  73. // HandleChildPolicy and HandleEDSResponse must be called by the same goroutine.
  74. func (xdsB *EDSBalancer) HandleChildPolicy(name string, config json.RawMessage) {
  75. // name could come from cdsResp.GetLbPolicy().String(). LbPolicy.String()
  76. // are all UPPER_CASE with underscore.
  77. //
  78. // No conversion is needed here because balancer package converts all names
  79. // into lower_case before registering/looking up.
  80. xdsB.updateSubBalancerName(name)
  81. // TODO: (eds) send balancer config to the new child balancers.
  82. }
  83. func (xdsB *EDSBalancer) updateSubBalancerName(subBalancerName string) {
  84. if xdsB.subBalancerBuilder.Name() == subBalancerName {
  85. return
  86. }
  87. newSubBalancerBuilder := balancer.Get(subBalancerName)
  88. if newSubBalancerBuilder == nil {
  89. grpclog.Infof("EDSBalancer: failed to find balancer with name %q, keep using %q", subBalancerName, xdsB.subBalancerBuilder.Name())
  90. return
  91. }
  92. xdsB.subBalancerBuilder = newSubBalancerBuilder
  93. if xdsB.bg != nil {
  94. // xdsB.bg == nil until the first EDS response is handled. There's no
  95. // need to update balancer group before that.
  96. for id, config := range xdsB.lidToConfig {
  97. // TODO: (eds) add support to balancer group to support smoothly
  98. // switching sub-balancers (keep old balancer around until new
  99. // balancer becomes ready).
  100. xdsB.bg.remove(id)
  101. xdsB.bg.add(id, config.weight, xdsB.subBalancerBuilder)
  102. xdsB.bg.handleResolvedAddrs(id, config.addrs)
  103. }
  104. }
  105. }
  106. // updateDrops compares new drop policies with the old. If they are different,
  107. // it updates the drop policies and send ClientConn an updated picker.
  108. func (xdsB *EDSBalancer) updateDrops(dropPolicies []*xdspb.ClusterLoadAssignment_Policy_DropOverload) {
  109. var (
  110. newDrops []*dropper
  111. dropsChanged bool
  112. )
  113. for i, dropPolicy := range dropPolicies {
  114. percentage := dropPolicy.GetDropPercentage()
  115. var (
  116. numerator = percentage.GetNumerator()
  117. denominator uint32
  118. )
  119. switch percentage.GetDenominator() {
  120. case xdstypepb.FractionalPercent_HUNDRED:
  121. denominator = 100
  122. case xdstypepb.FractionalPercent_TEN_THOUSAND:
  123. denominator = 10000
  124. case xdstypepb.FractionalPercent_MILLION:
  125. denominator = 1000000
  126. }
  127. newDrops = append(newDrops, newDropper(numerator, denominator))
  128. // The following reading xdsB.drops doesn't need mutex because it can only
  129. // be updated by the code following.
  130. if dropsChanged {
  131. continue
  132. }
  133. if i >= len(xdsB.drops) {
  134. dropsChanged = true
  135. continue
  136. }
  137. if oldDrop := xdsB.drops[i]; numerator != oldDrop.numerator || denominator != oldDrop.denominator {
  138. dropsChanged = true
  139. }
  140. }
  141. if dropsChanged {
  142. xdsB.pickerMu.Lock()
  143. xdsB.drops = newDrops
  144. if xdsB.innerPicker != nil {
  145. // Update picker with old inner picker, new drops.
  146. xdsB.ClientConn.UpdateBalancerState(xdsB.innerState, newDropPicker(xdsB.innerPicker, newDrops))
  147. }
  148. xdsB.pickerMu.Unlock()
  149. }
  150. }
  151. // HandleEDSResponse handles the EDS response and creates/deletes localities and
  152. // SubConns. It also handles drops.
  153. //
  154. // HandleCDSResponse and HandleEDSResponse must be called by the same goroutine.
  155. func (xdsB *EDSBalancer) HandleEDSResponse(edsResp *xdspb.ClusterLoadAssignment) {
  156. // Create balancer group if it's never created (this is the first EDS
  157. // response).
  158. if xdsB.bg == nil {
  159. xdsB.bg = newBalancerGroup(xdsB)
  160. }
  161. // TODO: Unhandled fields from EDS response:
  162. // - edsResp.GetPolicy().GetOverprovisioningFactor()
  163. // - locality.GetPriority()
  164. // - lbEndpoint.GetMetadata(): contains BNS name, send to sub-balancers
  165. // - as service config or as resolved address
  166. // - if socketAddress is not ip:port
  167. // - socketAddress.GetNamedPort(), socketAddress.GetResolverName()
  168. // - resolve endpoint's name with another resolver
  169. xdsB.updateDrops(edsResp.GetPolicy().GetDropOverloads())
  170. // newLocalitiesSet contains all names of localitis in the new EDS response.
  171. // It's used to delete localities that are removed in the new EDS response.
  172. newLocalitiesSet := make(map[string]struct{})
  173. for _, locality := range edsResp.Endpoints {
  174. // One balancer for each locality.
  175. l := locality.GetLocality()
  176. if l == nil {
  177. grpclog.Warningf("xds: received LocalityLbEndpoints with <nil> Locality")
  178. continue
  179. }
  180. lid := fmt.Sprintf("%s-%s-%s", l.Region, l.Zone, l.SubZone)
  181. newLocalitiesSet[lid] = struct{}{}
  182. newWeight := locality.GetLoadBalancingWeight().GetValue()
  183. if newWeight == 0 {
  184. // Weight can never be 0.
  185. newWeight = 1
  186. }
  187. var newAddrs []resolver.Address
  188. for _, lbEndpoint := range locality.GetLbEndpoints() {
  189. socketAddress := lbEndpoint.GetEndpoint().GetAddress().GetSocketAddress()
  190. newAddrs = append(newAddrs, resolver.Address{
  191. Addr: net.JoinHostPort(socketAddress.GetAddress(), strconv.Itoa(int(socketAddress.GetPortValue()))),
  192. })
  193. }
  194. var weightChanged, addrsChanged bool
  195. config, ok := xdsB.lidToConfig[lid]
  196. if !ok {
  197. // A new balancer, add it to balancer group and balancer map.
  198. xdsB.bg.add(lid, newWeight, xdsB.subBalancerBuilder)
  199. config = &localityConfig{
  200. weight: newWeight,
  201. }
  202. xdsB.lidToConfig[lid] = config
  203. // weightChanged is false for new locality, because there's no need to
  204. // update weight in bg.
  205. addrsChanged = true
  206. } else {
  207. // Compare weight and addrs.
  208. if config.weight != newWeight {
  209. weightChanged = true
  210. }
  211. if !reflect.DeepEqual(config.addrs, newAddrs) {
  212. addrsChanged = true
  213. }
  214. }
  215. if weightChanged {
  216. config.weight = newWeight
  217. xdsB.bg.changeWeight(lid, newWeight)
  218. }
  219. if addrsChanged {
  220. config.addrs = newAddrs
  221. xdsB.bg.handleResolvedAddrs(lid, newAddrs)
  222. }
  223. }
  224. // Delete localities that are removed in the latest response.
  225. for lid := range xdsB.lidToConfig {
  226. if _, ok := newLocalitiesSet[lid]; !ok {
  227. xdsB.bg.remove(lid)
  228. delete(xdsB.lidToConfig, lid)
  229. }
  230. }
  231. }
  232. // HandleSubConnStateChange handles the state change and update pickers accordingly.
  233. func (xdsB *EDSBalancer) HandleSubConnStateChange(sc balancer.SubConn, s connectivity.State) {
  234. xdsB.bg.handleSubConnStateChange(sc, s)
  235. }
  236. // UpdateBalancerState overrides balancer.ClientConn to wrap the picker in a
  237. // dropPicker.
  238. func (xdsB *EDSBalancer) UpdateBalancerState(s connectivity.State, p balancer.Picker) {
  239. xdsB.pickerMu.Lock()
  240. defer xdsB.pickerMu.Unlock()
  241. xdsB.innerPicker = p
  242. xdsB.innerState = s
  243. // Don't reset drops when it's a state change.
  244. xdsB.ClientConn.UpdateBalancerState(s, newDropPicker(p, xdsB.drops))
  245. }
  246. // Close closes the balancer.
  247. func (xdsB *EDSBalancer) Close() {
  248. xdsB.bg.close()
  249. }
  250. type dropPicker struct {
  251. drops []*dropper
  252. p balancer.Picker
  253. }
  254. func newDropPicker(p balancer.Picker, drops []*dropper) *dropPicker {
  255. return &dropPicker{
  256. drops: drops,
  257. p: p,
  258. }
  259. }
  260. func (d *dropPicker) Pick(ctx context.Context, opts balancer.PickOptions) (conn balancer.SubConn, done func(balancer.DoneInfo), err error) {
  261. var drop bool
  262. for _, dp := range d.drops {
  263. // It's necessary to call drop on all droppers if the droppers are
  264. // stateful. For example, if the second drop only drops 1/2, and only
  265. // drops even number picks, we need to call it's drop() even if the
  266. // first dropper already returned true.
  267. //
  268. // It won't be necessary if droppers are stateless, like toss a coin.
  269. drop = drop || dp.drop()
  270. }
  271. if drop {
  272. return nil, nil, status.Errorf(codes.Unavailable, "RPC is dropped")
  273. }
  274. // TODO: (eds) don't drop unless the inner picker is READY. Similar to
  275. // https://github.com/grpc/grpc-go/issues/2622.
  276. return d.p.Pick(ctx, opts)
  277. }