github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/server/drain_test.go (about)

     1  // Copyright 2020 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 server_test
    12  
    13  import (
    14  	"context"
    15  	"io"
    16  	"reflect"
    17  	"testing"
    18  
    19  	"github.com/cockroachdb/cockroach/pkg/base"
    20  	"github.com/cockroachdb/cockroach/pkg/rpc"
    21  	"github.com/cockroachdb/cockroach/pkg/server"
    22  	"github.com/cockroachdb/cockroach/pkg/server/serverpb"
    23  	"github.com/cockroachdb/cockroach/pkg/sql"
    24  	"github.com/cockroachdb/cockroach/pkg/testutils"
    25  	"github.com/cockroachdb/cockroach/pkg/testutils/testcluster"
    26  	"github.com/cockroachdb/cockroach/pkg/util/grpcutil"
    27  	"github.com/cockroachdb/cockroach/pkg/util/leaktest"
    28  	"github.com/cockroachdb/cockroach/pkg/util/log"
    29  	"github.com/cockroachdb/cockroach/pkg/util/stop"
    30  	"github.com/cockroachdb/errors"
    31  	"github.com/kr/pretty"
    32  )
    33  
    34  // TestDrain tests the Drain RPC.
    35  func TestDrain(t *testing.T) {
    36  	defer leaktest.AfterTest(t)()
    37  	doTestDrain(t, true /* newInterface */)
    38  }
    39  
    40  // TestDrainLegacy tests the Drain RPC using the pre-20.1 probe signaling.
    41  // TODO(knz): Remove this test when compatibility with pre-20.1 nodes
    42  // is dropped.
    43  func TestDrainLegacy(t *testing.T) {
    44  	defer leaktest.AfterTest(t)()
    45  	doTestDrain(t, false /* newInterface */)
    46  }
    47  
    48  // doTestDrain runs the drain test.
    49  // The parameter newInterface indicates whether to use the pre-20.1
    50  // protocol based on "drain modes" or the post-20.1 protocol
    51  // using discrete fields on the request object.
    52  func doTestDrain(tt *testing.T, newInterface bool) {
    53  	t := newTestDrainContext(tt, newInterface)
    54  	defer t.Close()
    55  
    56  	// Issue a probe. We're not draining yet, so the probe should
    57  	// reflect that.
    58  	resp := t.sendProbe()
    59  	t.assertDraining(resp, false)
    60  	t.assertRemaining(resp, false)
    61  
    62  	// Issue a drain without shutdown, so we can probe more afterwards.
    63  	resp = t.sendDrainNoShutdown()
    64  	t.assertDraining(resp, true)
    65  	t.assertRemaining(resp, true)
    66  
    67  	// Issue another probe. This checks that the server is still running
    68  	// (i.e. Shutdown: false was effective) and also that the draining
    69  	// status is still properly reported.
    70  	resp = t.sendProbe()
    71  	t.assertDraining(resp, true)
    72  	// probe-only has no remaining.
    73  	t.assertRemaining(resp, false)
    74  
    75  	// Issue another drain. Verify that the remaining is zero (i.e. complete).
    76  	resp = t.sendDrainNoShutdown()
    77  	t.assertDraining(resp, true)
    78  	t.assertRemaining(resp, false)
    79  
    80  	// Now issue a drain request without drain but with shutdown.
    81  	// We're expecting the node to be shut down after that.
    82  	resp = t.sendShutdown()
    83  	if resp != nil {
    84  		t.assertDraining(resp, true)
    85  		t.assertRemaining(resp, false)
    86  	}
    87  
    88  	// Now expect the server to be shut down.
    89  	testutils.SucceedsSoon(t, func() error {
    90  		_, err := t.c.Drain(context.Background(), &serverpb.DrainRequest{Shutdown: false})
    91  		if grpcutil.IsClosedConnection(err) {
    92  			return nil
    93  		}
    94  		return errors.Newf("server not yet refusing RPC, got %v", err)
    95  	})
    96  }
    97  
    98  type testDrainContext struct {
    99  	*testing.T
   100  	tc           *testcluster.TestCluster
   101  	newInterface bool
   102  	c            serverpb.AdminClient
   103  	connCloser   func()
   104  }
   105  
   106  func newTestDrainContext(t *testing.T, newInterface bool) *testDrainContext {
   107  	tc := &testDrainContext{
   108  		T:            t,
   109  		newInterface: newInterface,
   110  		tc: testcluster.StartTestCluster(t, 3, base.TestClusterArgs{
   111  			// We need to start the cluster insecure in order to not
   112  			// care about TLS settings for the RPC client connection.
   113  			ServerArgs: base.TestServerArgs{
   114  				Insecure: true,
   115  			},
   116  		}),
   117  	}
   118  
   119  	// We'll have the RPC talk to the first node.
   120  	var err error
   121  	tc.c, tc.connCloser, err = getAdminClientForServer(context.Background(),
   122  		tc.tc, 0 /* serverIdx */)
   123  	if err != nil {
   124  		tc.Close()
   125  		t.Fatal(err)
   126  	}
   127  
   128  	return tc
   129  }
   130  
   131  func (t *testDrainContext) Close() {
   132  	if t.connCloser != nil {
   133  		t.connCloser()
   134  	}
   135  	t.tc.Stopper().Stop(context.Background())
   136  }
   137  
   138  func (t *testDrainContext) sendProbe() *serverpb.DrainResponse {
   139  	return t.drainRequest(false /* drain */, false /* shutdown */)
   140  }
   141  
   142  func (t *testDrainContext) sendDrainNoShutdown() *serverpb.DrainResponse {
   143  	return t.drainRequest(true /* drain */, false /* shutdown */)
   144  }
   145  
   146  func (t *testDrainContext) drainRequest(drain, shutdown bool) *serverpb.DrainResponse {
   147  	// Issue a simple drain probe.
   148  	req := &serverpb.DrainRequest{Shutdown: shutdown}
   149  
   150  	if drain {
   151  		if t.newInterface {
   152  			req.DoDrain = true
   153  		} else {
   154  			req.DeprecatedProbeIndicator = server.DeprecatedDrainParameter
   155  		}
   156  	}
   157  
   158  	drainStream, err := t.c.Drain(context.Background(), req)
   159  	if err != nil {
   160  		t.Fatal(err)
   161  	}
   162  	resp, err := t.getDrainResponse(drainStream)
   163  	if err != nil {
   164  		t.Fatal(err)
   165  	}
   166  	return resp
   167  }
   168  
   169  func (t *testDrainContext) sendShutdown() *serverpb.DrainResponse {
   170  	req := &serverpb.DrainRequest{Shutdown: true}
   171  	drainStream, err := t.c.Drain(context.Background(), req)
   172  	if err != nil {
   173  		t.Fatal(err)
   174  	}
   175  	resp, err := t.getDrainResponse(drainStream)
   176  	if err != nil {
   177  		// It's possible we're getting "connection reset by peer" or some
   178  		// gRPC initialization failure because the server is shutting
   179  		// down. Tolerate that.
   180  		t.Logf("RPC error: %v", err)
   181  	}
   182  	return resp
   183  }
   184  
   185  func (t *testDrainContext) assertDraining(resp *serverpb.DrainResponse, drain bool) {
   186  	if resp.IsDraining != drain {
   187  		t.Fatalf("expected draining %v, got %v", drain, resp.IsDraining)
   188  	}
   189  	// Check that the deprecated status field is compatible with expectation.
   190  	// TODO(knz): Remove this test when compatibility with pre-20.1 nodes
   191  	// is dropped.
   192  	if drain {
   193  		if !reflect.DeepEqual(resp.DeprecatedDrainStatus, server.DeprecatedDrainParameter) {
   194  			t.Fatalf("expected compat drain status, got %# v", pretty.Formatter(resp))
   195  		}
   196  	} else {
   197  		if len(resp.DeprecatedDrainStatus) > 0 {
   198  			t.Fatalf("expected no compat drain status, got %# v", pretty.Formatter(resp))
   199  		}
   200  	}
   201  }
   202  
   203  func (t *testDrainContext) assertRemaining(resp *serverpb.DrainResponse, remaining bool) {
   204  	if actualRemaining := (resp.DrainRemainingIndicator > 0); remaining != actualRemaining {
   205  		t.Fatalf("expected remaining %v, got %v", remaining, actualRemaining)
   206  	}
   207  }
   208  
   209  func (t *testDrainContext) getDrainResponse(
   210  	stream serverpb.Admin_DrainClient,
   211  ) (*serverpb.DrainResponse, error) {
   212  	resp, err := stream.Recv()
   213  	if err != nil {
   214  		return nil, err
   215  	}
   216  	unexpected, err := stream.Recv()
   217  	if err != io.EOF {
   218  		if unexpected != nil {
   219  			t.Fatalf("unexpected additional response: %# v // %v", pretty.Formatter(unexpected), err)
   220  		}
   221  		if err == nil {
   222  			err = errors.New("unexpected response")
   223  		}
   224  		return nil, err
   225  	}
   226  	return resp, nil
   227  }
   228  
   229  func getAdminClientForServer(
   230  	ctx context.Context, tc *testcluster.TestCluster, serverIdx int,
   231  ) (c serverpb.AdminClient, closer func(), err error) {
   232  	stopper := stop.NewStopper() // stopper for the client.
   233  	// Retrieve some parameters to initialize the client RPC context.
   234  	cfg := tc.Server(0).RPCContext().Config
   235  	execCfg := tc.Server(0).ExecutorConfig().(sql.ExecutorConfig)
   236  	rpcContext := rpc.NewContext(
   237  		log.AmbientContext{Tracer: execCfg.Settings.Tracer},
   238  		cfg, execCfg.Clock, stopper, execCfg.Settings,
   239  	)
   240  	conn, err := rpcContext.GRPCUnvalidatedDial(tc.Server(serverIdx).ServingRPCAddr()).Connect(ctx)
   241  	if err != nil {
   242  		return nil, nil, err
   243  	}
   244  	return serverpb.NewAdminClient(conn), func() { stopper.Stop(ctx) }, nil
   245  }