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  }