github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvserver/rangefeed/task_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 rangefeed
    12  
    13  import (
    14  	"context"
    15  	"sort"
    16  	"testing"
    17  
    18  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    19  	"github.com/cockroachdb/cockroach/pkg/storage"
    20  	"github.com/cockroachdb/cockroach/pkg/storage/enginepb"
    21  	"github.com/cockroachdb/cockroach/pkg/util/hlc"
    22  	"github.com/cockroachdb/cockroach/pkg/util/leaktest"
    23  	"github.com/cockroachdb/cockroach/pkg/util/protoutil"
    24  	"github.com/cockroachdb/cockroach/pkg/util/uuid"
    25  	"github.com/stretchr/testify/require"
    26  )
    27  
    28  func makeKV(key, val string, ts int64) storage.MVCCKeyValue {
    29  	return storage.MVCCKeyValue{
    30  		Key: storage.MVCCKey{
    31  			Key:       roachpb.Key(key),
    32  			Timestamp: hlc.Timestamp{WallTime: ts},
    33  		},
    34  		Value: []byte(val),
    35  	}
    36  }
    37  
    38  func makeProvisionalKV(key, val string, ts int64) storage.MVCCKeyValue {
    39  	return makeKV(key, val, ts)
    40  }
    41  
    42  func makeMetaKV(key string, meta enginepb.MVCCMetadata) storage.MVCCKeyValue {
    43  	b, err := protoutil.Marshal(&meta)
    44  	if err != nil {
    45  		panic(err)
    46  	}
    47  	return storage.MVCCKeyValue{
    48  		Key: storage.MVCCKey{
    49  			Key: roachpb.Key(key),
    50  		},
    51  		Value: b,
    52  	}
    53  }
    54  
    55  func makeInline(key, val string) storage.MVCCKeyValue {
    56  	return makeMetaKV(key, enginepb.MVCCMetadata{
    57  		RawBytes: []byte(val),
    58  	})
    59  }
    60  
    61  func makeIntent(key string, txnID uuid.UUID, txnKey string, txnTS int64) storage.MVCCKeyValue {
    62  	return makeMetaKV(key, enginepb.MVCCMetadata{
    63  		Txn: &enginepb.TxnMeta{
    64  			ID:             txnID,
    65  			Key:            []byte(txnKey),
    66  			WriteTimestamp: hlc.Timestamp{WallTime: txnTS},
    67  			MinTimestamp:   hlc.Timestamp{WallTime: txnTS},
    68  		},
    69  		Timestamp: hlc.LegacyTimestamp{WallTime: txnTS},
    70  	})
    71  }
    72  
    73  type testIterator struct {
    74  	kvs []storage.MVCCKeyValue
    75  	cur int
    76  
    77  	closed bool
    78  	err    error
    79  	block  chan struct{}
    80  	done   chan struct{}
    81  }
    82  
    83  func newTestIterator(kvs []storage.MVCCKeyValue) *testIterator {
    84  	// Ensure that the key-values are sorted.
    85  	if !sort.SliceIsSorted(kvs, func(i, j int) bool {
    86  		return kvs[i].Key.Less(kvs[j].Key)
    87  	}) {
    88  		panic("unsorted kvs")
    89  	}
    90  
    91  	// Ensure that every intent has a matching MVCCMetadata key
    92  	// and provisional key-value pair.
    93  	const missingErr = "missing provisional kv (makeProvisionalKV) for intent meta key (makeIntent)"
    94  	var meta enginepb.MVCCMetadata
    95  	for i := 0; i < len(kvs); i++ {
    96  		kv := kvs[i]
    97  		if !kv.Key.IsValue() {
    98  			if err := protoutil.Unmarshal(kv.Value, &meta); err != nil {
    99  				panic(err)
   100  			}
   101  			if !meta.IsInline() {
   102  				i++
   103  				if i == len(kvs) {
   104  					panic(missingErr)
   105  				}
   106  				expNextKey := storage.MVCCKey{
   107  					Key:       kv.Key.Key,
   108  					Timestamp: hlc.Timestamp(meta.Timestamp),
   109  				}
   110  				if !kvs[i].Key.Equal(expNextKey) {
   111  					panic(missingErr)
   112  				}
   113  			}
   114  		}
   115  	}
   116  
   117  	return &testIterator{
   118  		kvs:  kvs,
   119  		cur:  -1,
   120  		done: make(chan struct{}),
   121  	}
   122  }
   123  
   124  func (s *testIterator) Close() {
   125  	s.closed = true
   126  	close(s.done)
   127  }
   128  
   129  func (s *testIterator) SeekGE(key storage.MVCCKey) {
   130  	if s.closed {
   131  		panic("testIterator closed")
   132  	}
   133  	if s.block != nil {
   134  		<-s.block
   135  	}
   136  	if s.err != nil {
   137  		return
   138  	}
   139  	if s.cur == -1 {
   140  		s.cur++
   141  	}
   142  	for ; s.cur < len(s.kvs); s.cur++ {
   143  		if !s.curKV().Key.Less(key) {
   144  			break
   145  		}
   146  	}
   147  }
   148  
   149  func (s *testIterator) Valid() (bool, error) {
   150  	if s.err != nil {
   151  		return false, s.err
   152  	}
   153  	if s.cur == -1 || s.cur >= len(s.kvs) {
   154  		return false, nil
   155  	}
   156  	return true, nil
   157  }
   158  
   159  func (s *testIterator) Next() { s.cur++ }
   160  
   161  func (s *testIterator) NextKey() {
   162  	if s.cur == -1 {
   163  		s.cur = 0
   164  		return
   165  	}
   166  	origKey := s.curKV().Key.Key
   167  	for s.cur++; s.cur < len(s.kvs); s.cur++ {
   168  		if !s.curKV().Key.Key.Equal(origKey) {
   169  			break
   170  		}
   171  	}
   172  }
   173  
   174  func (s *testIterator) UnsafeKey() storage.MVCCKey {
   175  	return s.curKV().Key
   176  }
   177  
   178  func (s *testIterator) UnsafeValue() []byte {
   179  	return s.curKV().Value
   180  }
   181  
   182  func (s *testIterator) curKV() storage.MVCCKeyValue {
   183  	return s.kvs[s.cur]
   184  }
   185  
   186  func TestInitResolvedTSScan(t *testing.T) {
   187  	defer leaktest.AfterTest(t)()
   188  
   189  	// Mock processor. We just needs its eventC.
   190  	p := Processor{
   191  		Config: Config{
   192  			Span: roachpb.RSpan{
   193  				Key:    roachpb.RKey("d"),
   194  				EndKey: roachpb.RKey("w"),
   195  			},
   196  		},
   197  		eventC: make(chan event, 100),
   198  	}
   199  
   200  	// Run an init rts scan over a test iterator with the following keys.
   201  	txn1, txn2 := uuid.MakeV4(), uuid.MakeV4()
   202  	iter := newTestIterator([]storage.MVCCKeyValue{
   203  		makeKV("a", "val1", 10),
   204  		makeInline("b", "val2"),
   205  		makeIntent("c", txn1, "txnKey1", 15),
   206  		makeProvisionalKV("c", "txnKey1", 15),
   207  		makeKV("c", "val3", 11),
   208  		makeKV("c", "val4", 9),
   209  		makeIntent("d", txn2, "txnKey2", 21),
   210  		makeProvisionalKV("d", "txnKey2", 21),
   211  		makeKV("d", "val5", 20),
   212  		makeKV("d", "val6", 19),
   213  		makeInline("g", "val7"),
   214  		makeKV("m", "val8", 1),
   215  		makeIntent("n", txn1, "txnKey1", 12),
   216  		makeProvisionalKV("n", "txnKey1", 12),
   217  		makeIntent("r", txn1, "txnKey1", 19),
   218  		makeProvisionalKV("r", "txnKey1", 19),
   219  		makeKV("r", "val9", 4),
   220  		makeIntent("w", txn1, "txnKey1", 3),
   221  		makeProvisionalKV("w", "txnKey1", 3),
   222  		makeInline("x", "val10"),
   223  		makeIntent("z", txn2, "txnKey2", 21),
   224  		makeProvisionalKV("z", "txnKey2", 21),
   225  		makeKV("z", "val11", 4),
   226  	})
   227  
   228  	initScan := newInitResolvedTSScan(&p, iter)
   229  	initScan.Run(context.Background())
   230  	require.True(t, iter.closed)
   231  
   232  	// Compare the event channel to the expected events.
   233  	expEvents := []event{
   234  		{ops: []enginepb.MVCCLogicalOp{
   235  			writeIntentOpWithKey(txn2, []byte("txnKey2"), hlc.Timestamp{WallTime: 21}),
   236  		}},
   237  		{ops: []enginepb.MVCCLogicalOp{
   238  			writeIntentOpWithKey(txn1, []byte("txnKey1"), hlc.Timestamp{WallTime: 12}),
   239  		}},
   240  		{ops: []enginepb.MVCCLogicalOp{
   241  			writeIntentOpWithKey(txn1, []byte("txnKey1"), hlc.Timestamp{WallTime: 19}),
   242  		}},
   243  		{initRTS: true},
   244  	}
   245  	require.Equal(t, len(expEvents), len(p.eventC))
   246  	for _, expEvent := range expEvents {
   247  		require.Equal(t, expEvent, <-p.eventC)
   248  	}
   249  }
   250  
   251  type testTxnPusher struct {
   252  	pushTxnsFn               func([]enginepb.TxnMeta, hlc.Timestamp) ([]*roachpb.Transaction, error)
   253  	cleanupTxnIntentsAsyncFn func([]*roachpb.Transaction) error
   254  }
   255  
   256  func (tp *testTxnPusher) PushTxns(
   257  	ctx context.Context, txns []enginepb.TxnMeta, ts hlc.Timestamp,
   258  ) ([]*roachpb.Transaction, error) {
   259  	return tp.pushTxnsFn(txns, ts)
   260  }
   261  
   262  func (tp *testTxnPusher) CleanupTxnIntentsAsync(
   263  	ctx context.Context, txns []*roachpb.Transaction,
   264  ) error {
   265  	return tp.cleanupTxnIntentsAsyncFn(txns)
   266  }
   267  
   268  func (tp *testTxnPusher) mockPushTxns(
   269  	fn func([]enginepb.TxnMeta, hlc.Timestamp) ([]*roachpb.Transaction, error),
   270  ) {
   271  	tp.pushTxnsFn = fn
   272  }
   273  
   274  func (tp *testTxnPusher) mockCleanupTxnIntentsAsync(fn func([]*roachpb.Transaction) error) {
   275  	tp.cleanupTxnIntentsAsyncFn = fn
   276  }
   277  
   278  func TestTxnPushAttempt(t *testing.T) {
   279  	defer leaktest.AfterTest(t)()
   280  
   281  	// Create a set of transactions.
   282  	txn1, txn2, txn3 := uuid.MakeV4(), uuid.MakeV4(), uuid.MakeV4()
   283  	ts1, ts2, ts3 := hlc.Timestamp{WallTime: 1}, hlc.Timestamp{WallTime: 2}, hlc.Timestamp{WallTime: 3}
   284  	txn1Meta := enginepb.TxnMeta{ID: txn1, Key: keyA, WriteTimestamp: ts1, MinTimestamp: ts1}
   285  	txn2Meta := enginepb.TxnMeta{ID: txn2, Key: keyB, WriteTimestamp: ts2, MinTimestamp: ts2}
   286  	txn3Meta := enginepb.TxnMeta{ID: txn3, Key: keyC, WriteTimestamp: ts3, MinTimestamp: ts3}
   287  	txn1Proto := &roachpb.Transaction{TxnMeta: txn1Meta, Status: roachpb.PENDING}
   288  	txn2Proto := &roachpb.Transaction{TxnMeta: txn2Meta, Status: roachpb.COMMITTED}
   289  	txn3Proto := &roachpb.Transaction{TxnMeta: txn3Meta, Status: roachpb.ABORTED}
   290  
   291  	// Run a txnPushAttempt.
   292  	var tp testTxnPusher
   293  	tp.mockPushTxns(func(txns []enginepb.TxnMeta, ts hlc.Timestamp) ([]*roachpb.Transaction, error) {
   294  		require.Equal(t, 3, len(txns))
   295  		require.Equal(t, txn1Meta, txns[0])
   296  		require.Equal(t, txn2Meta, txns[1])
   297  		require.Equal(t, txn3Meta, txns[2])
   298  		require.Equal(t, hlc.Timestamp{WallTime: 15}, ts)
   299  
   300  		// Return all three protos. The PENDING txn is pushed.
   301  		txn1ProtoPushed := txn1Proto.Clone()
   302  		txn1ProtoPushed.WriteTimestamp = ts
   303  		return []*roachpb.Transaction{txn1ProtoPushed, txn2Proto, txn3Proto}, nil
   304  	})
   305  	tp.mockCleanupTxnIntentsAsync(func(txns []*roachpb.Transaction) error {
   306  		require.Equal(t, 2, len(txns))
   307  		require.Equal(t, txn2Proto, txns[0])
   308  		require.Equal(t, txn3Proto, txns[1])
   309  		return nil
   310  	})
   311  
   312  	// Mock processor. We just needs its eventC.
   313  	p := Processor{eventC: make(chan event, 100)}
   314  	p.TxnPusher = &tp
   315  
   316  	txns := []enginepb.TxnMeta{txn1Meta, txn2Meta, txn3Meta}
   317  	doneC := make(chan struct{})
   318  	pushAttempt := newTxnPushAttempt(&p, txns, hlc.Timestamp{WallTime: 15}, doneC)
   319  	pushAttempt.Run(context.Background())
   320  	<-doneC // check if closed
   321  
   322  	// Compare the event channel to the expected events.
   323  	expEvents := []event{
   324  		{ops: []enginepb.MVCCLogicalOp{
   325  			updateIntentOp(txn1, hlc.Timestamp{WallTime: 15}),
   326  			updateIntentOp(txn2, hlc.Timestamp{WallTime: 2}),
   327  			abortTxnOp(txn3),
   328  		}},
   329  	}
   330  	require.Equal(t, len(expEvents), len(p.eventC))
   331  	for _, expEvent := range expEvents {
   332  		require.Equal(t, expEvent, <-p.eventC)
   333  	}
   334  }