google.golang.org/grpc@v1.72.2/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 "google.golang.org/grpc/balancer" 28 "google.golang.org/grpc/connectivity" 29 "google.golang.org/grpc/experimental/stats" 30 "google.golang.org/grpc/internal" 31 "google.golang.org/grpc/internal/grpcsync" 32 "google.golang.org/grpc/resolver" 33 34 istats "google.golang.org/grpc/internal/stats" 35 ) 36 37 // TestSubConn implements the SubConn interface, to be used in tests. 38 type TestSubConn struct { 39 balancer.SubConn 40 tcc *BalancerClientConn // the CC that owns this SubConn 41 id string 42 ConnectCh chan struct{} 43 stateListener func(balancer.SubConnState) 44 healthListener func(balancer.SubConnState) 45 connectCalled *grpcsync.Event 46 Addresses []resolver.Address 47 } 48 49 // NewTestSubConn returns a newly initialized SubConn. Typically, subconns 50 // should be created via TestClientConn.NewSubConn instead, but can be useful 51 // for some tests. 52 func NewTestSubConn(id string) *TestSubConn { 53 return &TestSubConn{ 54 ConnectCh: make(chan struct{}, 1), 55 connectCalled: grpcsync.NewEvent(), 56 id: id, 57 } 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 tsc.connectCalled.Fire() 66 select { 67 case tsc.ConnectCh <- struct{}{}: 68 default: 69 } 70 } 71 72 // GetOrBuildProducer is a no-op. 73 func (tsc *TestSubConn) GetOrBuildProducer(balancer.ProducerBuilder) (balancer.Producer, func()) { 74 return nil, nil 75 } 76 77 // UpdateState pushes the state to the listener, if one is registered. 78 func (tsc *TestSubConn) UpdateState(state balancer.SubConnState) { 79 <-tsc.connectCalled.Done() 80 if tsc.stateListener != nil { 81 tsc.stateListener(state) 82 } 83 // pickfirst registers a health listener synchronously while handing updates 84 // to READY. It updates the balancing state only after receiving the health 85 // update. We update the health state here so callers of tsc.UpdateState 86 // can verify picker updates as soon as UpdateState returns. 87 if state.ConnectivityState == connectivity.Ready && tsc.healthListener != nil { 88 tsc.healthListener(balancer.SubConnState{ConnectivityState: connectivity.Ready}) 89 } 90 } 91 92 // Shutdown pushes the SubConn to the ShutdownSubConn channel in the parent 93 // TestClientConn. 94 func (tsc *TestSubConn) Shutdown() { 95 tsc.tcc.logger.Logf("SubConn %s: Shutdown", tsc) 96 select { 97 case tsc.tcc.ShutdownSubConnCh <- tsc: 98 default: 99 } 100 } 101 102 // String implements stringer to print human friendly error message. 103 func (tsc *TestSubConn) String() string { 104 return tsc.id 105 } 106 107 // RegisterHealthListener sends a READY update to mock a situation when no 108 // health checking mechanisms are configured. 109 func (tsc *TestSubConn) RegisterHealthListener(lis func(balancer.SubConnState)) { 110 tsc.healthListener = lis 111 } 112 113 // BalancerClientConn is a mock balancer.ClientConn used in tests. 114 type BalancerClientConn struct { 115 internal.EnforceClientConnEmbedding 116 logger Logger 117 118 NewSubConnAddrsCh chan []resolver.Address // the last 10 []Address to create subconn. 119 NewSubConnCh chan *TestSubConn // the last 10 subconn created. 120 ShutdownSubConnCh chan *TestSubConn // the last 10 subconn removed. 121 UpdateAddressesAddrsCh chan []resolver.Address // last updated address via UpdateAddresses(). 122 123 NewPickerCh chan balancer.Picker // the last picker updated. 124 NewStateCh chan connectivity.State // the last state. 125 ResolveNowCh chan resolver.ResolveNowOptions // the last ResolveNow(). 126 127 subConnIdx int 128 } 129 130 // NewBalancerClientConn creates a BalancerClientConn. 131 func NewBalancerClientConn(t *testing.T) *BalancerClientConn { 132 return &BalancerClientConn{ 133 logger: t, 134 135 NewSubConnAddrsCh: make(chan []resolver.Address, 10), 136 NewSubConnCh: make(chan *TestSubConn, 10), 137 ShutdownSubConnCh: make(chan *TestSubConn, 10), 138 UpdateAddressesAddrsCh: make(chan []resolver.Address, 1), 139 140 NewPickerCh: make(chan balancer.Picker, 1), 141 NewStateCh: make(chan connectivity.State, 1), 142 ResolveNowCh: make(chan resolver.ResolveNowOptions, 1), 143 } 144 } 145 146 // NewSubConn creates a new SubConn. 147 func (tcc *BalancerClientConn) NewSubConn(a []resolver.Address, o balancer.NewSubConnOptions) (balancer.SubConn, error) { 148 sc := &TestSubConn{ 149 tcc: tcc, 150 id: fmt.Sprintf("sc%d", tcc.subConnIdx), 151 ConnectCh: make(chan struct{}, 1), 152 stateListener: o.StateListener, 153 connectCalled: grpcsync.NewEvent(), 154 Addresses: a, 155 } 156 tcc.subConnIdx++ 157 tcc.logger.Logf("testClientConn: NewSubConn(%v, %+v) => %s", a, o, sc) 158 select { 159 case tcc.NewSubConnAddrsCh <- a: 160 default: 161 } 162 163 select { 164 case tcc.NewSubConnCh <- sc: 165 default: 166 } 167 168 return sc, nil 169 } 170 171 // MetricsRecorder returns an empty MetricsRecorderList. 172 func (*BalancerClientConn) MetricsRecorder() stats.MetricsRecorder { 173 return istats.NewMetricsRecorderList(nil) 174 } 175 176 // RemoveSubConn is a nop; tests should all be updated to use sc.Shutdown() 177 // instead. 178 func (tcc *BalancerClientConn) RemoveSubConn(sc balancer.SubConn) { 179 tcc.logger.Errorf("RemoveSubConn(%v) called unexpectedly", sc) 180 } 181 182 // UpdateAddresses updates the addresses on the SubConn. 183 func (tcc *BalancerClientConn) UpdateAddresses(sc balancer.SubConn, addrs []resolver.Address) { 184 tcc.logger.Logf("testutils.BalancerClientConn: UpdateAddresses(%v, %+v)", sc, addrs) 185 select { 186 case tcc.UpdateAddressesAddrsCh <- addrs: 187 default: 188 } 189 } 190 191 // UpdateState updates connectivity state and picker. 192 func (tcc *BalancerClientConn) UpdateState(bs balancer.State) { 193 tcc.logger.Logf("testutils.BalancerClientConn: UpdateState(%v)", bs) 194 select { 195 case <-tcc.NewStateCh: 196 default: 197 } 198 tcc.NewStateCh <- bs.ConnectivityState 199 200 select { 201 case <-tcc.NewPickerCh: 202 default: 203 } 204 tcc.NewPickerCh <- bs.Picker 205 } 206 207 // ResolveNow panics. 208 func (tcc *BalancerClientConn) ResolveNow(o resolver.ResolveNowOptions) { 209 select { 210 case <-tcc.ResolveNowCh: 211 default: 212 } 213 tcc.ResolveNowCh <- o 214 } 215 216 // Target panics. 217 func (tcc *BalancerClientConn) Target() string { 218 panic("not implemented") 219 } 220 221 // WaitForErrPicker waits until an error picker is pushed to this ClientConn. 222 // Returns error if the provided context expires or a non-error picker is pushed 223 // to the ClientConn. 224 func (tcc *BalancerClientConn) WaitForErrPicker(ctx context.Context) error { 225 select { 226 case <-ctx.Done(): 227 return errors.New("timeout when waiting for an error picker") 228 case picker := <-tcc.NewPickerCh: 229 if _, perr := picker.Pick(balancer.PickInfo{}); perr == nil { 230 return fmt.Errorf("balancer returned a picker which is not an error picker") 231 } 232 } 233 return nil 234 } 235 236 // WaitForPickerWithErr waits until an error picker is pushed to this 237 // ClientConn with the error matching the wanted error. Returns an error if 238 // the provided context expires, including the last received picker error (if 239 // any). 240 func (tcc *BalancerClientConn) WaitForPickerWithErr(ctx context.Context, want error) error { 241 lastErr := errors.New("received no picker") 242 for { 243 select { 244 case <-ctx.Done(): 245 return fmt.Errorf("timeout when waiting for an error picker with %v; last picker error: %v", want, lastErr) 246 case picker := <-tcc.NewPickerCh: 247 if _, lastErr = picker.Pick(balancer.PickInfo{}); lastErr != nil && lastErr.Error() == want.Error() { 248 return nil 249 } 250 } 251 } 252 } 253 254 // WaitForConnectivityState waits until the state pushed to this ClientConn 255 // matches the wanted state. Returns an error if the provided context expires, 256 // including the last received state (if any). 257 func (tcc *BalancerClientConn) WaitForConnectivityState(ctx context.Context, want connectivity.State) error { 258 var lastState connectivity.State = -1 259 for { 260 select { 261 case <-ctx.Done(): 262 return fmt.Errorf("timeout when waiting for state to be %s; last state: %s", want, lastState) 263 case s := <-tcc.NewStateCh: 264 if s == want { 265 return nil 266 } 267 lastState = s 268 } 269 } 270 } 271 272 // WaitForRoundRobinPicker waits for a picker that passes IsRoundRobin. Also 273 // drains the matching state channel and requires it to be READY (if an entry 274 // is pending) to be considered. Returns an error if the provided context 275 // expires, including the last received error from IsRoundRobin or the picker 276 // (if any). 277 func (tcc *BalancerClientConn) WaitForRoundRobinPicker(ctx context.Context, want ...balancer.SubConn) error { 278 lastErr := errors.New("received no picker") 279 for { 280 select { 281 case <-ctx.Done(): 282 return fmt.Errorf("timeout when waiting for round robin picker with %v; last error: %v", want, lastErr) 283 case p := <-tcc.NewPickerCh: 284 s := connectivity.Ready 285 select { 286 case s = <-tcc.NewStateCh: 287 default: 288 } 289 if s != connectivity.Ready { 290 lastErr = fmt.Errorf("received state %v instead of ready", s) 291 break 292 } 293 var pickerErr error 294 if err := IsRoundRobin(want, func() balancer.SubConn { 295 sc, err := p.Pick(balancer.PickInfo{}) 296 if err != nil { 297 pickerErr = err 298 } else if sc.Done != nil { 299 sc.Done(balancer.DoneInfo{}) 300 } 301 return sc.SubConn 302 }); pickerErr != nil { 303 lastErr = pickerErr 304 continue 305 } else if err != nil { 306 lastErr = err 307 continue 308 } 309 return nil 310 } 311 } 312 } 313 314 // WaitForPicker waits for a picker that results in f returning nil. If the 315 // context expires, returns the last error returned by f (if any). 316 func (tcc *BalancerClientConn) WaitForPicker(ctx context.Context, f func(balancer.Picker) error) error { 317 lastErr := errors.New("received no picker") 318 for { 319 select { 320 case <-ctx.Done(): 321 return fmt.Errorf("timeout when waiting for picker; last error: %v", lastErr) 322 case p := <-tcc.NewPickerCh: 323 if err := f(p); err != nil { 324 lastErr = err 325 continue 326 } 327 return nil 328 } 329 } 330 } 331 332 // IsRoundRobin checks whether f's return value is roundrobin of elements from 333 // want. But it doesn't check for the order. Note that want can contain 334 // duplicate items, which makes it weight-round-robin. 335 // 336 // Step 1. the return values of f should form a permutation of all elements in 337 // want, but not necessary in the same order. E.g. if want is {a,a,b}, the check 338 // fails if f returns: 339 // - {a,a,a}: third a is returned before b 340 // - {a,b,b}: second b is returned before the second a 341 // 342 // If error is found in this step, the returned error contains only the first 343 // iteration until where it goes wrong. 344 // 345 // Step 2. the return values of f should be repetitions of the same permutation. 346 // E.g. if want is {a,a,b}, the check fails if f returns: 347 // - {a,b,a,b,a,a}: though it satisfies step 1, the second iteration is not 348 // repeating the first iteration. 349 // 350 // If error is found in this step, the returned error contains the first 351 // iteration + the second iteration until where it goes wrong. 352 func IsRoundRobin(want []balancer.SubConn, f func() balancer.SubConn) error { 353 wantSet := make(map[balancer.SubConn]int) // SubConn -> count, for weighted RR. 354 for _, sc := range want { 355 wantSet[sc]++ 356 } 357 358 // The first iteration: makes sure f's return values form a permutation of 359 // elements in want. 360 // 361 // Also keep the returns values in a slice, so we can compare the order in 362 // the second iteration. 363 gotSliceFirstIteration := make([]balancer.SubConn, 0, len(want)) 364 for range want { 365 got := f() 366 gotSliceFirstIteration = append(gotSliceFirstIteration, got) 367 wantSet[got]-- 368 if wantSet[got] < 0 { 369 return fmt.Errorf("non-roundrobin want: %v, result: %v", want, gotSliceFirstIteration) 370 } 371 } 372 373 // The second iteration should repeat the first iteration. 374 var gotSliceSecondIteration []balancer.SubConn 375 for i := 0; i < 2; i++ { 376 for _, w := range gotSliceFirstIteration { 377 g := f() 378 gotSliceSecondIteration = append(gotSliceSecondIteration, g) 379 if w != g { 380 return fmt.Errorf("non-roundrobin, first iter: %v, second iter: %v", gotSliceFirstIteration, gotSliceSecondIteration) 381 } 382 } 383 } 384 385 return nil 386 } 387 388 // SubConnFromPicker returns a function which returns a SubConn by calling the 389 // Pick() method of the provided picker. There is no caching of SubConns here. 390 // Every invocation of the returned function results in a new pick. 391 func SubConnFromPicker(p balancer.Picker) func() balancer.SubConn { 392 return func() balancer.SubConn { 393 scst, _ := p.Pick(balancer.PickInfo{}) 394 return scst.SubConn 395 } 396 } 397 398 // ErrTestConstPicker is error returned by test const picker. 399 var ErrTestConstPicker = fmt.Errorf("const picker error") 400 401 // TestConstPicker is a const picker for tests. 402 type TestConstPicker struct { 403 Err error 404 SC balancer.SubConn 405 } 406 407 // Pick returns the const SubConn or the error. 408 func (tcp *TestConstPicker) Pick(balancer.PickInfo) (balancer.PickResult, error) { 409 if tcp.Err != nil { 410 return balancer.PickResult{}, tcp.Err 411 } 412 return balancer.PickResult{SubConn: tcp.SC}, nil 413 }