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 }