github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/pingcap/go-themis/txn_test.go (about)

     1  package themis
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/insionng/yougam/libraries/ngaut/log"
    10  	. "github.com/insionng/yougam/libraries/pingcap/check"
    11  	"github.com/insionng/yougam/libraries/pingcap/go-hbase"
    12  )
    13  
    14  type TransactionTestSuit struct {
    15  	cli hbase.HBaseClient
    16  }
    17  
    18  var _ = Suite(&TransactionTestSuit{})
    19  
    20  func Test(t *testing.T) { TestingT(t) }
    21  
    22  func (s *TransactionTestSuit) SetUpSuite(c *C) {
    23  	var err error
    24  	s.cli, err = createHBaseClient()
    25  	c.Assert(err, Equals, nil)
    26  
    27  	log.Warn("new test, reset tables")
    28  	err = createNewTableAndDropOldTable(s.cli, themisTestTableName, string(cf), nil)
    29  	c.Assert(err, IsNil)
    30  }
    31  
    32  func (s *TransactionTestSuit) SetUpTest(c *C) {
    33  }
    34  
    35  func getTestRowKey(c *C) []byte {
    36  	return []byte("test_row_" + c.TestName())
    37  }
    38  
    39  func (s *TransactionTestSuit) TestAsyncCommit(c *C) {
    40  	conf := defaultTxnConf
    41  	conf.brokenCommitSecondaryTest = true
    42  
    43  	tx := newTxn(s.cli, conf)
    44  	// simulating broken commit
    45  	for i := 0; i < 10; i++ {
    46  		p := hbase.NewPut([]byte(fmt.Sprintf("test_%d", i)))
    47  		p.AddValue(cf, q, []byte(fmt.Sprintf("%d", tx.(*themisTxn).GetStartTS())))
    48  		tx.Put(themisTestTableName, p)
    49  	}
    50  	err := tx.Commit()
    51  	c.Assert(err, Equals, nil)
    52  
    53  	//  wait until lock expired.
    54  	log.Warn("Wait for lock expired. Sleep...")
    55  	tick := 6
    56  	for tick > 0 {
    57  		time.Sleep(1 * time.Second)
    58  		tick--
    59  		log.Infof("remain %ds...", tick)
    60  	}
    61  
    62  	log.Warn("Try commit again")
    63  	// new transction will not see lock
    64  	for {
    65  		tx = newTxn(s.cli, defaultTxnConf)
    66  		for i := 0; i < 5; i++ {
    67  			p := hbase.NewPut([]byte(fmt.Sprintf("test_%d", i)))
    68  			p.AddValue(cf, q, []byte(fmt.Sprintf("%d", tx.(*themisTxn).GetStartTS())))
    69  			tx.Put(themisTestTableName, p)
    70  		}
    71  		err = tx.Commit()
    72  		if err == nil || !errorEqual(err, ErrRetryable) {
    73  			break
    74  		}
    75  		time.Sleep(100 * time.Millisecond)
    76  	}
    77  	c.Assert(err, Equals, nil)
    78  
    79  	for {
    80  		tx = newTxn(s.cli, defaultTxnConf)
    81  		for i := 5; i < 10; i++ {
    82  			p := hbase.NewPut([]byte(fmt.Sprintf("test_%d", i)))
    83  			p.AddValue(cf, q, []byte(fmt.Sprintf("%d", tx.(*themisTxn).GetStartTS())))
    84  			tx.Put(themisTestTableName, p)
    85  		}
    86  		err = tx.Commit()
    87  		if err == nil || !errorEqual(err, ErrRetryable) {
    88  			break
    89  		}
    90  		time.Sleep(100 * time.Millisecond)
    91  	}
    92  	c.Assert(err, Equals, nil)
    93  }
    94  
    95  func (s *TransactionTestSuit) TestBrokenPrewriteSecondary(c *C) {
    96  	tx := newTxn(s.cli, defaultTxnConf)
    97  	ts := tx.(*themisTxn).GetStartTS()
    98  	// simulating broken commit
    99  	for i := 0; i < 10; i++ {
   100  		p := hbase.NewPut([]byte(fmt.Sprintf("test_%d", i)))
   101  		p.AddValue(cf, q, []byte(fmt.Sprintf("%d", ts)))
   102  		tx.Put(themisTestTableName, p)
   103  	}
   104  	err := tx.Commit()
   105  	c.Assert(err, IsNil)
   106  
   107  	// TODO: check rallback & cleanup locks
   108  	conf := defaultTxnConf
   109  	conf.brokenPrewriteSecondaryTest = true
   110  
   111  	tx = newTxn(s.cli, conf)
   112  	ts = tx.GetStartTS()
   113  	// simulating broken commit
   114  	for i := 0; i < 10; i++ {
   115  		p := hbase.NewPut([]byte(fmt.Sprintf("test_%d", i)))
   116  		p.AddValue(cf, q, []byte(fmt.Sprintf("%d", ts)))
   117  		tx.Put(themisTestTableName, p)
   118  	}
   119  	err = tx.Commit()
   120  	c.Assert(err, NotNil)
   121  
   122  	// check if locks are cleaned successfully
   123  	tx = newTxn(s.cli, defaultTxnConf)
   124  	for i := 0; i < 10; i++ {
   125  		g := hbase.NewGet([]byte(fmt.Sprintf("test_%d", i)))
   126  		r, err := tx.Get(themisTestTableName, g)
   127  		c.Assert(err, Equals, nil)
   128  		c.Assert(r == nil || string(r.SortedColumns[0].Value) != fmt.Sprintf("%d", ts), Equals, true)
   129  	}
   130  }
   131  
   132  func (s *TransactionTestSuit) TestPrimaryLockTimeout(c *C) {
   133  	// TODO: check if lock can be cleaned up when secondary prewrite failed and
   134  	// rollback is also failed
   135  	conf := defaultTxnConf
   136  	conf.brokenPrewriteSecondaryTest = true
   137  	conf.brokenPrewriteSecondaryAndRollbackTest = true
   138  	tx := newTxn(s.cli, conf)
   139  	ts := tx.GetStartTS()
   140  	// simulating broken commit
   141  	for i := 0; i < 2; i++ {
   142  		p := hbase.NewPut([]byte(fmt.Sprintf("test_%d", i)))
   143  		p.AddValue(cf, q, []byte(fmt.Sprintf("%d", ts)))
   144  		tx.Put(themisTestTableName, p)
   145  	}
   146  	err := tx.Commit()
   147  	c.Assert(err, NotNil)
   148  	log.Error(err)
   149  
   150  	//  wait until lock expired.
   151  	log.Warn("Wait for lock expired. Sleep...")
   152  	tick := 6
   153  	for tick > 0 {
   154  		time.Sleep(1 * time.Second)
   155  		tick--
   156  		log.Infof("remain %ds...", tick)
   157  	}
   158  
   159  	// check if locks are cleaned successfully
   160  	tx = newTxn(s.cli, defaultTxnConf)
   161  	for i := 0; i < 2; i++ {
   162  		g := hbase.NewGet([]byte(fmt.Sprintf("test_%d", i)))
   163  		r, err := tx.Get(themisTestTableName, g)
   164  		c.Assert(err, Equals, nil)
   165  		// this commit must rollback
   166  		c.Assert(r == nil || string(r.SortedColumns[0].Value) != fmt.Sprintf("%d", ts), Equals, true)
   167  	}
   168  }
   169  
   170  func checkCommitSuccess(s *TransactionTestSuit, c *C, row []byte) {
   171  	tx := newTxn(s.cli, defaultTxnConf)
   172  	colMap := make(map[string]string)
   173  	colMap["#p:"+string(cf)+"#q"] = ""
   174  	colMap[string(cf)+":q"] = ""
   175  	r, err := tx.(*themisTxn).client.Get(themisTestTableName, hbase.NewGet(row))
   176  	c.Assert(err, Equals, nil)
   177  	c.Assert(2, Equals, len(r.Columns))
   178  	for _, v := range r.Columns {
   179  		_, exist := colMap[string(v.Family)+":"+string(v.Qual)]
   180  		c.Assert(exist, Equals, true)
   181  	}
   182  }
   183  
   184  func (s *TransactionTestSuit) TestLockRow(c *C) {
   185  	tx := newTxn(s.cli, defaultTxnConf)
   186  	row := []byte("lockRow")
   187  	put := hbase.NewPut(row)
   188  	put.AddValue(cf, q, []byte("v"))
   189  	tx.Put(themisTestTableName, put)
   190  	tx.Commit()
   191  
   192  	checkCommitSuccess(s, c, row)
   193  
   194  	tx = newTxn(s.cli, defaultTxnConf)
   195  	err := tx.LockRow(themisTestTableName, row)
   196  	c.Assert(err, Equals, nil)
   197  
   198  	tx.(*themisTxn).selectPrimaryAndSecondaries()
   199  	err = tx.(*themisTxn).prewritePrimary()
   200  	c.Assert(err, Equals, nil)
   201  	colMap := make(map[string]string)
   202  	colMap["#p:"+string(cf)+"#q"] = ""
   203  	colMap[string(cf)+":q"] = ""
   204  	colMap["L:"+string(cf)+"#q"] = ""
   205  	var r *hbase.ResultRow
   206  	r, err = tx.(*themisTxn).client.Get(themisTestTableName, hbase.NewGet(row))
   207  	c.Assert(err, Equals, nil)
   208  	c.Assert(3, Equals, len(r.Columns))
   209  	for _, v := range r.Columns {
   210  		_, exist := colMap[string(v.Family)+":"+string(v.Qual)]
   211  		c.Assert(exist, Equals, true)
   212  	}
   213  	tx.(*themisTxn).commitTs = tx.GetStartTS() + 1
   214  	tx.(*themisTxn).commitPrimary()
   215  	checkCommitSuccess(s, c, row)
   216  }
   217  
   218  func (s *TransactionTestSuit) TestBatchGet(c *C) {
   219  	batchGetTestTbl := "batch_get_test"
   220  	err := createNewTableAndDropOldTable(s.cli, batchGetTestTbl, string(cf), [][]byte{
   221  		// split in middle
   222  		[]byte("batch_test_5"),
   223  	})
   224  	defer dropTable(s.cli, batchGetTestTbl)
   225  	// prepare data
   226  	tx := newTxn(s.cli, defaultTxnConf)
   227  	for i := 0; i < 10; i++ {
   228  		p := hbase.NewPut([]byte(fmt.Sprintf("batch_test_%d", i))).AddValue(cf, q, []byte("v"))
   229  		tx.Put(batchGetTestTbl, p)
   230  	}
   231  	err = tx.Commit()
   232  	c.Assert(err, IsNil)
   233  
   234  	// batch get
   235  	var gets []*hbase.Get
   236  	for i := 0; i < 10; i++ {
   237  		g := hbase.NewGet([]byte(fmt.Sprintf("batch_test_%d", i))).AddColumn(cf, q)
   238  		gets = append(gets, g)
   239  	}
   240  	for i := 5; i < 10; i++ {
   241  		g := hbase.NewGet([]byte(fmt.Sprintf("batch_test_no_such_row_%d", i))).AddColumn(cf, q)
   242  		gets = append(gets, g)
   243  	}
   244  	tx = newTxn(s.cli, defaultTxnConf)
   245  	_, err = tx.Gets(batchGetTestTbl, gets)
   246  	c.Assert(isWrongRegionErr(err), Equals, true)
   247  
   248  	gets = nil
   249  	for i := 0; i < 5; i++ {
   250  		g := hbase.NewGet([]byte(fmt.Sprintf("batch_test_%d", i))).AddColumn(cf, q)
   251  		gets = append(gets, g)
   252  	}
   253  	tx = newTxn(s.cli, defaultTxnConf)
   254  	rs, err := tx.Gets(batchGetTestTbl, gets)
   255  	c.Assert(err, IsNil)
   256  	c.Assert(len(rs), Equals, 5)
   257  }
   258  
   259  func (s *TransactionTestSuit) TestBatchGetWithLocks(c *C) {
   260  	// simulating locks
   261  	conf := defaultTxnConf
   262  	conf.brokenCommitSecondaryTest = true
   263  
   264  	tx := newTxn(s.cli, conf)
   265  	ts := tx.GetStartTS()
   266  	// simulating broken commit
   267  	for i := 0; i < 10; i++ {
   268  		p := hbase.NewPut([]byte(fmt.Sprintf("batch_test_with_lock_%d", i)))
   269  		p.AddValue(cf, q, []byte(fmt.Sprintf("%d", ts)))
   270  		tx.Put(themisTestTableName, p)
   271  	}
   272  	tx.Commit()
   273  
   274  	tx = newTxn(s.cli, defaultTxnConf)
   275  
   276  	var gets []*hbase.Get
   277  	for i := 0; i < 10; i++ {
   278  		g := hbase.NewGet([]byte(fmt.Sprintf("batch_test_with_lock_%d", i))).AddColumn(cf, q)
   279  		gets = append(gets, g)
   280  	}
   281  	rs, err := tx.Gets(themisTestTableName, gets)
   282  	c.Assert(err, IsNil)
   283  	// we had already cleaned secondary locks
   284  	c.Assert(len(rs), Equals, 10)
   285  }
   286  
   287  func (s *TransactionTestSuit) TestAsyncSecondaryCommit(c *C) {
   288  	conf := defaultTxnConf
   289  	conf.brokenCommitSecondaryTest = true
   290  	tx := newTxn(s.cli, conf)
   291  	for i := 0; i < 10; i++ {
   292  		p := hbase.NewPut([]byte(fmt.Sprintf("async_commit_test_%d", i))).AddValue(cf, q, []byte(fmt.Sprintf("%d", tx.GetStartTS())))
   293  		tx.Put(themisTestTableName, p)
   294  	}
   295  	err := tx.Commit()
   296  	c.Assert(err, IsNil)
   297  
   298  	tx = newTxn(s.cli, conf)
   299  	for i := 0; i < 10; i++ {
   300  		g := hbase.NewGet([]byte(fmt.Sprintf("async_commit_test_%d", i)))
   301  		rs, err := tx.Get(themisTestTableName, g)
   302  		c.Assert(err, IsNil)
   303  		c.Assert(len(rs.SortedColumns), Greater, 0)
   304  	}
   305  }
   306  
   307  func (s *TransactionTestSuit) TestTTL(c *C) {
   308  	conf := defaultTxnConf
   309  	conf.brokenCommitPrimaryTest = true
   310  	tx := newTxn(s.cli, conf)
   311  	p := hbase.NewPut(getTestRowKey(c)).AddValue(cf, q, []byte("val"))
   312  	tx.Put(themisTestTableName, p)
   313  	tx.Commit()
   314  
   315  	startTs := time.Now()
   316  	conf = defaultTxnConf
   317  	conf.TTLInMs = 1000
   318  	tx = newTxn(s.cli, conf)
   319  	rs, err := tx.Get(themisTestTableName, hbase.NewGet(getTestRowKey(c)).AddColumn(cf, q))
   320  	c.Assert(time.Since(startTs).Seconds(), Greater, float64(1))
   321  	c.Assert(time.Since(startTs).Seconds(), Less, float64(1.5))
   322  	// transction timeout, alreay rolled back.
   323  	c.Assert(rs, IsNil)
   324  	c.Assert(err, IsNil)
   325  	tx.Commit()
   326  }
   327  
   328  type mockOracle struct {
   329  	tick uint64
   330  }
   331  
   332  func (o *mockOracle) GetTimestamp() (uint64, error) {
   333  	return o.tick, nil
   334  }
   335  
   336  func (o *mockOracle) IsExpired(beginMs uint64, TTL uint64) bool {
   337  	return false
   338  }
   339  
   340  func (s *TransactionTestSuit) TestPhantomRead(c *C) {
   341  	o := &mockOracle{}
   342  
   343  	conf := defaultTxnConf
   344  	conf.brokenCommitPrimaryTest = true
   345  	o.tick = 1
   346  	tx, _ := NewTxnWithConf(s.cli, conf, o)
   347  	p := hbase.NewPut(getTestRowKey(c)).AddValue(cf, q, []byte("val"))
   348  	o.tick = 3
   349  	tx.Put(themisTestTableName, p)
   350  	tx.Commit()
   351  
   352  	o.tick = 2
   353  	tx, _ = NewTxn(s.cli, o)
   354  	rs, err := tx.Get(themisTestTableName, hbase.NewGet(getTestRowKey(c)).AddColumn(cf, q))
   355  	c.Assert(err, NotNil)
   356  	c.Assert(rs, IsNil)
   357  }
   358  
   359  func (s *TransactionTestSuit) TestExceedMaxRows(c *C) {
   360  	conf := defaultTxnConf
   361  	tx := newTxn(s.cli, conf)
   362  	for i := 0; i < conf.MaxRowsInOneTxn+1; i++ {
   363  		tx.Put(themisTestTableName, hbase.NewPut([]byte(strconv.Itoa(i))).AddValue(cf, q, []byte("test")))
   364  	}
   365  	err := tx.Commit()
   366  	c.Assert(err, NotNil)
   367  }
   368  
   369  func (s *TransactionTestSuit) TestCheckCommitStatus(c *C) {
   370  	conf := defaultTxnConf
   371  	hook := newHook()
   372  	hook.beforeCommitSecondary = func(txn *themisTxn, ctx interface{}) (bool, interface{}, error) {
   373  		// add before commit secondary hook, just return, do not commit
   374  		// secondaries
   375  		log.Info("before commit secondary")
   376  		return false, nil, nil
   377  	}
   378  	tx := newTxn(s.cli, conf)
   379  	tx.(*themisTxn).setHook(hook)
   380  	for i := 0; i < 10; i++ {
   381  		tx.Put(themisTestTableName, hbase.NewPut([]byte(fmt.Sprintf("%s_%d", getTestRowKey(c), i))).AddValue(cf, q, []byte("test")))
   382  	}
   383  	tx.Commit()
   384  	commitTs := tx.(*themisTxn).commitTs
   385  
   386  	pRow := append([]byte(nil), tx.(*themisTxn).primaryRow.row...)
   387  	sRow := append([]byte(nil), tx.(*themisTxn).secondaryRows[0].row...)
   388  
   389  	for i := 0; i < 10; i++ {
   390  		conf = defaultTxnConf
   391  		tx = newTxn(s.cli, conf)
   392  		tx.Put(themisTestTableName, hbase.NewPut([]byte(pRow)).AddValue(cf, q, []byte("newVal")))
   393  		tx.Commit()
   394  	}
   395  
   396  	hook = newHook()
   397  	hook.beforePrewriteLockClean = func(txn *themisTxn, ctx interface{}) (bool, interface{}, error) {
   398  		lock := ctx.(Lock)
   399  		cc := lock.Primary().Coordinate()
   400  		exists, err := txn.lockCleaner.IsLockExists(lock.Coordinate(), 0, lock.Timestamp())
   401  		c.Assert(err, IsNil)
   402  		c.Assert(exists, Equals, true)
   403  		ts, err := txn.lockCleaner.GetCommitTimestamp(cc, lock.Timestamp())
   404  		c.Assert(err, IsNil)
   405  		c.Assert(ts, Equals, commitTs)
   406  		return true, nil, nil
   407  	}
   408  	tx = newTxn(s.cli, conf)
   409  	tx.(*themisTxn).setHook(hook)
   410  	tx.Put(themisTestTableName, hbase.NewPut([]byte(sRow)).AddValue(cf, q, []byte("newVal")))
   411  	tx.Commit()
   412  }
   413  
   414  func createChoosePrimaryRowHook(target string) *txnHook {
   415  	hook := newHook()
   416  	hook.afterChoosePrimaryAndSecondary = func(txn *themisTxn, ctx interface{}) (bool, interface{}, error) {
   417  		txn.secondary = nil
   418  		txn.secondaryRows = nil
   419  		for tblName, rowMutations := range txn.mutationCache.mutations {
   420  			for _, rowMutation := range rowMutations {
   421  				row := rowMutation.row
   422  				for i, mutation := range rowMutation.mutationList(true) {
   423  					colcord := hbase.NewColumnCoordinate([]byte(tblName), row, mutation.Family, mutation.Qual)
   424  					// set the first column as primary if primary is not set by user
   425  					if string(row) == target {
   426  						txn.primary = colcord
   427  						txn.primaryRowOffset = i
   428  						txn.primaryRow = rowMutation
   429  					} else {
   430  						txn.secondary = append(txn.secondary, colcord)
   431  					}
   432  				}
   433  				if string(row) != target {
   434  					txn.secondaryRows = append(txn.secondaryRows, rowMutation)
   435  				}
   436  			}
   437  		}
   438  		return true, nil, nil
   439  	}
   440  	return hook
   441  }
   442  
   443  // Fix https://yougam/libraries/pingcap/go-themis/issues/19
   444  func (s *TransactionTestSuit) TestPrewriteSecondaryMissingRows(c *C) {
   445  	conf := defaultTxnConf
   446  	hook := createChoosePrimaryRowHook("A")
   447  	hook.beforePrewriteSecondary = func(txn *themisTxn, ctx interface{}) (bool, interface{}, error) {
   448  		go func() {
   449  			hook2 := createChoosePrimaryRowHook("B")
   450  			hook2.onSecondaryOccursLock = func(txn *themisTxn, ctx interface{}) (bool, interface{}, error) {
   451  				log.Info("tx2 occurs secondary lock", ctx)
   452  				return true, nil, nil
   453  			}
   454  			tx2 := newTxn(s.cli, conf)
   455  			tx2.(*themisTxn).setHook(hook2)
   456  			tx2.Put(themisTestTableName, hbase.NewPut([]byte("A")).AddValue(cf, q, []byte("A")))
   457  			tx2.Put(themisTestTableName, hbase.NewPut([]byte("B")).AddValue(cf, q, []byte("B")))
   458  			tx2.Put(themisTestTableName, hbase.NewPut([]byte("C")).AddValue(cf, q, []byte("C")))
   459  			tx2.Commit()
   460  		}()
   461  		time.Sleep(500 * time.Millisecond)
   462  		return true, nil, nil
   463  	}
   464  
   465  	hook.onSecondaryOccursLock = func(txn *themisTxn, ctx interface{}) (bool, interface{}, error) {
   466  		log.Info("tx1", ctx)
   467  		return true, nil, nil
   468  	}
   469  
   470  	hook.onPrewriteRow = func(txn *themisTxn, ctx interface{}) (bool, interface{}, error) {
   471  		containPrimary := ctx.([]interface{})[1].(bool)
   472  		if !containPrimary {
   473  			rm := ctx.([]interface{})[0].(*rowMutation)
   474  			log.Info(string(rm.row))
   475  		}
   476  
   477  		return true, nil, nil
   478  	}
   479  
   480  	tx1 := newTxn(s.cli, conf)
   481  	tx1.(*themisTxn).setHook(hook)
   482  	tx1.Put(themisTestTableName, hbase.NewPut([]byte("A")).AddValue(cf, q, []byte("A")))
   483  	tx1.Put(themisTestTableName, hbase.NewPut([]byte("B")).AddValue(cf, q, []byte("B")))
   484  	tx1.Put(themisTestTableName, hbase.NewPut([]byte("C")).AddValue(cf, q, []byte("C")))
   485  	err := tx1.Commit()
   486  	c.Assert(err, IsNil)
   487  
   488  	tx3 := newTxn(s.cli, conf)
   489  	rs, err := tx3.Get(themisTestTableName, hbase.NewGet([]byte("C")).AddColumn(cf, q))
   490  	c.Assert(rs, NotNil)
   491  	c.Assert(err, IsNil)
   492  	tx3.Commit()
   493  }