github.com/pingcap/chaos@v0.0.0-20190710112158-c86faf4b3719/db/tidb/multi_bank.go (about)

     1  package tidb
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"fmt"
     7  	"log"
     8  	"math/rand"
     9  	"time"
    10  
    11  	"github.com/pingcap/chaos/pkg/core"
    12  )
    13  
    14  type multiBankClient struct {
    15  	db         *sql.DB
    16  	r          *rand.Rand
    17  	accountNum int
    18  }
    19  
    20  func (c *multiBankClient) SetUp(ctx context.Context, nodes []string, node string) error {
    21  	c.r = rand.New(rand.NewSource(time.Now().UnixNano()))
    22  	db, err := sql.Open("mysql", fmt.Sprintf("root@tcp(%s:4000)/test", node))
    23  	if err != nil {
    24  		return err
    25  	}
    26  	c.db = db
    27  
    28  	db.SetMaxIdleConns(1 + c.accountNum)
    29  
    30  	// Do SetUp in the first node
    31  	if node != nodes[0] {
    32  		return nil
    33  	}
    34  
    35  	log.Printf("begin to create table accounts on node %s", node)
    36  
    37  	for i := 0; i < c.accountNum; i++ {
    38  		sql := fmt.Sprintf(`create table if not exists accounts_%d
    39  			(id     int not null primary key,
    40  			balance bigint not null)`, i)
    41  
    42  		if _, err = db.ExecContext(ctx, sql); err != nil {
    43  			return err
    44  		}
    45  
    46  		sql = fmt.Sprintf("insert into accounts_%d values (?, ?)", i)
    47  		if _, err = db.ExecContext(ctx, sql, i, initBalance); err != nil {
    48  			return err
    49  		}
    50  
    51  	}
    52  
    53  	return nil
    54  }
    55  
    56  func (c *multiBankClient) TearDown(ctx context.Context, nodes []string, node string) error {
    57  	return c.db.Close()
    58  }
    59  
    60  func (c *multiBankClient) invokeRead(ctx context.Context, r bankRequest) bankResponse {
    61  	txn, err := c.db.Begin()
    62  
    63  	if err != nil {
    64  		return bankResponse{Unknown: true}
    65  	}
    66  	defer txn.Rollback()
    67  
    68  	var tso uint64
    69  	if err = txn.QueryRow("select @@tidb_current_ts").Scan(&tso); err != nil {
    70  		return bankResponse{Unknown: true}
    71  	}
    72  
    73  	balances := make([]int64, 0, c.accountNum)
    74  	for i := 0; i < c.accountNum; i++ {
    75  		var balance int64
    76  		sql := fmt.Sprintf("select balance from accounts_%d", i)
    77  		if err = txn.QueryRowContext(ctx, sql).Scan(&balance); err != nil {
    78  			return bankResponse{Unknown: true}
    79  		}
    80  		balances = append(balances, balance)
    81  	}
    82  
    83  	return bankResponse{Balances: balances, Tso: tso}
    84  }
    85  
    86  func (c *multiBankClient) Invoke(ctx context.Context, node string, r interface{}) interface{} {
    87  	arg := r.(bankRequest)
    88  	if arg.Op == 0 {
    89  		return c.invokeRead(ctx, arg)
    90  	}
    91  
    92  	txn, err := c.db.Begin()
    93  
    94  	if err != nil {
    95  		return bankResponse{Ok: false}
    96  	}
    97  	defer txn.Rollback()
    98  
    99  	var (
   100  		fromBalance int64
   101  		toBalance   int64
   102  		tso         uint64
   103  	)
   104  
   105  	if err = txn.QueryRow("select @@tidb_current_ts").Scan(&tso); err != nil {
   106  		return bankResponse{Ok: false}
   107  	}
   108  
   109  	if err = txn.QueryRowContext(ctx, fmt.Sprintf("select balance from accounts_%d where id = ? for update", arg.From), arg.From).Scan(&fromBalance); err != nil {
   110  		return bankResponse{Ok: false}
   111  	}
   112  
   113  	if err = txn.QueryRowContext(ctx, fmt.Sprintf("select balance from accounts_%d where id = ? for update", arg.To), arg.To).Scan(&toBalance); err != nil {
   114  		return bankResponse{Ok: false}
   115  	}
   116  
   117  	if fromBalance < arg.Amount {
   118  		return bankResponse{Ok: false}
   119  	}
   120  
   121  	if _, err = txn.ExecContext(ctx, fmt.Sprintf("update accounts_%d set balance = balance - ? where id = ?", arg.From), arg.Amount, arg.From); err != nil {
   122  		return bankResponse{Ok: false}
   123  	}
   124  
   125  	if _, err = txn.ExecContext(ctx, fmt.Sprintf("update accounts_%d set balance = balance + ? where id = ?", arg.To), arg.Amount, arg.To); err != nil {
   126  		return bankResponse{Ok: false}
   127  	}
   128  
   129  	if err = txn.Commit(); err != nil {
   130  		return bankResponse{Unknown: true, Tso: tso, FromBalance: fromBalance, ToBalance: toBalance}
   131  	}
   132  
   133  	return bankResponse{Ok: true, Tso: tso, FromBalance: fromBalance, ToBalance: toBalance}
   134  }
   135  
   136  func (c *multiBankClient) NextRequest() interface{} {
   137  	r := bankRequest{
   138  		Op: c.r.Int() % 2,
   139  	}
   140  	if r.Op == 0 {
   141  		return r
   142  	}
   143  
   144  	r.From = c.r.Intn(c.accountNum)
   145  
   146  	r.To = c.r.Intn(c.accountNum)
   147  	if r.From == r.To {
   148  		r.To = (r.To + 1) % c.accountNum
   149  	}
   150  
   151  	r.Amount = 5
   152  	return r
   153  }
   154  
   155  // DumpState the database state(also the model's state)
   156  func (c *multiBankClient) DumpState(ctx context.Context) (interface{}, error) {
   157  	txn, err := c.db.Begin()
   158  
   159  	if err != nil {
   160  		return nil, err
   161  	}
   162  	defer txn.Rollback()
   163  
   164  	balances := make([]int64, 0, c.accountNum)
   165  	for i := 0; i < c.accountNum; i++ {
   166  		var balance int64
   167  		sql := fmt.Sprintf("select balance from accounts_%d", i)
   168  		if err = txn.QueryRowContext(ctx, sql).Scan(&balance); err != nil {
   169  			return nil, err
   170  		}
   171  		balances = append(balances, balance)
   172  	}
   173  	return balances, nil
   174  }
   175  
   176  // MultiBankClientCreator creates a bank test client for tidb.
   177  type MultiBankClientCreator struct {
   178  }
   179  
   180  // Create creates a client.
   181  func (MultiBankClientCreator) Create(node string) core.Client {
   182  	return &multiBankClient{
   183  		accountNum: accountNum,
   184  	}
   185  }