github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/grpc/internal/testutils/balancer.go (about) 1 /* 2 * 3 * Copyright 2020 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 testutils 20 21 import ( 22 "context" 23 "errors" 24 "fmt" 25 "testing" 26 27 "github.com/hxx258456/ccgo/grpc/balancer" 28 "github.com/hxx258456/ccgo/grpc/connectivity" 29 "github.com/hxx258456/ccgo/grpc/resolver" 30 ) 31 32 // TestSubConnsCount is the number of TestSubConns initialized as part of 33 // package init. 34 const TestSubConnsCount = 16 35 36 // testingLogger wraps the logging methods from testing.T. 37 type testingLogger interface { 38 Log(args ...interface{}) 39 Logf(format string, args ...interface{}) 40 } 41 42 // TestSubConns contains a list of SubConns to be used in tests. 43 var TestSubConns []*TestSubConn 44 45 func init() { 46 for i := 0; i < TestSubConnsCount; i++ { 47 TestSubConns = append(TestSubConns, &TestSubConn{ 48 id: fmt.Sprintf("sc%d", i), 49 ConnectCh: make(chan struct{}, 1), 50 }) 51 } 52 } 53 54 // TestSubConn implements the SubConn interface, to be used in tests. 55 type TestSubConn struct { 56 id string 57 ConnectCh chan struct{} 58 } 59 60 // UpdateAddresses is a no-op. 61 func (tsc *TestSubConn) UpdateAddresses([]resolver.Address) {} 62 63 // Connect is a no-op. 64 func (tsc *TestSubConn) Connect() { 65 select { 66 case tsc.ConnectCh <- struct{}{}: 67 default: 68 } 69 } 70 71 // String implements stringer to print human friendly error message. 72 func (tsc *TestSubConn) String() string { 73 return tsc.id 74 } 75 76 // TestClientConn is a mock balancer.ClientConn used in tests. 77 type TestClientConn struct { 78 logger testingLogger 79 80 NewSubConnAddrsCh chan []resolver.Address // the last 10 []Address to create subconn. 81 NewSubConnCh chan balancer.SubConn // the last 10 subconn created. 82 RemoveSubConnCh chan balancer.SubConn // the last 10 subconn removed. 83 UpdateAddressesAddrsCh chan []resolver.Address // last updated address via UpdateAddresses(). 84 85 NewPickerCh chan balancer.Picker // the last picker updated. 86 NewStateCh chan connectivity.State // the last state. 87 ResolveNowCh chan resolver.ResolveNowOptions // the last ResolveNow(). 88 89 subConnIdx int 90 } 91 92 // NewTestClientConn creates a TestClientConn. 93 func NewTestClientConn(t *testing.T) *TestClientConn { 94 return &TestClientConn{ 95 logger: t, 96 97 NewSubConnAddrsCh: make(chan []resolver.Address, 10), 98 NewSubConnCh: make(chan balancer.SubConn, 10), 99 RemoveSubConnCh: make(chan balancer.SubConn, 10), 100 UpdateAddressesAddrsCh: make(chan []resolver.Address, 1), 101 102 NewPickerCh: make(chan balancer.Picker, 1), 103 NewStateCh: make(chan connectivity.State, 1), 104 ResolveNowCh: make(chan resolver.ResolveNowOptions, 1), 105 } 106 } 107 108 // NewSubConn creates a new SubConn. 109 func (tcc *TestClientConn) NewSubConn(a []resolver.Address, o balancer.NewSubConnOptions) (balancer.SubConn, error) { 110 sc := TestSubConns[tcc.subConnIdx] 111 tcc.subConnIdx++ 112 113 tcc.logger.Logf("testClientConn: NewSubConn(%v, %+v) => %s", a, o, sc) 114 select { 115 case tcc.NewSubConnAddrsCh <- a: 116 default: 117 } 118 119 select { 120 case tcc.NewSubConnCh <- sc: 121 default: 122 } 123 124 return sc, nil 125 } 126 127 // RemoveSubConn removes the SubConn. 128 func (tcc *TestClientConn) RemoveSubConn(sc balancer.SubConn) { 129 tcc.logger.Logf("testClientConn: RemoveSubConn(%s)", sc) 130 select { 131 case tcc.RemoveSubConnCh <- sc: 132 default: 133 } 134 } 135 136 // UpdateAddresses updates the addresses on the SubConn. 137 func (tcc *TestClientConn) UpdateAddresses(sc balancer.SubConn, addrs []resolver.Address) { 138 tcc.logger.Logf("testClientConn: UpdateAddresses(%v, %+v)", sc, addrs) 139 select { 140 case tcc.UpdateAddressesAddrsCh <- addrs: 141 default: 142 } 143 } 144 145 // UpdateState updates connectivity state and picker. 146 func (tcc *TestClientConn) UpdateState(bs balancer.State) { 147 tcc.logger.Logf("testClientConn: UpdateState(%v)", bs) 148 select { 149 case <-tcc.NewStateCh: 150 default: 151 } 152 tcc.NewStateCh <- bs.ConnectivityState 153 154 select { 155 case <-tcc.NewPickerCh: 156 default: 157 } 158 tcc.NewPickerCh <- bs.Picker 159 } 160 161 // ResolveNow panics. 162 func (tcc *TestClientConn) ResolveNow(o resolver.ResolveNowOptions) { 163 select { 164 case <-tcc.ResolveNowCh: 165 default: 166 } 167 tcc.ResolveNowCh <- o 168 } 169 170 // Target panics. 171 func (tcc *TestClientConn) Target() string { 172 panic("not implemented") 173 } 174 175 // WaitForErrPicker waits until an error picker is pushed to this ClientConn. 176 // Returns error if the provided context expires or a non-error picker is pushed 177 // to the ClientConn. 178 func (tcc *TestClientConn) WaitForErrPicker(ctx context.Context) error { 179 select { 180 case <-ctx.Done(): 181 return errors.New("timeout when waiting for an error picker") 182 case picker := <-tcc.NewPickerCh: 183 if _, perr := picker.Pick(balancer.PickInfo{}); perr == nil { 184 return fmt.Errorf("balancer returned a picker which is not an error picker") 185 } 186 } 187 return nil 188 } 189 190 // IsRoundRobin checks whether f's return value is roundrobin of elements from 191 // want. But it doesn't check for the order. Note that want can contain 192 // duplicate items, which makes it weight-round-robin. 193 // 194 // Step 1. the return values of f should form a permutation of all elements in 195 // want, but not necessary in the same order. E.g. if want is {a,a,b}, the check 196 // fails if f returns: 197 // - {a,a,a}: third a is returned before b 198 // - {a,b,b}: second b is returned before the second a 199 // 200 // If error is found in this step, the returned error contains only the first 201 // iteration until where it goes wrong. 202 // 203 // Step 2. the return values of f should be repetitions of the same permutation. 204 // E.g. if want is {a,a,b}, the check failes if f returns: 205 // - {a,b,a,b,a,a}: though it satisfies step 1, the second iteration is not 206 // repeating the first iteration. 207 // 208 // If error is found in this step, the returned error contains the first 209 // iteration + the second iteration until where it goes wrong. 210 func IsRoundRobin(want []balancer.SubConn, f func() balancer.SubConn) error { 211 wantSet := make(map[balancer.SubConn]int) // SubConn -> count, for weighted RR. 212 for _, sc := range want { 213 wantSet[sc]++ 214 } 215 216 // The first iteration: makes sure f's return values form a permutation of 217 // elements in want. 218 // 219 // Also keep the returns values in a slice, so we can compare the order in 220 // the second iteration. 221 gotSliceFirstIteration := make([]balancer.SubConn, 0, len(want)) 222 for range want { 223 got := f() 224 gotSliceFirstIteration = append(gotSliceFirstIteration, got) 225 wantSet[got]-- 226 if wantSet[got] < 0 { 227 return fmt.Errorf("non-roundrobin want: %v, result: %v", want, gotSliceFirstIteration) 228 } 229 } 230 231 // The second iteration should repeat the first iteration. 232 var gotSliceSecondIteration []balancer.SubConn 233 for i := 0; i < 2; i++ { 234 for _, w := range gotSliceFirstIteration { 235 g := f() 236 gotSliceSecondIteration = append(gotSliceSecondIteration, g) 237 if w != g { 238 return fmt.Errorf("non-roundrobin, first iter: %v, second iter: %v", gotSliceFirstIteration, gotSliceSecondIteration) 239 } 240 } 241 } 242 243 return nil 244 } 245 246 // ErrTestConstPicker is error returned by test const picker. 247 var ErrTestConstPicker = fmt.Errorf("const picker error") 248 249 // TestConstPicker is a const picker for tests. 250 type TestConstPicker struct { 251 Err error 252 SC balancer.SubConn 253 } 254 255 // Pick returns the const SubConn or the error. 256 func (tcp *TestConstPicker) Pick(info balancer.PickInfo) (balancer.PickResult, error) { 257 if tcp.Err != nil { 258 return balancer.PickResult{}, tcp.Err 259 } 260 return balancer.PickResult{SubConn: tcp.SC}, nil 261 }