github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/util/grpcutil/grpc_util_test.go (about)

     1  // Copyright 2017 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package grpcutil_test
    12  
    13  import (
    14  	"context"
    15  	"net"
    16  	"strings"
    17  	"testing"
    18  
    19  	"github.com/cockroachdb/cockroach/pkg/testutils"
    20  	"github.com/cockroachdb/cockroach/pkg/util/grpcutil"
    21  	"github.com/cockroachdb/cockroach/pkg/util/leaktest"
    22  	"github.com/cockroachdb/errors"
    23  	"google.golang.org/grpc"
    24  	healthpb "google.golang.org/grpc/health/grpc_health_v1"
    25  )
    26  
    27  // Implement the grpc health check interface (just because it's the
    28  // simplest predefined RPC service I could find that seems unlikely to
    29  // change out from under us) with an implementation that shuts itself
    30  // down whenever anything calls it. This lets us distinguish errors
    31  // caused by server shutdowns during the request from those when the
    32  // server was already down.
    33  type healthServer struct {
    34  	grpcServer *grpc.Server
    35  }
    36  
    37  func (hs healthServer) Check(
    38  	ctx context.Context, req *healthpb.HealthCheckRequest,
    39  ) (*healthpb.HealthCheckResponse, error) {
    40  	hs.grpcServer.Stop()
    41  
    42  	// Wait for the shutdown to happen before returning from this
    43  	// method.
    44  	<-ctx.Done()
    45  	return nil, errors.New("no one should see this")
    46  }
    47  
    48  func (hs healthServer) Watch(*healthpb.HealthCheckRequest, healthpb.Health_WatchServer) error {
    49  	panic("not implemented")
    50  }
    51  
    52  func TestRequestDidNotStart(t *testing.T) {
    53  	defer leaktest.AfterTest(t)()
    54  
    55  	t.Skip("https://github.com/cockroachdb/cockroach/issues/19708")
    56  
    57  	lis, err := net.Listen("tcp", "127.0.0.1:0")
    58  	if err != nil {
    59  		t.Fatal(err)
    60  	}
    61  	defer func() {
    62  		_ = lis.Close()
    63  	}()
    64  
    65  	server := grpc.NewServer()
    66  	hs := healthServer{server}
    67  	healthpb.RegisterHealthServer(server, hs)
    68  	go func() {
    69  		_ = server.Serve(lis)
    70  	}()
    71  
    72  	conn, err := grpc.Dial(lis.Addr().String(), grpc.WithInsecure())
    73  	if err != nil {
    74  		t.Fatal(err)
    75  	}
    76  	defer func() {
    77  		_ = conn.Close()
    78  	}()
    79  	client := healthpb.NewHealthClient(conn)
    80  
    81  	// The first time, the request will start and we'll get a
    82  	// "connection is closing" message.
    83  	_, err = client.Check(context.Background(), &healthpb.HealthCheckRequest{})
    84  	if err == nil {
    85  		t.Fatal("did not get expected error")
    86  	} else if grpcutil.RequestDidNotStart(err) {
    87  		t.Fatalf("request should have started, but got %s", err)
    88  	} else if !strings.Contains(err.Error(), "is closing") {
    89  		// This assertion is not essential to this test, but since this
    90  		// logic is sensitive to grpc error handling details it's safer to
    91  		// make the test fail when anything changes. This error could be
    92  		// either "transport is closing" or "connection is closing"
    93  		t.Fatalf("expected 'is closing' error but got %s", err)
    94  	}
    95  
    96  	// Afterwards, the request will fail immediately without being sent.
    97  	// But if we try too soon, there's a chance the transport hasn't
    98  	// been put in the "transient failure" state yet and we get a
    99  	// different error.
   100  	testutils.SucceedsSoon(t, func() error {
   101  		_, err = client.Check(context.Background(), &healthpb.HealthCheckRequest{})
   102  		if err == nil {
   103  			return errors.New("did not get expected error")
   104  		} else if !grpcutil.RequestDidNotStart(err) {
   105  			return errors.Errorf("request should not have started, but got %s", err)
   106  		}
   107  		return nil
   108  	})
   109  
   110  	// Once the transport is in the "transient failure" state it should
   111  	// stay that way, and every subsequent request will fail
   112  	// immediately.
   113  	_, err = client.Check(context.Background(), &healthpb.HealthCheckRequest{})
   114  	if err == nil {
   115  		t.Fatal("did not get expected error")
   116  	} else if !grpcutil.RequestDidNotStart(err) {
   117  		t.Fatalf("request should not have started, but got %s", err)
   118  	}
   119  }