vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletserver/tx_executor_test.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package tabletserver 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "reflect" 24 "testing" 25 "time" 26 27 "vitess.io/vitess/go/vt/vttablet/tabletserver/tx" 28 29 "github.com/stretchr/testify/require" 30 "google.golang.org/protobuf/proto" 31 32 "vitess.io/vitess/go/mysql/fakesqldb" 33 "vitess.io/vitess/go/sqltypes" 34 "vitess.io/vitess/go/vt/vtgate/fakerpcvtgateconn" 35 "vitess.io/vitess/go/vt/vtgate/vtgateconn" 36 "vitess.io/vitess/go/vt/vttablet/tabletserver/tabletenv" 37 38 querypb "vitess.io/vitess/go/vt/proto/query" 39 topodatapb "vitess.io/vitess/go/vt/proto/topodata" 40 ) 41 42 func TestTxExecutorEmptyPrepare(t *testing.T) { 43 txe, tsv, db := newTestTxExecutor(t) 44 defer db.Close() 45 defer tsv.StopService() 46 txid := newTransaction(tsv, nil) 47 err := txe.Prepare(txid, "aa") 48 require.NoError(t, err) 49 // Nothing should be prepared. 50 require.Empty(t, txe.te.preparedPool.conns, "txe.te.preparedPool.conns") 51 } 52 53 func TestTxExecutorPrepare(t *testing.T) { 54 txe, tsv, db := newTestTxExecutor(t) 55 defer db.Close() 56 defer tsv.StopService() 57 txid := newTxForPrep(tsv) 58 err := txe.Prepare(txid, "aa") 59 require.NoError(t, err) 60 err = txe.RollbackPrepared("aa", 1) 61 require.NoError(t, err) 62 // A retry should still succeed. 63 err = txe.RollbackPrepared("aa", 1) 64 require.NoError(t, err) 65 // A retry with no original id should also succeed. 66 err = txe.RollbackPrepared("aa", 0) 67 require.NoError(t, err) 68 } 69 70 func TestTxExecutorPrepareNotInTx(t *testing.T) { 71 txe, tsv, db := newTestTxExecutor(t) 72 defer db.Close() 73 defer tsv.StopService() 74 err := txe.Prepare(0, "aa") 75 require.EqualError(t, err, "transaction 0: not found") 76 } 77 78 func TestTxExecutorPreparePoolFail(t *testing.T) { 79 txe, tsv, db := newTestTxExecutor(t) 80 defer db.Close() 81 defer tsv.StopService() 82 txid1 := newTxForPrep(tsv) 83 txid2 := newTxForPrep(tsv) 84 err := txe.Prepare(txid1, "aa") 85 require.NoError(t, err) 86 defer txe.RollbackPrepared("aa", 0) 87 err = txe.Prepare(txid2, "bb") 88 require.Error(t, err) 89 require.Contains(t, err.Error(), "prepared transactions exceeded limit") 90 } 91 92 func TestTxExecutorPrepareRedoBeginFail(t *testing.T) { 93 txe, tsv, db := newTestTxExecutor(t) 94 defer db.Close() 95 defer tsv.StopService() 96 txid := newTxForPrep(tsv) 97 db.AddRejectedQuery("begin", errors.New("begin fail")) 98 err := txe.Prepare(txid, "aa") 99 defer txe.RollbackPrepared("aa", 0) 100 require.Error(t, err) 101 require.Contains(t, err.Error(), "begin fail") 102 } 103 104 func TestTxExecutorPrepareRedoFail(t *testing.T) { 105 txe, tsv, db := newTestTxExecutor(t) 106 defer db.Close() 107 defer tsv.StopService() 108 txid := newTxForPrep(tsv) 109 err := txe.Prepare(txid, "bb") 110 defer txe.RollbackPrepared("bb", 0) 111 require.Error(t, err) 112 require.Contains(t, err.Error(), "is not supported") 113 } 114 115 func TestTxExecutorPrepareRedoCommitFail(t *testing.T) { 116 txe, tsv, db := newTestTxExecutor(t) 117 defer db.Close() 118 defer tsv.StopService() 119 txid := newTxForPrep(tsv) 120 db.AddRejectedQuery("commit", errors.New("commit fail")) 121 err := txe.Prepare(txid, "aa") 122 defer txe.RollbackPrepared("aa", 0) 123 require.Error(t, err) 124 require.Contains(t, err.Error(), "commit fail") 125 } 126 127 func TestTxExecutorCommit(t *testing.T) { 128 txe, tsv, db := newTestTxExecutor(t) 129 defer db.Close() 130 defer tsv.StopService() 131 txid := newTxForPrep(tsv) 132 err := txe.Prepare(txid, "aa") 133 require.NoError(t, err) 134 err = txe.CommitPrepared("aa") 135 require.NoError(t, err) 136 // Committing an absent transaction should succeed. 137 err = txe.CommitPrepared("bb") 138 require.NoError(t, err) 139 } 140 141 func TestTxExecutorCommitRedoFail(t *testing.T) { 142 txe, tsv, db := newTestTxExecutor(t) 143 defer db.Close() 144 defer tsv.StopService() 145 txid := newTxForPrep(tsv) 146 // Allow all additions to redo logs to succeed 147 db.AddQueryPattern("insert into _vt\\.redo_state.*", &sqltypes.Result{}) 148 err := txe.Prepare(txid, "bb") 149 require.NoError(t, err) 150 defer txe.RollbackPrepared("bb", 0) 151 db.AddQuery("update _vt.redo_state set state = 'Failed' where dtid = 'bb'", &sqltypes.Result{}) 152 err = txe.CommitPrepared("bb") 153 require.Error(t, err) 154 require.Contains(t, err.Error(), "is not supported") 155 // A retry should fail differently. 156 err = txe.CommitPrepared("bb") 157 require.Error(t, err) 158 require.Contains(t, err.Error(), "cannot commit dtid bb, state: failed") 159 } 160 161 func TestTxExecutorCommitRedoCommitFail(t *testing.T) { 162 txe, tsv, db := newTestTxExecutor(t) 163 defer db.Close() 164 defer tsv.StopService() 165 txid := newTxForPrep(tsv) 166 err := txe.Prepare(txid, "aa") 167 require.NoError(t, err) 168 defer txe.RollbackPrepared("aa", 0) 169 db.AddRejectedQuery("commit", errors.New("commit fail")) 170 err = txe.CommitPrepared("aa") 171 require.Error(t, err) 172 require.Contains(t, err.Error(), "commit fail") 173 } 174 175 func TestTxExecutorRollbackBeginFail(t *testing.T) { 176 txe, tsv, db := newTestTxExecutor(t) 177 defer db.Close() 178 defer tsv.StopService() 179 txid := newTxForPrep(tsv) 180 err := txe.Prepare(txid, "aa") 181 require.NoError(t, err) 182 db.AddRejectedQuery("begin", errors.New("begin fail")) 183 err = txe.RollbackPrepared("aa", txid) 184 require.Error(t, err) 185 require.Contains(t, err.Error(), "begin fail") 186 } 187 188 func TestTxExecutorRollbackRedoFail(t *testing.T) { 189 txe, tsv, db := newTestTxExecutor(t) 190 defer db.Close() 191 defer tsv.StopService() 192 txid := newTxForPrep(tsv) 193 // Allow all additions to redo logs to succeed 194 db.AddQueryPattern("insert into _vt\\.redo_state.*", &sqltypes.Result{}) 195 err := txe.Prepare(txid, "bb") 196 require.NoError(t, err) 197 err = txe.RollbackPrepared("bb", txid) 198 require.Error(t, err) 199 require.Contains(t, err.Error(), "is not supported") 200 } 201 202 func TestExecutorCreateTransaction(t *testing.T) { 203 txe, tsv, db := newTestTxExecutor(t) 204 defer db.Close() 205 defer tsv.StopService() 206 207 db.AddQueryPattern(fmt.Sprintf("insert into _vt\\.dt_state\\(dtid, state, time_created\\) values \\('aa', %d,.*", int(querypb.TransactionState_PREPARE)), &sqltypes.Result{}) 208 db.AddQueryPattern("insert into _vt\\.dt_participant\\(dtid, id, keyspace, shard\\) values \\('aa', 1,.*", &sqltypes.Result{}) 209 err := txe.CreateTransaction("aa", []*querypb.Target{{ 210 Keyspace: "t1", 211 Shard: "0", 212 }}) 213 require.NoError(t, err) 214 } 215 216 func TestExecutorStartCommit(t *testing.T) { 217 txe, tsv, db := newTestTxExecutor(t) 218 defer db.Close() 219 defer tsv.StopService() 220 221 commitTransition := fmt.Sprintf("update _vt.dt_state set state = %d where dtid = 'aa' and state = %d", int(querypb.TransactionState_COMMIT), int(querypb.TransactionState_PREPARE)) 222 db.AddQuery(commitTransition, &sqltypes.Result{RowsAffected: 1}) 223 txid := newTxForPrep(tsv) 224 err := txe.StartCommit(txid, "aa") 225 require.NoError(t, err) 226 227 db.AddQuery(commitTransition, &sqltypes.Result{}) 228 txid = newTxForPrep(tsv) 229 err = txe.StartCommit(txid, "aa") 230 require.Error(t, err) 231 require.Contains(t, err.Error(), "could not transition to COMMIT: aa") 232 } 233 234 func TestExecutorSetRollback(t *testing.T) { 235 txe, tsv, db := newTestTxExecutor(t) 236 defer db.Close() 237 defer tsv.StopService() 238 239 rollbackTransition := fmt.Sprintf("update _vt.dt_state set state = %d where dtid = 'aa' and state = %d", int(querypb.TransactionState_ROLLBACK), int(querypb.TransactionState_PREPARE)) 240 db.AddQuery(rollbackTransition, &sqltypes.Result{RowsAffected: 1}) 241 txid := newTxForPrep(tsv) 242 err := txe.SetRollback("aa", txid) 243 require.NoError(t, err) 244 245 db.AddQuery(rollbackTransition, &sqltypes.Result{}) 246 txid = newTxForPrep(tsv) 247 err = txe.SetRollback("aa", txid) 248 require.Error(t, err) 249 require.Contains(t, err.Error(), "could not transition to ROLLBACK: aa") 250 } 251 252 func TestExecutorConcludeTransaction(t *testing.T) { 253 txe, tsv, db := newTestTxExecutor(t) 254 defer db.Close() 255 defer tsv.StopService() 256 257 db.AddQuery("delete from _vt.dt_state where dtid = 'aa'", &sqltypes.Result{}) 258 db.AddQuery("delete from _vt.dt_participant where dtid = 'aa'", &sqltypes.Result{}) 259 err := txe.ConcludeTransaction("aa") 260 require.NoError(t, err) 261 } 262 263 func TestExecutorReadTransaction(t *testing.T) { 264 txe, tsv, db := newTestTxExecutor(t) 265 defer db.Close() 266 defer tsv.StopService() 267 268 db.AddQuery("select dtid, state, time_created from _vt.dt_state where dtid = 'aa'", &sqltypes.Result{}) 269 got, err := txe.ReadTransaction("aa") 270 require.NoError(t, err) 271 want := &querypb.TransactionMetadata{} 272 if !proto.Equal(got, want) { 273 t.Errorf("ReadTransaction: %v, want %v", got, want) 274 } 275 276 txResult := &sqltypes.Result{ 277 Fields: []*querypb.Field{ 278 {Type: sqltypes.VarChar}, 279 {Type: sqltypes.Int64}, 280 {Type: sqltypes.Int64}, 281 }, 282 Rows: [][]sqltypes.Value{{ 283 sqltypes.NewVarBinary("aa"), 284 sqltypes.NewInt64(int64(querypb.TransactionState_PREPARE)), 285 sqltypes.NewVarBinary("1"), 286 }}, 287 } 288 db.AddQuery("select dtid, state, time_created from _vt.dt_state where dtid = 'aa'", txResult) 289 db.AddQuery("select keyspace, shard from _vt.dt_participant where dtid = 'aa'", &sqltypes.Result{ 290 Fields: []*querypb.Field{ 291 {Type: sqltypes.VarChar}, 292 {Type: sqltypes.VarChar}, 293 }, 294 Rows: [][]sqltypes.Value{{ 295 sqltypes.NewVarBinary("test1"), 296 sqltypes.NewVarBinary("0"), 297 }, { 298 sqltypes.NewVarBinary("test2"), 299 sqltypes.NewVarBinary("1"), 300 }}, 301 }) 302 got, err = txe.ReadTransaction("aa") 303 require.NoError(t, err) 304 want = &querypb.TransactionMetadata{ 305 Dtid: "aa", 306 State: querypb.TransactionState_PREPARE, 307 TimeCreated: 1, 308 Participants: []*querypb.Target{{ 309 Keyspace: "test1", 310 Shard: "0", 311 TabletType: topodatapb.TabletType_PRIMARY, 312 }, { 313 Keyspace: "test2", 314 Shard: "1", 315 TabletType: topodatapb.TabletType_PRIMARY, 316 }}, 317 } 318 if !proto.Equal(got, want) { 319 t.Errorf("ReadTransaction: %v, want %v", got, want) 320 } 321 322 txResult = &sqltypes.Result{ 323 Fields: []*querypb.Field{ 324 {Type: sqltypes.VarChar}, 325 {Type: sqltypes.Int64}, 326 {Type: sqltypes.Int64}, 327 }, 328 Rows: [][]sqltypes.Value{{ 329 sqltypes.NewVarBinary("aa"), 330 sqltypes.NewInt64(int64(querypb.TransactionState_COMMIT)), 331 sqltypes.NewVarBinary("1"), 332 }}, 333 } 334 db.AddQuery("select dtid, state, time_created from _vt.dt_state where dtid = 'aa'", txResult) 335 want.State = querypb.TransactionState_COMMIT 336 got, err = txe.ReadTransaction("aa") 337 require.NoError(t, err) 338 if !proto.Equal(got, want) { 339 t.Errorf("ReadTransaction: %v, want %v", got, want) 340 } 341 342 txResult = &sqltypes.Result{ 343 Fields: []*querypb.Field{ 344 {Type: sqltypes.VarChar}, 345 {Type: sqltypes.Int64}, 346 {Type: sqltypes.Int64}, 347 }, 348 Rows: [][]sqltypes.Value{{ 349 sqltypes.NewVarBinary("aa"), 350 sqltypes.NewInt64(int64(querypb.TransactionState_ROLLBACK)), 351 sqltypes.NewVarBinary("1"), 352 }}, 353 } 354 db.AddQuery("select dtid, state, time_created from _vt.dt_state where dtid = 'aa'", txResult) 355 want.State = querypb.TransactionState_ROLLBACK 356 got, err = txe.ReadTransaction("aa") 357 require.NoError(t, err) 358 if !proto.Equal(got, want) { 359 t.Errorf("ReadTransaction: %v, want %v", got, want) 360 } 361 } 362 363 func TestExecutorReadAllTransactions(t *testing.T) { 364 txe, tsv, db := newTestTxExecutor(t) 365 defer db.Close() 366 defer tsv.StopService() 367 368 db.AddQuery(txe.te.twoPC.readAllTransactions, &sqltypes.Result{ 369 Fields: []*querypb.Field{ 370 {Type: sqltypes.VarChar}, 371 {Type: sqltypes.Int64}, 372 {Type: sqltypes.Int64}, 373 {Type: sqltypes.VarChar}, 374 {Type: sqltypes.VarChar}, 375 }, 376 Rows: [][]sqltypes.Value{{ 377 sqltypes.NewVarBinary("dtid0"), 378 sqltypes.NewInt64(int64(querypb.TransactionState_PREPARE)), 379 sqltypes.NewVarBinary("1"), 380 sqltypes.NewVarBinary("ks01"), 381 sqltypes.NewVarBinary("shard01"), 382 }}, 383 }) 384 got, _, _, err := txe.ReadTwopcInflight() 385 require.NoError(t, err) 386 want := []*tx.DistributedTx{{ 387 Dtid: "dtid0", 388 State: "PREPARE", 389 Created: time.Unix(0, 1), 390 Participants: []querypb.Target{{ 391 Keyspace: "ks01", 392 Shard: "shard01", 393 }}, 394 }} 395 if !reflect.DeepEqual(got, want) { 396 t.Errorf("ReadAllTransactions:\n%s, want\n%s", jsonStr(got), jsonStr(want)) 397 } 398 } 399 400 // These vars and types are used only for TestExecutorResolveTransaction 401 var dtidCh = make(chan string) 402 403 type FakeVTGateConn struct { 404 fakerpcvtgateconn.FakeVTGateConn 405 } 406 407 func (conn *FakeVTGateConn) ResolveTransaction(ctx context.Context, dtid string) error { 408 dtidCh <- dtid 409 return nil 410 } 411 412 func TestExecutorResolveTransaction(t *testing.T) { 413 protocol := "resolveTest" 414 oldValue := vtgateconn.GetVTGateProtocol() 415 vtgateconn.SetVTGateProtocol(protocol) 416 defer func() { 417 vtgateconn.SetVTGateProtocol(oldValue) 418 }() 419 vtgateconn.RegisterDialer(protocol, func(context.Context, string) (vtgateconn.Impl, error) { 420 return &FakeVTGateConn{ 421 FakeVTGateConn: fakerpcvtgateconn.FakeVTGateConn{}, 422 }, nil 423 }) 424 _, tsv, db := newShortAgeExecutor(t) 425 defer db.Close() 426 defer tsv.StopService() 427 want := "aa" 428 db.AddQueryPattern( 429 "select dtid, time_created from _vt\\.dt_state where time_created.*", 430 &sqltypes.Result{ 431 Fields: []*querypb.Field{ 432 {Type: sqltypes.VarChar}, 433 {Type: sqltypes.Int64}, 434 }, 435 Rows: [][]sqltypes.Value{{ 436 sqltypes.NewVarBinary(want), 437 sqltypes.NewVarBinary("1"), 438 }}, 439 }) 440 got := <-dtidCh 441 if got != want { 442 t.Errorf("ResolveTransaction: %s, want %s", got, want) 443 } 444 } 445 446 func TestNoTwopc(t *testing.T) { 447 txe, tsv, db := newNoTwopcExecutor(t) 448 defer db.Close() 449 defer tsv.StopService() 450 451 testcases := []struct { 452 desc string 453 fun func() error 454 }{{ 455 desc: "Prepare", 456 fun: func() error { return txe.Prepare(1, "aa") }, 457 }, { 458 desc: "CommitPrepared", 459 fun: func() error { return txe.CommitPrepared("aa") }, 460 }, { 461 desc: "RollbackPrepared", 462 fun: func() error { return txe.RollbackPrepared("aa", 1) }, 463 }, { 464 desc: "CreateTransaction", 465 fun: func() error { return txe.CreateTransaction("aa", nil) }, 466 }, { 467 desc: "StartCommit", 468 fun: func() error { return txe.StartCommit(1, "aa") }, 469 }, { 470 desc: "SetRollback", 471 fun: func() error { return txe.SetRollback("aa", 1) }, 472 }, { 473 desc: "ConcludeTransaction", 474 fun: func() error { return txe.ConcludeTransaction("aa") }, 475 }, { 476 desc: "ReadTransaction", 477 fun: func() error { 478 _, err := txe.ReadTransaction("aa") 479 return err 480 }, 481 }, { 482 desc: "ReadAllTransactions", 483 fun: func() error { 484 _, _, _, err := txe.ReadTwopcInflight() 485 return err 486 }, 487 }} 488 489 want := "2pc is not enabled" 490 for _, tc := range testcases { 491 err := tc.fun() 492 require.EqualError(t, err, want) 493 } 494 } 495 496 func newTestTxExecutor(t *testing.T) (txe *TxExecutor, tsv *TabletServer, db *fakesqldb.DB) { 497 db = setUpQueryExecutorTest(t) 498 logStats := tabletenv.NewLogStats(ctx, "TestTxExecutor") 499 tsv = newTestTabletServer(ctx, smallTxPool, db) 500 db.AddQueryPattern("insert into _vt\\.redo_state\\(dtid, state, time_created\\) values \\('aa', 1,.*", &sqltypes.Result{}) 501 db.AddQueryPattern("insert into _vt\\.redo_statement.*", &sqltypes.Result{}) 502 db.AddQuery("delete from _vt.redo_state where dtid = 'aa'", &sqltypes.Result{}) 503 db.AddQuery("delete from _vt.redo_statement where dtid = 'aa'", &sqltypes.Result{}) 504 db.AddQuery("update test_table set `name` = 2 where pk = 1 limit 10001", &sqltypes.Result{}) 505 return &TxExecutor{ 506 ctx: ctx, 507 logStats: logStats, 508 te: tsv.te, 509 }, tsv, db 510 } 511 512 // newShortAgeExecutor is same as newTestTxExecutor, but shorter transaction abandon age. 513 func newShortAgeExecutor(t *testing.T) (txe *TxExecutor, tsv *TabletServer, db *fakesqldb.DB) { 514 db = setUpQueryExecutorTest(t) 515 logStats := tabletenv.NewLogStats(ctx, "TestTxExecutor") 516 tsv = newTestTabletServer(ctx, smallTxPool|shortTwopcAge, db) 517 db.AddQueryPattern("insert into _vt\\.redo_state\\(dtid, state, time_created\\) values \\('aa', 1,.*", &sqltypes.Result{}) 518 db.AddQueryPattern("insert into _vt\\.redo_statement.*", &sqltypes.Result{}) 519 db.AddQuery("delete from _vt.redo_state where dtid = 'aa'", &sqltypes.Result{}) 520 db.AddQuery("delete from _vt.redo_statement where dtid = 'aa'", &sqltypes.Result{}) 521 db.AddQuery("update test_table set `name` = 2 where pk = 1 limit 10001", &sqltypes.Result{}) 522 return &TxExecutor{ 523 ctx: ctx, 524 logStats: logStats, 525 te: tsv.te, 526 }, tsv, db 527 } 528 529 // newNoTwopcExecutor is same as newTestTxExecutor, but 2pc disabled. 530 func newNoTwopcExecutor(t *testing.T) (txe *TxExecutor, tsv *TabletServer, db *fakesqldb.DB) { 531 db = setUpQueryExecutorTest(t) 532 logStats := tabletenv.NewLogStats(ctx, "TestTxExecutor") 533 tsv = newTestTabletServer(ctx, noTwopc, db) 534 return &TxExecutor{ 535 ctx: ctx, 536 logStats: logStats, 537 te: tsv.te, 538 }, tsv, db 539 } 540 541 // newTxForPrep creates a non-empty transaction. 542 func newTxForPrep(tsv *TabletServer) int64 { 543 txid := newTransaction(tsv, nil) 544 target := querypb.Target{TabletType: topodatapb.TabletType_PRIMARY} 545 _, err := tsv.Execute(ctx, &target, "update test_table set name = 2 where pk = 1", nil, txid, 0, nil) 546 if err != nil { 547 panic(err) 548 } 549 return txid 550 }