google.golang.org/grpc@v1.74.2/xds/server_ext_test.go (about)

     1  /*
     2   *
     3   * Copyright 2024 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 xds_test
    20  
    21  import (
    22  	"context"
    23  	"fmt"
    24  	"io"
    25  	"net"
    26  	"strconv"
    27  	"strings"
    28  	"sync"
    29  	"testing"
    30  	"time"
    31  
    32  	"github.com/google/go-cmp/cmp"
    33  	"github.com/google/uuid"
    34  	"google.golang.org/grpc"
    35  	"google.golang.org/grpc/codes"
    36  	"google.golang.org/grpc/connectivity"
    37  	"google.golang.org/grpc/credentials/insecure"
    38  	"google.golang.org/grpc/internal/grpctest"
    39  	"google.golang.org/grpc/internal/stubserver"
    40  	"google.golang.org/grpc/internal/testutils"
    41  	"google.golang.org/grpc/internal/testutils/xds/e2e"
    42  	"google.golang.org/grpc/internal/xds/bootstrap"
    43  	"google.golang.org/grpc/peer"
    44  	"google.golang.org/grpc/status"
    45  	"google.golang.org/grpc/xds"
    46  	"google.golang.org/grpc/xds/internal/xdsclient"
    47  
    48  	v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
    49  	v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
    50  	v3discoverypb "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
    51  	testgrpc "google.golang.org/grpc/interop/grpc_testing"
    52  	testpb "google.golang.org/grpc/interop/grpc_testing"
    53  )
    54  
    55  type s struct {
    56  	grpctest.Tester
    57  }
    58  
    59  func Test(t *testing.T) {
    60  	grpctest.RunSubTests(t, s{})
    61  }
    62  
    63  const (
    64  	defaultTestTimeout      = 10 * time.Second
    65  	defaultTestShortTimeout = 10 * time.Millisecond // For events expected to *not* happen.
    66  )
    67  
    68  func hostPortFromListener(lis net.Listener) (string, uint32, error) {
    69  	host, p, err := net.SplitHostPort(lis.Addr().String())
    70  	if err != nil {
    71  		return "", 0, fmt.Errorf("net.SplitHostPort(%s) failed: %v", lis.Addr().String(), err)
    72  	}
    73  	port, err := strconv.ParseInt(p, 10, 32)
    74  	if err != nil {
    75  		return "", 0, fmt.Errorf("strconv.ParseInt(%s, 10, 32) failed: %v", p, err)
    76  	}
    77  	return host, uint32(port), nil
    78  }
    79  
    80  // servingModeChangeHandler handles changes to the serving mode of an
    81  // xDS-enabled gRPC server. It logs the changes and sends the new mode and any
    82  // errors on appropriate channels for the test to consume.
    83  type servingModeChangeHandler struct {
    84  	logger interface {
    85  		Logf(format string, args ...any)
    86  	}
    87  	// Access to the below fields are guarded by this mutex.
    88  	mu          sync.Mutex
    89  	modeCh      chan connectivity.ServingMode
    90  	errCh       chan error
    91  	currentMode connectivity.ServingMode
    92  	currentErr  error
    93  }
    94  
    95  func newServingModeChangeHandler(t *testing.T) *servingModeChangeHandler {
    96  	return &servingModeChangeHandler{
    97  		logger: t,
    98  		modeCh: make(chan connectivity.ServingMode, 1),
    99  		errCh:  make(chan error, 1),
   100  	}
   101  }
   102  
   103  func (m *servingModeChangeHandler) modeChangeCallback(addr net.Addr, args xds.ServingModeChangeArgs) {
   104  	m.mu.Lock()
   105  	defer m.mu.Unlock()
   106  	// Suppress pushing duplicate mode change and error if the mode is staying
   107  	// in NOT_SERVING and the error is the same.
   108  	//
   109  	// TODO(purnesh42h): Should we move this check to listener wrapper? This
   110  	// shouldn't happen in practice a lot. But we never know what kind of
   111  	// management servers users run.
   112  	if m.currentMode == args.Mode && m.currentMode == connectivity.ServingModeNotServing && m.currentErr.Error() == args.Err.Error() {
   113  		return
   114  	}
   115  	m.logger.Logf("Serving mode for listener %q changed to %q, err: %v", addr.String(), args.Mode, args.Err)
   116  	m.modeCh <- args.Mode
   117  	m.currentMode = args.Mode
   118  	if args.Err != nil {
   119  		m.errCh <- args.Err
   120  	}
   121  	m.currentErr = args.Err
   122  }
   123  
   124  // createStubServer creates a new xDS-enabled gRPC server and returns a
   125  // stubserver.StubServer that can be used for testing. The server is configured
   126  // with the provided modeChangeOpt and xdsclient.Pool.
   127  func createStubServer(t *testing.T, lis net.Listener, opts ...grpc.ServerOption) *stubserver.StubServer {
   128  	stub := &stubserver.StubServer{
   129  		Listener: lis,
   130  		EmptyCallF: func(context.Context, *testpb.Empty) (*testpb.Empty, error) {
   131  			return &testpb.Empty{}, nil
   132  		},
   133  		FullDuplexCallF: func(stream testgrpc.TestService_FullDuplexCallServer) error {
   134  			for {
   135  				if _, err := stream.Recv(); err == io.EOF {
   136  					return nil
   137  				} else if err != nil {
   138  					return err
   139  				}
   140  			}
   141  		},
   142  	}
   143  	server, err := xds.NewGRPCServer(opts...)
   144  	if err != nil {
   145  		t.Fatalf("Failed to create an xDS enabled gRPC server: %v", err)
   146  	}
   147  	stub.S = server
   148  	stubserver.StartTestService(t, stub)
   149  	t.Cleanup(stub.Stop)
   150  	return stub
   151  }
   152  
   153  // waitForSuccessfulRPC waits for an RPC to succeed, repeatedly calling the
   154  // EmptyCall RPC on the provided client connection until the call succeeds.
   155  // If the context is canceled or the expected error is not before the context
   156  // timeout expires, the test will fail.
   157  func waitForSuccessfulRPC(ctx context.Context, t *testing.T, cc *grpc.ClientConn, opts ...grpc.CallOption) {
   158  	t.Helper()
   159  
   160  	client := testgrpc.NewTestServiceClient(cc)
   161  	for {
   162  		select {
   163  		case <-ctx.Done():
   164  			t.Fatalf("Timeout waiting for RPCs to succeed")
   165  		case <-time.After(defaultTestShortTimeout):
   166  			if _, err := client.EmptyCall(ctx, &testpb.Empty{}, opts...); err == nil {
   167  				return
   168  			}
   169  		}
   170  	}
   171  }
   172  
   173  // waitForFailedRPCWithStatus waits for an RPC to fail with the expected status
   174  // code, error message, and node ID.  It repeatedly calls the EmptyCall RPC on
   175  // the provided client connection until the error matches the expected values.
   176  // If the context is canceled or the expected error is not before the context
   177  // timeout expires, the test will fail.
   178  func waitForFailedRPCWithStatus(ctx context.Context, t *testing.T, cc *grpc.ClientConn, wantCode codes.Code, wantErr, wantNodeID string) {
   179  	t.Helper()
   180  
   181  	client := testgrpc.NewTestServiceClient(cc)
   182  	var err error
   183  	for {
   184  		select {
   185  		case <-ctx.Done():
   186  			t.Fatalf("RPCs failed with most recent error: %v. Want status code %v, error: %s, node id: %s", err, wantCode, wantErr, wantNodeID)
   187  		case <-time.After(defaultTestShortTimeout):
   188  			_, err = client.EmptyCall(ctx, &testpb.Empty{})
   189  			if gotCode := status.Code(err); gotCode != wantCode {
   190  				continue
   191  			}
   192  			if gotErr := err.Error(); !strings.Contains(gotErr, wantErr) {
   193  				continue
   194  			}
   195  			if !strings.Contains(err.Error(), wantNodeID) {
   196  				continue
   197  			}
   198  			t.Logf("Most recent error happy case: %v", err.Error())
   199  			return
   200  		}
   201  	}
   202  }
   203  
   204  // Tests the basic scenario for an xDS enabled gRPC server.
   205  //
   206  //   - Verifies that the xDS enabled gRPC server requests for the expected
   207  //     listener resource.
   208  //   - Once the listener resource is received from the management server, it
   209  //     verifies that the xDS enabled gRPC server requests for the appropriate
   210  //     route configuration name. Also verifies that at this point, the server has
   211  //     not yet started serving RPCs
   212  //   - Once the route configuration is received from the management server, it
   213  //     verifies that the server can serve RPCs successfully.
   214  func (s) TestServer_Basic(t *testing.T) {
   215  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   216  	defer cancel()
   217  
   218  	// Start an xDS management server.
   219  	listenerNamesCh := make(chan []string, 1)
   220  	routeNamesCh := make(chan []string, 1)
   221  	managementServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{
   222  		OnStreamRequest: func(_ int64, req *v3discoverypb.DiscoveryRequest) error {
   223  			// The test needs to only verify the names of resources being
   224  			// subscribed to, we can skip ACKs.
   225  			if req.GetVersionInfo() != "" {
   226  				return nil
   227  			}
   228  			switch req.GetTypeUrl() {
   229  			case "type.googleapis.com/envoy.config.listener.v3.Listener":
   230  				select {
   231  				case listenerNamesCh <- req.GetResourceNames():
   232  				case <-ctx.Done():
   233  				}
   234  			case "type.googleapis.com/envoy.config.route.v3.RouteConfiguration":
   235  				select {
   236  				case routeNamesCh <- req.GetResourceNames():
   237  				case <-ctx.Done():
   238  				}
   239  			}
   240  			return nil
   241  		},
   242  		AllowResourceSubset: true,
   243  	})
   244  
   245  	// Create bootstrap configuration pointing to the above management server.
   246  	nodeID := uuid.New().String()
   247  	bootstrapContents := e2e.DefaultBootstrapContents(t, nodeID, managementServer.Address)
   248  
   249  	// Create a listener on a local port to act as the xDS enabled gRPC server.
   250  	lis, err := testutils.LocalTCPListener()
   251  	if err != nil {
   252  		t.Fatalf("Failed to listen to local port: %v", err)
   253  	}
   254  	host, port, err := hostPortFromListener(lis)
   255  	if err != nil {
   256  		t.Fatalf("Failed to retrieve host and port of server: %v", err)
   257  	}
   258  
   259  	// Configure the managegement server with a listener resource for the above
   260  	// xDS enabled gRPC server.
   261  	const routeConfigName = "routeName"
   262  	resources := e2e.UpdateOptions{
   263  		NodeID:         nodeID,
   264  		Listeners:      []*v3listenerpb.Listener{e2e.DefaultServerListenerWithRouteConfigName(host, port, e2e.SecurityLevelNone, "routeName")},
   265  		SkipValidation: true,
   266  	}
   267  	if err := managementServer.Update(ctx, resources); err != nil {
   268  		t.Fatal(err)
   269  	}
   270  
   271  	// Start an xDS-enabled gRPC server with the above bootstrap configuration.
   272  	config, err := bootstrap.NewConfigFromContents(bootstrapContents)
   273  	if err != nil {
   274  		t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err)
   275  	}
   276  	pool := xdsclient.NewPool(config)
   277  	modeChangeHandler := newServingModeChangeHandler(t)
   278  	modeChangeOpt := xds.ServingModeCallback(modeChangeHandler.modeChangeCallback)
   279  	createStubServer(t, lis, modeChangeOpt, xds.ClientPoolForTesting(pool))
   280  
   281  	// Wait for the expected listener resource to be requested.
   282  	wantLisResourceNames := []string{fmt.Sprintf(e2e.ServerListenerResourceNameTemplate, net.JoinHostPort(host, strconv.Itoa(int(port))))}
   283  	select {
   284  	case <-ctx.Done():
   285  		t.Fatal("Timeout waiting for the expected listener resource to be requested")
   286  	case gotLisResourceName := <-listenerNamesCh:
   287  		if !cmp.Equal(gotLisResourceName, wantLisResourceNames) {
   288  			t.Fatalf("Got unexpected listener resource names: %v, want %v", gotLisResourceName, wantLisResourceNames)
   289  		}
   290  	}
   291  
   292  	// Wait for the expected route config resource to be requested.
   293  	select {
   294  	case <-ctx.Done():
   295  		t.Fatal("Timeout waiting for the expected route config resource to be requested")
   296  	case gotRouteNames := <-routeNamesCh:
   297  		if !cmp.Equal(gotRouteNames, []string{routeConfigName}) {
   298  			t.Fatalf("Got unexpected route config resource names: %v, want %v", gotRouteNames, []string{routeConfigName})
   299  		}
   300  	}
   301  
   302  	// Ensure that the server is not serving RPCs yet.
   303  	sCtx, sCancel := context.WithTimeout(ctx, defaultTestShortTimeout)
   304  	defer sCancel()
   305  	select {
   306  	case <-sCtx.Done():
   307  	case <-modeChangeHandler.modeCh:
   308  		t.Fatal("Server started serving RPCs before the route config was received")
   309  	}
   310  
   311  	// Create a gRPC channel to the xDS enabled server.
   312  	cc, err := grpc.NewClient(lis.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials()))
   313  	if err != nil {
   314  		t.Fatalf("grpc.NewClient(%q) failed: %v", lis.Addr(), err)
   315  	}
   316  	defer cc.Close()
   317  
   318  	// Ensure that the server isnt't serving RPCs successfully.
   319  	client := testgrpc.NewTestServiceClient(cc)
   320  	if _, err := client.EmptyCall(ctx, &testpb.Empty{}); err == nil || status.Code(err) != codes.Unavailable {
   321  		t.Fatalf("EmptyCall() returned %v, want %v", err, codes.Unavailable)
   322  	}
   323  
   324  	// Configure the management server with the expected route config resource,
   325  	// and expext RPCs to succeed.
   326  	resources.Routes = []*v3routepb.RouteConfiguration{e2e.RouteConfigNonForwardingAction(routeConfigName)}
   327  	if err := managementServer.Update(ctx, resources); err != nil {
   328  		t.Fatal(err)
   329  	}
   330  	select {
   331  	case <-ctx.Done():
   332  		t.Fatal("Timeout waiting for the server to start serving RPCs")
   333  	case gotMode := <-modeChangeHandler.modeCh:
   334  		if gotMode != connectivity.ServingModeServing {
   335  			t.Fatalf("Mode changed to %v, want %v", gotMode, connectivity.ServingModeServing)
   336  		}
   337  	}
   338  	waitForSuccessfulRPC(ctx, t, cc)
   339  }
   340  
   341  // Tests that the xDS-enabled gRPC server cleans up all its resources when all
   342  // connections to it are closed.
   343  func (s) TestServer_ConnectionCleanup(t *testing.T) {
   344  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   345  	defer cancel()
   346  
   347  	// Start an xDS management server.
   348  	managementServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{})
   349  
   350  	// Create bootstrap configuration pointing to the above management server.
   351  	nodeID := uuid.New().String()
   352  	bootstrapContents := e2e.DefaultBootstrapContents(t, nodeID, managementServer.Address)
   353  
   354  	// Create a listener on a local port to act as the xDS enabled gRPC server.
   355  	lis, err := testutils.LocalTCPListener()
   356  	if err != nil {
   357  		t.Fatalf("Failed to listen to local port: %v", err)
   358  	}
   359  	host, port, err := hostPortFromListener(lis)
   360  	if err != nil {
   361  		t.Fatalf("Failed to retrieve host and port of server: %v", err)
   362  	}
   363  
   364  	// Configure the managegement server with a listener and route configuration
   365  	// resource for the above xDS enabled gRPC server.
   366  	const routeConfigName = "routeName"
   367  	resources := e2e.UpdateOptions{
   368  		NodeID:         nodeID,
   369  		Listeners:      []*v3listenerpb.Listener{e2e.DefaultServerListenerWithRouteConfigName(host, port, e2e.SecurityLevelNone, "routeName")},
   370  		Routes:         []*v3routepb.RouteConfiguration{e2e.RouteConfigNonForwardingAction(routeConfigName)},
   371  		SkipValidation: true,
   372  	}
   373  	if err := managementServer.Update(ctx, resources); err != nil {
   374  		t.Fatal(err)
   375  	}
   376  
   377  	// Start an xDS-enabled gRPC server with the above bootstrap configuration.
   378  	config, err := bootstrap.NewConfigFromContents(bootstrapContents)
   379  	if err != nil {
   380  		t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err)
   381  	}
   382  	pool := xdsclient.NewPool(config)
   383  	modeChangeOpt := xds.ServingModeCallback(func(addr net.Addr, args xds.ServingModeChangeArgs) {
   384  		t.Logf("Serving mode for listener %q changed to %q, err: %v", addr.String(), args.Mode, args.Err)
   385  	})
   386  	createStubServer(t, lis, modeChangeOpt, xds.ClientPoolForTesting(pool))
   387  
   388  	// Create a gRPC channel and verify that RPCs succeed.
   389  	cc, err := grpc.NewClient(lis.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials()))
   390  	if err != nil {
   391  		t.Fatalf("grpc.NewClient(%q) failed: %v", lis.Addr(), err)
   392  	}
   393  	defer cc.Close()
   394  	waitForSuccessfulRPC(ctx, t, cc)
   395  
   396  	// Create multiple channels to the server, and make an RPC on each one. When
   397  	// everything is closed, the server should have cleaned up all its resources
   398  	// as well (and this will be verified by the leakchecker).
   399  	const numConns = 100
   400  	var wg sync.WaitGroup
   401  	wg.Add(numConns)
   402  	for range numConns {
   403  		go func() {
   404  			defer wg.Done()
   405  			cc, err := grpc.NewClient(lis.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials()))
   406  			if err != nil {
   407  				t.Errorf("grpc.NewClient failed with err: %v", err)
   408  			}
   409  			client := testgrpc.NewTestServiceClient(cc)
   410  			if _, err := client.EmptyCall(ctx, &testpb.Empty{}); err != nil {
   411  				t.Errorf("EmptyCall() failed: %v", err)
   412  			}
   413  			cc.Close()
   414  		}()
   415  	}
   416  	wg.Wait()
   417  }
   418  
   419  // Tests that multiple xDS-enabled gRPC servers can be created with different
   420  // bootstrap configurations, and that they correctly request different LDS
   421  // resources from the management server based on their respective listening
   422  // ports.  It also ensures that gRPC clients can connect to the intended server
   423  // and that RPCs function correctly. The test uses the grpc.Peer() call option
   424  // to validate that the client is connected to the correct server.
   425  func (s) TestServer_MultipleServers_DifferentBootstrapConfigurations(t *testing.T) {
   426  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   427  	defer cancel()
   428  	mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{AllowResourceSubset: true})
   429  
   430  	// Create two bootstrap configurations pointing to the above management server.
   431  	nodeID1 := uuid.New().String()
   432  	bootstrapContents1 := e2e.DefaultBootstrapContents(t, nodeID1, mgmtServer.Address)
   433  	nodeID2 := uuid.New().String()
   434  	bootstrapContents2 := e2e.DefaultBootstrapContents(t, nodeID2, mgmtServer.Address)
   435  
   436  	// Create two xDS-enabled gRPC servers using the above bootstrap configs.
   437  	lis1, err := testutils.LocalTCPListener()
   438  	if err != nil {
   439  		t.Fatalf("testutils.LocalTCPListener() failed: %v", err)
   440  	}
   441  	lis2, err := testutils.LocalTCPListener()
   442  	if err != nil {
   443  		t.Fatalf("testutils.LocalTCPListener() failed: %v", err)
   444  	}
   445  
   446  	modeChangeHandler1 := newServingModeChangeHandler(t)
   447  	modeChangeOpt1 := xds.ServingModeCallback(modeChangeHandler1.modeChangeCallback)
   448  	modeChangeHandler2 := newServingModeChangeHandler(t)
   449  	modeChangeOpt2 := xds.ServingModeCallback(modeChangeHandler2.modeChangeCallback)
   450  	config1, err := bootstrap.NewConfigFromContents(bootstrapContents1)
   451  	if err != nil {
   452  		t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents1), err)
   453  	}
   454  	pool1 := xdsclient.NewPool(config1)
   455  	config2, err := bootstrap.NewConfigFromContents(bootstrapContents2)
   456  	if err != nil {
   457  		t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents2), err)
   458  	}
   459  	pool2 := xdsclient.NewPool(config2)
   460  	createStubServer(t, lis1, modeChangeOpt1, xds.ClientPoolForTesting(pool1))
   461  	createStubServer(t, lis2, modeChangeOpt2, xds.ClientPoolForTesting(pool2))
   462  
   463  	// Update the management server with the listener resources pointing to the
   464  	// corresponding gRPC servers.
   465  	host1, port1, err := hostPortFromListener(lis1)
   466  	if err != nil {
   467  		t.Fatalf("Failed to retrieve host and port of server: %v", err)
   468  	}
   469  	host2, port2, err := hostPortFromListener(lis2)
   470  	if err != nil {
   471  		t.Fatalf("Failed to retrieve host and port of server: %v", err)
   472  	}
   473  
   474  	resources1 := e2e.UpdateOptions{
   475  		NodeID:    nodeID1,
   476  		Listeners: []*v3listenerpb.Listener{e2e.DefaultServerListener(host1, port1, e2e.SecurityLevelNone, "routeName")},
   477  	}
   478  	if err := mgmtServer.Update(ctx, resources1); err != nil {
   479  		t.Fatal(err)
   480  	}
   481  
   482  	resources2 := e2e.UpdateOptions{
   483  		NodeID:    nodeID2,
   484  		Listeners: []*v3listenerpb.Listener{e2e.DefaultServerListener(host2, port2, e2e.SecurityLevelNone, "routeName")},
   485  	}
   486  	if err := mgmtServer.Update(ctx, resources2); err != nil {
   487  		t.Fatal(err)
   488  	}
   489  
   490  	select {
   491  	case <-ctx.Done():
   492  		t.Fatal("Timeout waiting for the xDS-enabled gRPC server to go SERVING")
   493  	case gotMode := <-modeChangeHandler1.modeCh:
   494  		if gotMode != connectivity.ServingModeServing {
   495  			t.Fatalf("Mode changed to %v, want %v", gotMode, connectivity.ServingModeServing)
   496  		}
   497  	}
   498  	select {
   499  	case <-ctx.Done():
   500  		t.Fatal("Timeout waiting for the xDS-enabled gRPC server to go SERVING")
   501  	case gotMode := <-modeChangeHandler2.modeCh:
   502  		if gotMode != connectivity.ServingModeServing {
   503  			t.Fatalf("Mode changed to %v, want %v", gotMode, connectivity.ServingModeServing)
   504  		}
   505  	}
   506  
   507  	// Create two gRPC clients, one for each server.
   508  	cc1, err := grpc.NewClient(lis1.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials()))
   509  	if err != nil {
   510  		t.Fatalf("Failed to create client for test server 1: %s, %v", lis1.Addr().String(), err)
   511  	}
   512  	defer cc1.Close()
   513  
   514  	cc2, err := grpc.NewClient(lis2.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials()))
   515  	if err != nil {
   516  		t.Fatalf("Failed to create client for test server 2: %s, %v", lis2.Addr().String(), err)
   517  	}
   518  	defer cc2.Close()
   519  
   520  	// Both unary RPCs should work once the servers transitions into serving.
   521  	var peer1 peer.Peer
   522  	waitForSuccessfulRPC(ctx, t, cc1, grpc.Peer(&peer1))
   523  	if peer1.Addr.String() != lis1.Addr().String() {
   524  		t.Errorf("Connected to wrong peer: %s, want %s", peer1.Addr, lis1.Addr())
   525  	}
   526  
   527  	var peer2 peer.Peer
   528  	waitForSuccessfulRPC(ctx, t, cc2, grpc.Peer(&peer2))
   529  	if peer2.Addr.String() != lis2.Addr().String() {
   530  		t.Errorf("Connected to wrong peer: %s, want %s", peer2.Addr, lis2.Addr())
   531  	}
   532  }