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.
 
 
 

527 lines
16 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
  17. import (
  18. "context"
  19. "fmt"
  20. "net"
  21. "reflect"
  22. "strconv"
  23. "testing"
  24. xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2"
  25. corepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
  26. endpointpb "github.com/envoyproxy/go-control-plane/envoy/api/v2/endpoint"
  27. xdstypepb "github.com/envoyproxy/go-control-plane/envoy/type"
  28. typespb "github.com/gogo/protobuf/types"
  29. "google.golang.org/grpc/balancer"
  30. "google.golang.org/grpc/balancer/roundrobin"
  31. "google.golang.org/grpc/connectivity"
  32. "google.golang.org/grpc/resolver"
  33. )
  34. var (
  35. testClusterNames = []string{"test-cluster-1", "test-cluster-2"}
  36. testSubZones = []string{"I", "II", "III", "IV"}
  37. testEndpointAddrs = []string{"1.1.1.1:1", "2.2.2.2:2", "3.3.3.3:3", "4.4.4.4:4"}
  38. )
  39. type clusterLoadAssignmentBuilder struct {
  40. v *xdspb.ClusterLoadAssignment
  41. }
  42. func newClusterLoadAssignmentBuilder(clusterName string, dropPercents []uint32) *clusterLoadAssignmentBuilder {
  43. var drops []*xdspb.ClusterLoadAssignment_Policy_DropOverload
  44. for i, d := range dropPercents {
  45. drops = append(drops, &xdspb.ClusterLoadAssignment_Policy_DropOverload{
  46. Category: fmt.Sprintf("test-drop-%d", i),
  47. DropPercentage: &xdstypepb.FractionalPercent{
  48. Numerator: d,
  49. Denominator: xdstypepb.FractionalPercent_HUNDRED,
  50. },
  51. })
  52. }
  53. return &clusterLoadAssignmentBuilder{
  54. v: &xdspb.ClusterLoadAssignment{
  55. ClusterName: clusterName,
  56. Policy: &xdspb.ClusterLoadAssignment_Policy{
  57. DropOverloads: drops,
  58. },
  59. },
  60. }
  61. }
  62. func (clab *clusterLoadAssignmentBuilder) addLocality(subzone string, weight uint32, addrsWithPort []string) {
  63. var lbEndPoints []endpointpb.LbEndpoint
  64. for _, a := range addrsWithPort {
  65. host, portStr, err := net.SplitHostPort(a)
  66. if err != nil {
  67. panic("failed to split " + a)
  68. }
  69. port, err := strconv.Atoi(portStr)
  70. if err != nil {
  71. panic("failed to atoi " + portStr)
  72. }
  73. lbEndPoints = append(lbEndPoints, endpointpb.LbEndpoint{
  74. HostIdentifier: &endpointpb.LbEndpoint_Endpoint{
  75. Endpoint: &endpointpb.Endpoint{
  76. Address: &corepb.Address{
  77. Address: &corepb.Address_SocketAddress{
  78. SocketAddress: &corepb.SocketAddress{
  79. Protocol: corepb.TCP,
  80. Address: host,
  81. PortSpecifier: &corepb.SocketAddress_PortValue{
  82. PortValue: uint32(port)}}}}}}},
  83. )
  84. }
  85. clab.v.Endpoints = append(clab.v.Endpoints, endpointpb.LocalityLbEndpoints{
  86. Locality: &corepb.Locality{
  87. Region: "",
  88. Zone: "",
  89. SubZone: subzone,
  90. },
  91. LbEndpoints: lbEndPoints,
  92. LoadBalancingWeight: &typespb.UInt32Value{Value: weight},
  93. })
  94. }
  95. func (clab *clusterLoadAssignmentBuilder) build() *xdspb.ClusterLoadAssignment {
  96. return clab.v
  97. }
  98. // One locality
  99. // - add backend
  100. // - remove backend
  101. // - replace backend
  102. // - change drop rate
  103. func TestEDS_OneLocality(t *testing.T) {
  104. cc := newTestClientConn(t)
  105. edsb := NewXDSBalancer(cc)
  106. // One locality with one backend.
  107. clab1 := newClusterLoadAssignmentBuilder(testClusterNames[0], nil)
  108. clab1.addLocality(testSubZones[0], 1, testEndpointAddrs[:1])
  109. edsb.HandleEDSResponse(clab1.build())
  110. sc1 := <-cc.newSubConnCh
  111. edsb.HandleSubConnStateChange(sc1, connectivity.Connecting)
  112. edsb.HandleSubConnStateChange(sc1, connectivity.Ready)
  113. // Pick with only the first backend.
  114. p1 := <-cc.newPickerCh
  115. for i := 0; i < 5; i++ {
  116. gotSC, _, _ := p1.Pick(context.Background(), balancer.PickOptions{})
  117. if !reflect.DeepEqual(gotSC, sc1) {
  118. t.Fatalf("picker.Pick, got %v, want %v", gotSC, sc1)
  119. }
  120. }
  121. // The same locality, add one more backend.
  122. clab2 := newClusterLoadAssignmentBuilder(testClusterNames[0], nil)
  123. clab2.addLocality(testSubZones[0], 1, testEndpointAddrs[:2])
  124. edsb.HandleEDSResponse(clab2.build())
  125. sc2 := <-cc.newSubConnCh
  126. edsb.HandleSubConnStateChange(sc2, connectivity.Connecting)
  127. edsb.HandleSubConnStateChange(sc2, connectivity.Ready)
  128. // Test roundrobin with two subconns.
  129. p2 := <-cc.newPickerCh
  130. want := []balancer.SubConn{sc1, sc2}
  131. if err := isRoundRobin(want, func() balancer.SubConn {
  132. sc, _, _ := p2.Pick(context.Background(), balancer.PickOptions{})
  133. return sc
  134. }); err != nil {
  135. t.Fatalf("want %v, got %v", want, err)
  136. }
  137. // The same locality, delete first backend.
  138. clab3 := newClusterLoadAssignmentBuilder(testClusterNames[0], nil)
  139. clab3.addLocality(testSubZones[0], 1, testEndpointAddrs[1:2])
  140. edsb.HandleEDSResponse(clab3.build())
  141. scToRemove := <-cc.removeSubConnCh
  142. if !reflect.DeepEqual(scToRemove, sc1) {
  143. t.Fatalf("RemoveSubConn, want %v, got %v", sc1, scToRemove)
  144. }
  145. edsb.HandleSubConnStateChange(scToRemove, connectivity.Shutdown)
  146. // Test pick with only the second subconn.
  147. p3 := <-cc.newPickerCh
  148. for i := 0; i < 5; i++ {
  149. gotSC, _, _ := p3.Pick(context.Background(), balancer.PickOptions{})
  150. if !reflect.DeepEqual(gotSC, sc2) {
  151. t.Fatalf("picker.Pick, got %v, want %v", gotSC, sc2)
  152. }
  153. }
  154. // The same locality, replace backend.
  155. clab4 := newClusterLoadAssignmentBuilder(testClusterNames[0], nil)
  156. clab4.addLocality(testSubZones[0], 1, testEndpointAddrs[2:3])
  157. edsb.HandleEDSResponse(clab4.build())
  158. sc3 := <-cc.newSubConnCh
  159. edsb.HandleSubConnStateChange(sc3, connectivity.Connecting)
  160. edsb.HandleSubConnStateChange(sc3, connectivity.Ready)
  161. scToRemove = <-cc.removeSubConnCh
  162. if !reflect.DeepEqual(scToRemove, sc2) {
  163. t.Fatalf("RemoveSubConn, want %v, got %v", sc2, scToRemove)
  164. }
  165. edsb.HandleSubConnStateChange(scToRemove, connectivity.Shutdown)
  166. // Test pick with only the third subconn.
  167. p4 := <-cc.newPickerCh
  168. for i := 0; i < 5; i++ {
  169. gotSC, _, _ := p4.Pick(context.Background(), balancer.PickOptions{})
  170. if !reflect.DeepEqual(gotSC, sc3) {
  171. t.Fatalf("picker.Pick, got %v, want %v", gotSC, sc3)
  172. }
  173. }
  174. // The same locality, different drop rate, dropping 50%.
  175. clab5 := newClusterLoadAssignmentBuilder(testClusterNames[0], []uint32{50})
  176. clab5.addLocality(testSubZones[0], 1, testEndpointAddrs[2:3])
  177. edsb.HandleEDSResponse(clab5.build())
  178. // Picks with drops.
  179. p5 := <-cc.newPickerCh
  180. for i := 0; i < 100; i++ {
  181. _, _, err := p5.Pick(context.Background(), balancer.PickOptions{})
  182. // TODO: the dropping algorithm needs a design. When the dropping algorithm
  183. // is fixed, this test also needs fix.
  184. if i < 50 && err == nil {
  185. t.Errorf("The first 50%% picks should be drops, got error <nil>")
  186. } else if i > 50 && err != nil {
  187. t.Errorf("The second 50%% picks should be non-drops, got error %v", err)
  188. }
  189. }
  190. }
  191. // 2 locality
  192. // - start with 2 locality
  193. // - add locality
  194. // - remove locality
  195. // - address change for the <not-the-first> locality
  196. // - update locality weight
  197. func TestEDS_TwoLocalities(t *testing.T) {
  198. cc := newTestClientConn(t)
  199. edsb := NewXDSBalancer(cc)
  200. // Two localities, each with one backend.
  201. clab1 := newClusterLoadAssignmentBuilder(testClusterNames[0], nil)
  202. clab1.addLocality(testSubZones[0], 1, testEndpointAddrs[:1])
  203. clab1.addLocality(testSubZones[1], 1, testEndpointAddrs[1:2])
  204. edsb.HandleEDSResponse(clab1.build())
  205. sc1 := <-cc.newSubConnCh
  206. edsb.HandleSubConnStateChange(sc1, connectivity.Connecting)
  207. edsb.HandleSubConnStateChange(sc1, connectivity.Ready)
  208. sc2 := <-cc.newSubConnCh
  209. edsb.HandleSubConnStateChange(sc2, connectivity.Connecting)
  210. edsb.HandleSubConnStateChange(sc2, connectivity.Ready)
  211. // Test roundrobin with two subconns.
  212. p1 := <-cc.newPickerCh
  213. want := []balancer.SubConn{sc1, sc2}
  214. if err := isRoundRobin(want, func() balancer.SubConn {
  215. sc, _, _ := p1.Pick(context.Background(), balancer.PickOptions{})
  216. return sc
  217. }); err != nil {
  218. t.Fatalf("want %v, got %v", want, err)
  219. }
  220. // Add another locality, with one backend.
  221. clab2 := newClusterLoadAssignmentBuilder(testClusterNames[0], nil)
  222. clab2.addLocality(testSubZones[0], 1, testEndpointAddrs[:1])
  223. clab2.addLocality(testSubZones[1], 1, testEndpointAddrs[1:2])
  224. clab2.addLocality(testSubZones[2], 1, testEndpointAddrs[2:3])
  225. edsb.HandleEDSResponse(clab2.build())
  226. sc3 := <-cc.newSubConnCh
  227. edsb.HandleSubConnStateChange(sc3, connectivity.Connecting)
  228. edsb.HandleSubConnStateChange(sc3, connectivity.Ready)
  229. // Test roundrobin with three subconns.
  230. p2 := <-cc.newPickerCh
  231. want = []balancer.SubConn{sc1, sc2, sc3}
  232. if err := isRoundRobin(want, func() balancer.SubConn {
  233. sc, _, _ := p2.Pick(context.Background(), balancer.PickOptions{})
  234. return sc
  235. }); err != nil {
  236. t.Fatalf("want %v, got %v", want, err)
  237. }
  238. // Remove first locality.
  239. clab3 := newClusterLoadAssignmentBuilder(testClusterNames[0], nil)
  240. clab3.addLocality(testSubZones[1], 1, testEndpointAddrs[1:2])
  241. clab3.addLocality(testSubZones[2], 1, testEndpointAddrs[2:3])
  242. edsb.HandleEDSResponse(clab3.build())
  243. scToRemove := <-cc.removeSubConnCh
  244. if !reflect.DeepEqual(scToRemove, sc1) {
  245. t.Fatalf("RemoveSubConn, want %v, got %v", sc1, scToRemove)
  246. }
  247. edsb.HandleSubConnStateChange(scToRemove, connectivity.Shutdown)
  248. // Test pick with two subconns (without the first one).
  249. p3 := <-cc.newPickerCh
  250. want = []balancer.SubConn{sc2, sc3}
  251. if err := isRoundRobin(want, func() balancer.SubConn {
  252. sc, _, _ := p3.Pick(context.Background(), balancer.PickOptions{})
  253. return sc
  254. }); err != nil {
  255. t.Fatalf("want %v, got %v", want, err)
  256. }
  257. // Add a backend to the last locality.
  258. clab4 := newClusterLoadAssignmentBuilder(testClusterNames[0], nil)
  259. clab4.addLocality(testSubZones[1], 1, testEndpointAddrs[1:2])
  260. clab4.addLocality(testSubZones[2], 1, testEndpointAddrs[2:4])
  261. edsb.HandleEDSResponse(clab4.build())
  262. sc4 := <-cc.newSubConnCh
  263. edsb.HandleSubConnStateChange(sc4, connectivity.Connecting)
  264. edsb.HandleSubConnStateChange(sc4, connectivity.Ready)
  265. // Test pick with two subconns (without the first one).
  266. p4 := <-cc.newPickerCh
  267. // Locality-1 will be picked twice, and locality-2 will be picked twice.
  268. // Locality-1 contains only sc2, locality-2 contains sc3 and sc4. So expect
  269. // two sc2's and sc3, sc4.
  270. want = []balancer.SubConn{sc2, sc2, sc3, sc4}
  271. if err := isRoundRobin(want, func() balancer.SubConn {
  272. sc, _, _ := p4.Pick(context.Background(), balancer.PickOptions{})
  273. return sc
  274. }); err != nil {
  275. t.Fatalf("want %v, got %v", want, err)
  276. }
  277. // Change weight of the locality[1].
  278. clab5 := newClusterLoadAssignmentBuilder(testClusterNames[0], nil)
  279. clab5.addLocality(testSubZones[1], 2, testEndpointAddrs[1:2])
  280. clab5.addLocality(testSubZones[2], 1, testEndpointAddrs[2:4])
  281. edsb.HandleEDSResponse(clab5.build())
  282. // Test pick with two subconns different locality weight.
  283. p5 := <-cc.newPickerCh
  284. // Locality-1 will be picked four times, and locality-2 will be picked twice
  285. // (weight 2 and 1). Locality-1 contains only sc2, locality-2 contains sc3 and
  286. // sc4. So expect four sc2's and sc3, sc4.
  287. want = []balancer.SubConn{sc2, sc2, sc2, sc2, sc3, sc4}
  288. if err := isRoundRobin(want, func() balancer.SubConn {
  289. sc, _, _ := p5.Pick(context.Background(), balancer.PickOptions{})
  290. return sc
  291. }); err != nil {
  292. t.Fatalf("want %v, got %v", want, err)
  293. }
  294. }
  295. func init() {
  296. balancer.Register(&testConstBalancerBuilder{})
  297. }
  298. var errTestConstPicker = fmt.Errorf("const picker error")
  299. type testConstBalancerBuilder struct{}
  300. func (*testConstBalancerBuilder) Build(cc balancer.ClientConn, opts balancer.BuildOptions) balancer.Balancer {
  301. return &testConstBalancer{cc: cc}
  302. }
  303. func (*testConstBalancerBuilder) Name() string {
  304. return "test-const-balancer"
  305. }
  306. type testConstBalancer struct {
  307. cc balancer.ClientConn
  308. }
  309. func (tb *testConstBalancer) HandleSubConnStateChange(sc balancer.SubConn, state connectivity.State) {
  310. tb.cc.UpdateBalancerState(connectivity.Ready, &testConstPicker{err: errTestConstPicker})
  311. }
  312. func (tb *testConstBalancer) HandleResolvedAddrs([]resolver.Address, error) {
  313. tb.cc.UpdateBalancerState(connectivity.Ready, &testConstPicker{err: errTestConstPicker})
  314. }
  315. func (*testConstBalancer) Close() {
  316. }
  317. type testConstPicker struct {
  318. err error
  319. sc balancer.SubConn
  320. }
  321. func (tcp *testConstPicker) Pick(ctx context.Context, opts balancer.PickOptions) (conn balancer.SubConn, done func(balancer.DoneInfo), err error) {
  322. if tcp.err != nil {
  323. return nil, nil, tcp.err
  324. }
  325. return tcp.sc, nil, nil
  326. }
  327. // Create XDS balancer, and update sub-balancer before handling eds responses.
  328. // Then switch between round-robin and test-const-balancer after handling first
  329. // eds response.
  330. func TestEDS_UpdateSubBalancerName(t *testing.T) {
  331. cc := newTestClientConn(t)
  332. edsb := NewXDSBalancer(cc)
  333. t.Logf("update sub-balancer to test-const-balancer")
  334. edsb.HandleChildPolicy("test-const-balancer", nil)
  335. // Two localities, each with one backend.
  336. clab1 := newClusterLoadAssignmentBuilder(testClusterNames[0], nil)
  337. clab1.addLocality(testSubZones[0], 1, testEndpointAddrs[:1])
  338. clab1.addLocality(testSubZones[1], 1, testEndpointAddrs[1:2])
  339. edsb.HandleEDSResponse(clab1.build())
  340. p0 := <-cc.newPickerCh
  341. for i := 0; i < 5; i++ {
  342. _, _, err := p0.Pick(context.Background(), balancer.PickOptions{})
  343. if !reflect.DeepEqual(err, errTestConstPicker) {
  344. t.Fatalf("picker.Pick, got err %q, want err %q", err, errTestConstPicker)
  345. }
  346. }
  347. t.Logf("update sub-balancer to round-robin")
  348. edsb.HandleChildPolicy(roundrobin.Name, nil)
  349. sc1 := <-cc.newSubConnCh
  350. edsb.HandleSubConnStateChange(sc1, connectivity.Connecting)
  351. edsb.HandleSubConnStateChange(sc1, connectivity.Ready)
  352. sc2 := <-cc.newSubConnCh
  353. edsb.HandleSubConnStateChange(sc2, connectivity.Connecting)
  354. edsb.HandleSubConnStateChange(sc2, connectivity.Ready)
  355. // Test roundrobin with two subconns.
  356. p1 := <-cc.newPickerCh
  357. want := []balancer.SubConn{sc1, sc2}
  358. if err := isRoundRobin(want, func() balancer.SubConn {
  359. sc, _, _ := p1.Pick(context.Background(), balancer.PickOptions{})
  360. return sc
  361. }); err != nil {
  362. t.Fatalf("want %v, got %v", want, err)
  363. }
  364. t.Logf("update sub-balancer to test-const-balancer")
  365. edsb.HandleChildPolicy("test-const-balancer", nil)
  366. for i := 0; i < 2; i++ {
  367. scToRemove := <-cc.removeSubConnCh
  368. if !reflect.DeepEqual(scToRemove, sc1) && !reflect.DeepEqual(scToRemove, sc2) {
  369. t.Fatalf("RemoveSubConn, want (%v or %v), got %v", sc1, sc2, scToRemove)
  370. }
  371. edsb.HandleSubConnStateChange(scToRemove, connectivity.Shutdown)
  372. }
  373. p2 := <-cc.newPickerCh
  374. for i := 0; i < 5; i++ {
  375. _, _, err := p2.Pick(context.Background(), balancer.PickOptions{})
  376. if !reflect.DeepEqual(err, errTestConstPicker) {
  377. t.Fatalf("picker.Pick, got err %q, want err %q", err, errTestConstPicker)
  378. }
  379. }
  380. t.Logf("update sub-balancer to round-robin")
  381. edsb.HandleChildPolicy(roundrobin.Name, nil)
  382. sc3 := <-cc.newSubConnCh
  383. edsb.HandleSubConnStateChange(sc3, connectivity.Connecting)
  384. edsb.HandleSubConnStateChange(sc3, connectivity.Ready)
  385. sc4 := <-cc.newSubConnCh
  386. edsb.HandleSubConnStateChange(sc4, connectivity.Connecting)
  387. edsb.HandleSubConnStateChange(sc4, connectivity.Ready)
  388. p3 := <-cc.newPickerCh
  389. want = []balancer.SubConn{sc3, sc4}
  390. if err := isRoundRobin(want, func() balancer.SubConn {
  391. sc, _, _ := p3.Pick(context.Background(), balancer.PickOptions{})
  392. return sc
  393. }); err != nil {
  394. t.Fatalf("want %v, got %v", want, err)
  395. }
  396. }
  397. func TestDropPicker(t *testing.T) {
  398. const pickCount = 12
  399. var constPicker = &testConstPicker{
  400. sc: testSubConns[0],
  401. }
  402. tests := []struct {
  403. name string
  404. drops []*dropper
  405. }{
  406. {
  407. name: "no drop",
  408. drops: nil,
  409. },
  410. {
  411. name: "one drop",
  412. drops: []*dropper{
  413. newDropper(1, 2),
  414. },
  415. },
  416. {
  417. name: "two drops",
  418. drops: []*dropper{
  419. newDropper(1, 3),
  420. newDropper(1, 2),
  421. },
  422. },
  423. {
  424. name: "three drops",
  425. drops: []*dropper{
  426. newDropper(1, 3),
  427. newDropper(1, 4),
  428. newDropper(1, 2),
  429. },
  430. },
  431. }
  432. for _, tt := range tests {
  433. t.Run(tt.name, func(t *testing.T) {
  434. p := newDropPicker(constPicker, tt.drops)
  435. // scCount is the number of sc's returned by pick. The opposite of
  436. // drop-count.
  437. var (
  438. scCount int
  439. wantCount = pickCount
  440. )
  441. for _, dp := range tt.drops {
  442. wantCount = wantCount * int(dp.denominator-dp.numerator) / int(dp.denominator)
  443. }
  444. for i := 0; i < pickCount; i++ {
  445. _, _, err := p.Pick(context.Background(), balancer.PickOptions{})
  446. if err == nil {
  447. scCount++
  448. }
  449. }
  450. if scCount != (wantCount) {
  451. t.Errorf("drops: %+v, scCount %v, wantCount %v", tt.drops, scCount, wantCount)
  452. }
  453. })
  454. }
  455. }