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 }