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 }