|
- /*
- * Copyright 2019 gRPC authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
- package edsbalancer
-
- import (
- "fmt"
- "testing"
-
- "google.golang.org/grpc/balancer"
- "google.golang.org/grpc/connectivity"
- "google.golang.org/grpc/resolver"
- )
-
- var (
- testSubConns = []*testSubConn{{id: "sc1"}, {id: "sc2"}, {id: "sc3"}, {id: "sc4"}}
- )
-
- type testSubConn struct {
- id string
- }
-
- func (tsc *testSubConn) UpdateAddresses([]resolver.Address) {
- panic("not implemented")
- }
-
- func (tsc *testSubConn) Connect() {
- }
-
- // Implement stringer to get human friendly error message.
- func (tsc *testSubConn) String() string {
- return tsc.id
- }
-
- type testClientConn struct {
- t *testing.T // For logging only.
-
- newSubConnAddrsCh chan []resolver.Address // The last 10 []Address to create subconn.
- newSubConnCh chan balancer.SubConn // The last 10 subconn created.
- removeSubConnCh chan balancer.SubConn // The last 10 subconn removed.
-
- newPickerCh chan balancer.Picker // The last picker updated.
- newStateCh chan connectivity.State // The last state.
-
- subConnIdx int
- }
-
- func newTestClientConn(t *testing.T) *testClientConn {
- return &testClientConn{
- t: t,
-
- newSubConnAddrsCh: make(chan []resolver.Address, 10),
- newSubConnCh: make(chan balancer.SubConn, 10),
- removeSubConnCh: make(chan balancer.SubConn, 10),
-
- newPickerCh: make(chan balancer.Picker, 1),
- newStateCh: make(chan connectivity.State, 1),
- }
- }
-
- func (tcc *testClientConn) NewSubConn(a []resolver.Address, o balancer.NewSubConnOptions) (balancer.SubConn, error) {
- sc := testSubConns[tcc.subConnIdx]
- tcc.subConnIdx++
-
- tcc.t.Logf("testClientConn: NewSubConn(%v, %+v) => %p", a, o, sc)
- select {
- case tcc.newSubConnAddrsCh <- a:
- default:
- }
-
- select {
- case tcc.newSubConnCh <- sc:
- default:
- }
-
- return sc, nil
- }
-
- func (tcc *testClientConn) RemoveSubConn(sc balancer.SubConn) {
- tcc.t.Logf("testClientCOnn: RemoveSubConn(%p)", sc)
- select {
- case tcc.removeSubConnCh <- sc:
- default:
- }
- }
-
- func (tcc *testClientConn) UpdateBalancerState(s connectivity.State, p balancer.Picker) {
- tcc.t.Logf("testClientConn: UpdateBalancerState(%v, %p)", s, p)
- select {
- case <-tcc.newStateCh:
- default:
- }
- tcc.newStateCh <- s
-
- select {
- case <-tcc.newPickerCh:
- default:
- }
- tcc.newPickerCh <- p
- }
-
- func (tcc *testClientConn) ResolveNow(resolver.ResolveNowOption) {
- panic("not implemented")
- }
-
- func (tcc *testClientConn) Target() string {
- panic("not implemented")
- }
-
- // isRoundRobin checks whether f's return value is roundrobin of elements from
- // want. But it doesn't check for the order. Note that want can contain
- // duplicate items, which makes it weight-round-robin.
- //
- // Step 1. the return values of f should form a permutation of all elements in
- // want, but not necessary in the same order. E.g. if want is {a,a,b}, the check
- // fails if f returns:
- // - {a,a,a}: third a is returned before b
- // - {a,b,b}: second b is returned before the second a
- //
- // If error is found in this step, the returned error contains only the first
- // iteration until where it goes wrong.
- //
- // Step 2. the return values of f should be repetitions of the same permutation.
- // E.g. if want is {a,a,b}, the check failes if f returns:
- // - {a,b,a,b,a,a}: though it satisfies step 1, the second iteration is not
- // repeating the first iteration.
- //
- // If error is found in this step, the returned error contains the first
- // iteration + the second iteration until where it goes wrong.
- func isRoundRobin(want []balancer.SubConn, f func() balancer.SubConn) error {
- wantSet := make(map[balancer.SubConn]int) // SubConn -> count, for weighted RR.
- for _, sc := range want {
- wantSet[sc]++
- }
-
- // The first iteration: makes sure f's return values form a permutation of
- // elements in want.
- //
- // Also keep the returns values in a slice, so we can compare the order in
- // the second iteration.
- gotSliceFirstIteration := make([]balancer.SubConn, 0, len(want))
- for range want {
- got := f()
- gotSliceFirstIteration = append(gotSliceFirstIteration, got)
- wantSet[got]--
- if wantSet[got] < 0 {
- return fmt.Errorf("non-roundrobin want: %v, result: %v", want, gotSliceFirstIteration)
- }
- }
-
- // The second iteration should repeat the first iteration.
- var gotSliceSecondIteration []balancer.SubConn
- for i := 0; i < 2; i++ {
- for _, w := range gotSliceFirstIteration {
- g := f()
- gotSliceSecondIteration = append(gotSliceSecondIteration, g)
- if w != g {
- return fmt.Errorf("non-roundrobin, first iter: %v, second iter: %v", gotSliceFirstIteration, gotSliceSecondIteration)
- }
- }
- }
-
- return nil
- }
-
- // testClosure is a test util for TestIsRoundRobin.
- type testClosure struct {
- r []balancer.SubConn
- i int
- }
-
- func (tc *testClosure) next() balancer.SubConn {
- ret := tc.r[tc.i]
- tc.i = (tc.i + 1) % len(tc.r)
- return ret
- }
-
- func TestIsRoundRobin(t *testing.T) {
- var (
- sc1 = testSubConns[0]
- sc2 = testSubConns[1]
- sc3 = testSubConns[2]
- )
-
- testCases := []struct {
- desc string
- want []balancer.SubConn
- got []balancer.SubConn
- pass bool
- }{
- {
- desc: "0 element",
- want: []balancer.SubConn{},
- got: []balancer.SubConn{},
- pass: true,
- },
- {
- desc: "1 element RR",
- want: []balancer.SubConn{sc1},
- got: []balancer.SubConn{sc1, sc1, sc1, sc1},
- pass: true,
- },
- {
- desc: "1 element not RR",
- want: []balancer.SubConn{sc1},
- got: []balancer.SubConn{sc1, sc2, sc1},
- pass: false,
- },
- {
- desc: "2 elements RR",
- want: []balancer.SubConn{sc1, sc2},
- got: []balancer.SubConn{sc1, sc2, sc1, sc2, sc1, sc2},
- pass: true,
- },
- {
- desc: "2 elements RR different order from want",
- want: []balancer.SubConn{sc2, sc1},
- got: []balancer.SubConn{sc1, sc2, sc1, sc2, sc1, sc2},
- pass: true,
- },
- {
- desc: "2 elements RR not RR, mistake in first iter",
- want: []balancer.SubConn{sc1, sc2},
- got: []balancer.SubConn{sc1, sc1, sc1, sc2, sc1, sc2},
- pass: false,
- },
- {
- desc: "2 elements RR not RR, mistake in second iter",
- want: []balancer.SubConn{sc1, sc2},
- got: []balancer.SubConn{sc1, sc2, sc1, sc1, sc1, sc2},
- pass: false,
- },
- {
- desc: "2 elements weighted RR",
- want: []balancer.SubConn{sc1, sc1, sc2},
- got: []balancer.SubConn{sc1, sc1, sc2, sc1, sc1, sc2},
- pass: true,
- },
- {
- desc: "2 elements weighted RR different order",
- want: []balancer.SubConn{sc1, sc1, sc2},
- got: []balancer.SubConn{sc1, sc2, sc1, sc1, sc2, sc1},
- pass: true,
- },
-
- {
- desc: "3 elements RR",
- want: []balancer.SubConn{sc1, sc2, sc3},
- got: []balancer.SubConn{sc1, sc2, sc3, sc1, sc2, sc3, sc1, sc2, sc3},
- pass: true,
- },
- {
- desc: "3 elements RR different order",
- want: []balancer.SubConn{sc1, sc2, sc3},
- got: []balancer.SubConn{sc3, sc2, sc1, sc3, sc2, sc1},
- pass: true,
- },
- {
- desc: "3 elements weighted RR",
- want: []balancer.SubConn{sc1, sc1, sc1, sc2, sc2, sc3},
- got: []balancer.SubConn{sc1, sc2, sc3, sc1, sc2, sc1, sc1, sc2, sc3, sc1, sc2, sc1},
- pass: true,
- },
- {
- desc: "3 elements weighted RR not RR, mistake in first iter",
- want: []balancer.SubConn{sc1, sc1, sc1, sc2, sc2, sc3},
- got: []balancer.SubConn{sc1, sc2, sc1, sc1, sc2, sc1, sc1, sc2, sc3, sc1, sc2, sc1},
- pass: false,
- },
- {
- desc: "3 elements weighted RR not RR, mistake in second iter",
- want: []balancer.SubConn{sc1, sc1, sc1, sc2, sc2, sc3},
- got: []balancer.SubConn{sc1, sc2, sc3, sc1, sc2, sc1, sc1, sc1, sc3, sc1, sc2, sc1},
- pass: false,
- },
- }
- for _, tC := range testCases {
- t.Run(tC.desc, func(t *testing.T) {
- err := isRoundRobin(tC.want, (&testClosure{r: tC.got}).next)
- if err == nil != tC.pass {
- t.Errorf("want pass %v, want %v, got err %v", tC.pass, tC.want, err)
- }
- })
- }
- }
|