Skip to content

Commit db4bb28

Browse files
committed
grpclb: new balancer V2
pickfirst test passed switching from roundrobin to pickfirst works switching from pickfirst to roundrobin works cleanup config actually parse service config 2019
1 parent 4745f6a commit db4bb28

File tree

5 files changed

+363
-59
lines changed

5 files changed

+363
-59
lines changed

balancer/grpclb/grpclb.go

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -129,19 +129,8 @@ func newLBBuilderWithFallbackTimeout(fallbackTimeout time.Duration) balancer.Bui
129129
}
130130
}
131131

132-
// newLBBuilderWithPickFirst creates a grpclb builder with pick-first.
133-
func newLBBuilderWithPickFirst() balancer.Builder {
134-
return &lbBuilder{
135-
usePickFirst: true,
136-
}
137-
}
138-
139132
type lbBuilder struct {
140133
fallbackTimeout time.Duration
141-
142-
// TODO: delete this when balancer can handle service config. This should be
143-
// updated by service config.
144-
usePickFirst bool // Use roundrobin or pickfirst for backends.
145134
}
146135

147136
func (b *lbBuilder) Name() string {
@@ -167,7 +156,6 @@ func (b *lbBuilder) Build(cc balancer.ClientConn, opt balancer.BuildOptions) bal
167156
cc: newLBCacheClientConn(cc),
168157
target: target,
169158
opt: opt,
170-
usePickFirst: b.usePickFirst,
171159
fallbackTimeout: b.fallbackTimeout,
172160
doneCh: make(chan struct{}),
173161

@@ -231,11 +219,14 @@ type lbBalancer struct {
231219
// serverList contains anything new. Each generate picker will also have
232220
// reference to this list to do the first layer pick.
233221
fullServerList []*lbpb.Server
222+
// Backend addresses. It's kept so the addresses are available when
223+
// switching between round_robin and pickfirst.
224+
backendAddrs []resolver.Address
234225
// All backends addresses, with metadata set to nil. This list contains all
235226
// backend addresses in the same order and with the same duplicates as in
236227
// serverlist. When generating picker, a SubConn slice with the same order
237228
// but with only READY SCs will be gerenated.
238-
backendAddrs []resolver.Address
229+
backendAddrsWithoutMetadata []resolver.Address
239230
// Roundrobin functionalities.
240231
state connectivity.State
241232
subConns map[resolver.Address]balancer.SubConn // Used to new/remove SubConn.
@@ -275,7 +266,7 @@ func (lb *lbBalancer) regeneratePicker(resetDrop bool) {
275266
break
276267
}
277268
} else {
278-
for _, a := range lb.backendAddrs {
269+
for _, a := range lb.backendAddrsWithoutMetadata {
279270
if sc, ok := lb.subConns[a]; ok {
280271
if st, ok := lb.scStates[sc]; ok && st == connectivity.Ready {
281272
readySCs = append(readySCs, sc)
@@ -339,6 +330,11 @@ func (lb *lbBalancer) aggregateSubConnStates() connectivity.State {
339330
}
340331

341332
func (lb *lbBalancer) HandleSubConnStateChange(sc balancer.SubConn, s connectivity.State) {
333+
panic("not used")
334+
}
335+
336+
func (lb *lbBalancer) UpdateSubConnState(sc balancer.SubConn, scs balancer.SubConnState) {
337+
s := scs.ConnectivityState
342338
if grpclog.V(2) {
343339
grpclog.Infof("lbBalancer: handle SubConn state change: %p, %v", sc, s)
344340
}
@@ -371,7 +367,7 @@ func (lb *lbBalancer) HandleSubConnStateChange(sc balancer.SubConn, s connectivi
371367
if lb.state != connectivity.Ready {
372368
if !lb.inFallback && !lb.remoteBalancerConnected {
373369
// Enter fallback.
374-
lb.refreshSubConns(lb.resolvedBackendAddrs, false)
370+
lb.refreshSubConns(lb.resolvedBackendAddrs, false, lb.usePickFirst)
375371
}
376372
}
377373
}
@@ -410,17 +406,39 @@ func (lb *lbBalancer) fallbackToBackendsAfter(fallbackTimeout time.Duration) {
410406
return
411407
}
412408
// Enter fallback.
413-
lb.refreshSubConns(lb.resolvedBackendAddrs, false)
409+
lb.refreshSubConns(lb.resolvedBackendAddrs, true, lb.usePickFirst)
414410
lb.mu.Unlock()
415411
}
416412

417413
// HandleResolvedAddrs sends the updated remoteLB addresses to remoteLB
418414
// clientConn. The remoteLB clientConn will handle creating/removing remoteLB
419415
// connections.
420416
func (lb *lbBalancer) HandleResolvedAddrs(addrs []resolver.Address, err error) {
417+
panic("not used")
418+
}
419+
420+
func (lb *lbBalancer) handleServiceConfig(sc string) {
421+
lb.mu.Lock()
422+
defer lb.mu.Unlock()
423+
424+
newUsePickFirst := childIsPickFirst(sc)
425+
if lb.usePickFirst == newUsePickFirst {
426+
return
427+
}
421428
if grpclog.V(2) {
422-
grpclog.Infof("lbBalancer: handleResolvedResult: %+v", addrs)
429+
grpclog.Infof("lbBalancer: switching mode, new usePickFirst: %+v", newUsePickFirst)
423430
}
431+
lb.refreshSubConns(lb.backendAddrs, lb.inFallback, newUsePickFirst)
432+
lb.regeneratePicker(true)
433+
}
434+
435+
func (lb *lbBalancer) UpdateResolverState(rs resolver.State) {
436+
if grpclog.V(2) {
437+
grpclog.Infof("lbBalancer: UpdateResolverState: %+v", rs)
438+
}
439+
lb.handleServiceConfig(rs.ServiceConfig)
440+
441+
addrs := rs.Addresses
424442
if len(addrs) <= 0 {
425443
return
426444
}
@@ -457,7 +475,7 @@ func (lb *lbBalancer) HandleResolvedAddrs(addrs []resolver.Address, err error) {
457475
// This means we received a new list of resolved backends, and we are
458476
// still in fallback mode. Need to update the list of backends we are
459477
// using to the new list of backends.
460-
lb.refreshSubConns(lb.resolvedBackendAddrs, false)
478+
lb.refreshSubConns(lb.resolvedBackendAddrs, true, lb.usePickFirst)
461479
}
462480
lb.mu.Unlock()
463481
}

balancer/grpclb/grpclb_config.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
*
3+
* Copyright 2019 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 grpclb
20+
21+
import (
22+
"encoding/json"
23+
24+
"google.golang.org/grpc"
25+
"google.golang.org/grpc/balancer/roundrobin"
26+
)
27+
28+
type serviceConfig struct {
29+
LoadBalancingConfig *[]map[string]*grpclbServiceConfig
30+
}
31+
32+
type grpclbServiceConfig struct {
33+
ChildPolicy *[]map[string]json.RawMessage
34+
}
35+
36+
func parseFullServiceConfig(s string) *serviceConfig {
37+
var ret serviceConfig
38+
err := json.Unmarshal([]byte(s), &ret)
39+
if err != nil {
40+
return nil
41+
}
42+
return &ret
43+
}
44+
45+
func parseServiceConfig(s string) *grpclbServiceConfig {
46+
parsedSC := parseFullServiceConfig(s)
47+
if parsedSC == nil {
48+
return nil
49+
}
50+
lbConfigs := parsedSC.LoadBalancingConfig
51+
if lbConfigs == nil {
52+
return nil
53+
}
54+
for _, lbC := range *lbConfigs {
55+
if v, ok := lbC[grpclbName]; ok {
56+
return v
57+
}
58+
}
59+
return nil
60+
}
61+
62+
const (
63+
roundRobinName = roundrobin.Name
64+
pickFirstName = grpc.PickFirstBalancerName
65+
)
66+
67+
func childIsPickFirst(s string) bool {
68+
parsedSC := parseServiceConfig(s)
69+
if parsedSC == nil {
70+
return false
71+
}
72+
childConfigs := parsedSC.ChildPolicy
73+
if childConfigs == nil {
74+
return false
75+
}
76+
for _, childC := range *childConfigs {
77+
// If round_robin exists before pick_first, return false
78+
if _, ok := childC[roundRobinName]; ok {
79+
return false
80+
}
81+
// If pick_first is before round_robin, return true
82+
if _, ok := childC[pickFirstName]; ok {
83+
return true
84+
}
85+
}
86+
return false
87+
}
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/*
2+
*
3+
* Copyright 2019 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 grpclb
20+
21+
import (
22+
"encoding/json"
23+
"reflect"
24+
"testing"
25+
)
26+
27+
func Test_parseFullServiceConfig(t *testing.T) {
28+
tests := []struct {
29+
name string
30+
s string
31+
want *serviceConfig
32+
}{
33+
{
34+
name: "empty",
35+
s: "",
36+
want: nil,
37+
},
38+
{
39+
name: "success1",
40+
s: `{"loadBalancingConfig":[{"grpclb":{"childPolicy":[{"pick_first":{}}]}}]}`,
41+
want: &serviceConfig{
42+
LoadBalancingConfig: &[]map[string]*grpclbServiceConfig{
43+
{"grpclb": &grpclbServiceConfig{
44+
ChildPolicy: &[]map[string]json.RawMessage{
45+
{"pick_first": json.RawMessage("{}")},
46+
},
47+
}},
48+
},
49+
},
50+
},
51+
{
52+
name: "success2",
53+
s: `{"loadBalancingConfig":[{"grpclb":{"childPolicy":[{"round_robin":{}},{"pick_first":{}}]}}]}`,
54+
want: &serviceConfig{
55+
LoadBalancingConfig: &[]map[string]*grpclbServiceConfig{
56+
{"grpclb": &grpclbServiceConfig{
57+
ChildPolicy: &[]map[string]json.RawMessage{
58+
{"round_robin": json.RawMessage("{}")},
59+
{"pick_first": json.RawMessage("{}")},
60+
},
61+
}},
62+
},
63+
},
64+
},
65+
}
66+
for _, tt := range tests {
67+
t.Run(tt.name, func(t *testing.T) {
68+
if got := parseFullServiceConfig(tt.s); !reflect.DeepEqual(got, tt.want) {
69+
t.Errorf("parseFullServiceConfig() = %+v, want %+v", got, tt.want)
70+
}
71+
})
72+
}
73+
}
74+
75+
func Test_parseServiceConfig(t *testing.T) {
76+
tests := []struct {
77+
name string
78+
s string
79+
want *grpclbServiceConfig
80+
}{
81+
{
82+
name: "empty",
83+
s: "",
84+
want: nil,
85+
},
86+
{
87+
name: "success1",
88+
s: `{"loadBalancingConfig":[{"grpclb":{"childPolicy":[{"pick_first":{}}]}}]}`,
89+
want: &grpclbServiceConfig{
90+
ChildPolicy: &[]map[string]json.RawMessage{
91+
{"pick_first": json.RawMessage("{}")},
92+
},
93+
},
94+
},
95+
{
96+
name: "success2",
97+
s: `{"loadBalancingConfig":[{"grpclb":{"childPolicy":[{"round_robin":{}},{"pick_first":{}}]}}]}`,
98+
want: &grpclbServiceConfig{
99+
ChildPolicy: &[]map[string]json.RawMessage{
100+
{"round_robin": json.RawMessage("{}")},
101+
{"pick_first": json.RawMessage("{}")},
102+
},
103+
},
104+
},
105+
{
106+
name: "no_grpclb",
107+
s: `{"loadBalancingConfig":[{"notgrpclb":{"childPolicy":[{"round_robin":{}},{"pick_first":{}}]}}]}`,
108+
want: nil,
109+
},
110+
}
111+
for _, tt := range tests {
112+
t.Run(tt.name, func(t *testing.T) {
113+
if got := parseServiceConfig(tt.s); !reflect.DeepEqual(got, tt.want) {
114+
t.Errorf("parseFullServiceConfig() = %+v, want %+v", got, tt.want)
115+
}
116+
})
117+
}
118+
}
119+
120+
func Test_childIsPickFirst(t *testing.T) {
121+
tests := []struct {
122+
name string
123+
s string
124+
want bool
125+
}{
126+
{
127+
name: "invalid",
128+
s: "",
129+
want: false,
130+
},
131+
{
132+
name: "pickfirst_only",
133+
s: `{"loadBalancingConfig":[{"grpclb":{"childPolicy":[{"pick_first":{}}]}}]}`,
134+
want: true,
135+
},
136+
{
137+
name: "pickfirst_before_rr",
138+
s: `{"loadBalancingConfig":[{"grpclb":{"childPolicy":[{"pick_first":{}},{"round_robin":{}}]}}]}`,
139+
want: true,
140+
},
141+
{
142+
name: "rr_before_pickfirst",
143+
s: `{"loadBalancingConfig":[{"grpclb":{"childPolicy":[{"round_robin":{}},{"pick_first":{}}]}}]}`,
144+
want: false,
145+
},
146+
}
147+
for _, tt := range tests {
148+
t.Run(tt.name, func(t *testing.T) {
149+
if got := childIsPickFirst(tt.s); got != tt.want {
150+
t.Errorf("childIsPickFirst() = %v, want %v", got, tt.want)
151+
}
152+
})
153+
}
154+
}

0 commit comments

Comments
 (0)