github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvserver/closedts/transport/transport_test.go (about)

     1  // Copyright 2018 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 transport_test
    12  
    13  import (
    14  	"context"
    15  	"fmt"
    16  	"strings"
    17  	"testing"
    18  
    19  	"github.com/cockroachdb/cockroach/pkg/kv/kvserver/closedts/ctpb"
    20  	"github.com/cockroachdb/cockroach/pkg/kv/kvserver/closedts/transport"
    21  	transporttestutils "github.com/cockroachdb/cockroach/pkg/kv/kvserver/closedts/transport/testutils"
    22  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    23  	"github.com/cockroachdb/cockroach/pkg/settings/cluster"
    24  	"github.com/cockroachdb/cockroach/pkg/testutils"
    25  	"github.com/cockroachdb/cockroach/pkg/util/hlc"
    26  	"github.com/cockroachdb/cockroach/pkg/util/leaktest"
    27  	"github.com/cockroachdb/cockroach/pkg/util/stop"
    28  	"github.com/cockroachdb/errors"
    29  	"github.com/kr/pretty"
    30  )
    31  
    32  // NewTestContainer sets up an environment suitable for black box testing the
    33  // transport subsystem. The returned test container contains most notably a
    34  // Clients and Server set up to communicate to each other via a Dialer (which
    35  // keeps a transcript that can be verified).
    36  func NewTestContainer() *TestContainer {
    37  	stopper := stop.NewStopper()
    38  
    39  	st := cluster.MakeTestingClusterSettings()
    40  	p := &TestProducer{}
    41  	sink := newTestNotifyee(stopper)
    42  	refreshed := &RefreshTracker{}
    43  	s := transport.NewServer(stopper, p, refreshed.Add)
    44  	dialer := transporttestutils.NewChanDialer(stopper, s)
    45  	c := transport.NewClients(transport.Config{
    46  		NodeID:   roachpb.NodeID(12345),
    47  		Settings: st,
    48  		Stopper:  stopper,
    49  		Dialer:   dialer,
    50  		Sink:     sink,
    51  	})
    52  	return &TestContainer{
    53  		Settings:  st,
    54  		Stopper:   stopper,
    55  		Producer:  p,
    56  		Notifyee:  sink,
    57  		Refreshed: refreshed,
    58  		Server:    s,
    59  		Dialer:    dialer,
    60  		Clients:   c,
    61  	}
    62  }
    63  
    64  func assertNumSubscribers(t *testing.T, p *TestProducer, exp int) {
    65  	testutils.SucceedsSoon(t, func() error {
    66  		n := p.numSubscriptions()
    67  		if n > exp {
    68  			t.Fatalf("expected a single subscription, got %d", n)
    69  		}
    70  		if n < exp {
    71  			return errors.New("waiting for subscription")
    72  		}
    73  		return nil
    74  	})
    75  }
    76  
    77  func TestTransportConnectOnRequest(t *testing.T) {
    78  	defer leaktest.AfterTest(t)()
    79  
    80  	container := NewTestContainer()
    81  	defer container.Stopper.Stop(context.Background())
    82  
    83  	const (
    84  		nodeID  = 1
    85  		rangeID = 13
    86  	)
    87  
    88  	// Requesting an update for a Range implies a connection attempt.
    89  	container.Clients.Request(nodeID, rangeID)
    90  
    91  	// Find the connection (via its subscription to receive new Entries).
    92  	assertNumSubscribers(t, container.Producer, 1)
    93  
    94  	// Verify that the client soon asks the server for an update for this range.
    95  	testutils.SucceedsSoon(t, func() error {
    96  		act := container.Refreshed.Get()
    97  		exp := []roachpb.RangeID{rangeID}
    98  
    99  		if diff := pretty.Diff(act, exp); len(diff) != 0 {
   100  			// We have to kick the tires a little bit. The client can only send
   101  			// the request as the reaction to an Entry.
   102  			container.Producer.sendAll(ctpb.Entry{})
   103  			return errors.Errorf("diff(act, exp): %s", strings.Join(diff, "\n"))
   104  		}
   105  		return nil
   106  	})
   107  }
   108  
   109  func TestTransportClientReceivesEntries(t *testing.T) {
   110  	defer leaktest.AfterTest(t)()
   111  
   112  	container := NewTestContainer()
   113  	defer container.Stopper.Stop(context.Background())
   114  
   115  	const nodeID = 7
   116  
   117  	// Manual reconnections don't spawn new clients.
   118  	container.Clients.EnsureClient(nodeID)
   119  	container.Clients.EnsureClient(nodeID)
   120  	container.Clients.EnsureClient(nodeID)
   121  	assertNumSubscribers(t, container.Producer, 1)
   122  
   123  	// But connecting to other nodes does (only once).
   124  	for i := 0; i < 7; i++ {
   125  		container.Clients.EnsureClient(nodeID + 1)
   126  		container.Clients.EnsureClient(nodeID + 2)
   127  		container.Clients.Request(nodeID+3, roachpb.RangeID(7))
   128  	}
   129  	assertNumSubscribers(t, container.Producer, 4)
   130  
   131  	// Our initial client doesn't do anything except say "hello" via
   132  	// a Reaction.
   133  	testutils.SucceedsSoon(t, func() error {
   134  		expectedTranscript := []interface{}{
   135  			&ctpb.Reaction{},
   136  		}
   137  		return checkTranscript(t, container.Dialer.Transcript(nodeID), expectedTranscript)
   138  	})
   139  
   140  	// Now the producer (to which the server should maintain a subscription for this client, and
   141  	// notifications from which it should relay) emits an Entry.
   142  	e1 := ctpb.Entry{ClosedTimestamp: hlc.Timestamp{WallTime: 1e9}, Epoch: 12, MLAI: map[roachpb.RangeID]ctpb.LAI{12: 7}}
   143  	container.Producer.sendAll(e1)
   144  
   145  	// The client should see this entry soon thereafter. it responds with an empty
   146  	// Reaction (since we haven't Request()ed anything).
   147  	testutils.SucceedsSoon(t, func() error {
   148  		expectedTranscript := []interface{}{
   149  			&ctpb.Reaction{},
   150  			&e1,
   151  			&ctpb.Reaction{},
   152  		}
   153  		return checkTranscript(t, container.Dialer.Transcript(nodeID), expectedTranscript)
   154  	})
   155  
   156  	// And again, but only after Request() is called (which should be reflected in the transcript).
   157  	const rangeID = 7
   158  	container.Clients.Request(nodeID, rangeID)
   159  	e2 := ctpb.Entry{ClosedTimestamp: hlc.Timestamp{WallTime: 2e9}, Epoch: 13, MLAI: map[roachpb.RangeID]ctpb.LAI{13: 8}}
   160  	container.Producer.sendAll(e2)
   161  	testutils.SucceedsSoon(t, func() error {
   162  		expectedTranscript := []interface{}{
   163  			&ctpb.Reaction{},
   164  			&e1,
   165  			&ctpb.Reaction{},
   166  			&e2,
   167  			&ctpb.Reaction{Requested: []roachpb.RangeID{rangeID}},
   168  		}
   169  		return checkTranscript(t, container.Dialer.Transcript(nodeID), expectedTranscript)
   170  	})
   171  
   172  }
   173  
   174  func checkTranscript(t *testing.T, actI, expI []interface{}) error {
   175  	t.Helper()
   176  	var act, exp []string
   177  	for _, i := range actI {
   178  		act = append(act, strings.TrimSpace(fmt.Sprintf("%v", i)))
   179  	}
   180  	for _, i := range expI {
   181  		exp = append(exp, strings.TrimSpace(fmt.Sprintf("%v", i)))
   182  	}
   183  
   184  	diffErr := errors.Errorf("actual:\n%s\nexpected:\n%s", strings.Join(act, "\n"), strings.Join(exp, "\n"))
   185  	if len(act) > len(exp) {
   186  		t.Fatal(errors.Wrap(diffErr, "actual transcript longer than expected"))
   187  	}
   188  	if len(act) < len(exp) {
   189  		return errors.Wrap(diffErr, "waiting for more")
   190  	}
   191  	if diff := pretty.Diff(actI, expI); len(diff) != 0 {
   192  		t.Fatal(errors.Wrapf(diffErr, "diff:\n%v\n", strings.Join(diff, "\n")))
   193  	}
   194  	return nil
   195  }