github.com/pingcap/chaos@v0.0.0-20190710112158-c86faf4b3719/db/tidb/sequential.go (about) 1 package tidb 2 3 import ( 4 "context" 5 "database/sql" 6 "encoding/json" 7 "fmt" 8 "hash/fnv" 9 "log" 10 "math/rand" 11 "sync" 12 "time" 13 14 "github.com/pingcap/chaos/pkg/generator" 15 "github.com/pingcap/chaos/pkg/history" 16 17 "github.com/pingcap/chaos/pkg/core" 18 ) 19 20 const ( 21 tablePrefix = "seq_" 22 tpRead = "read" 23 tpWrite = "write" 24 ) 25 26 type seqRequest struct { 27 Tp string 28 K int 29 } 30 31 type seqResponse struct { 32 Ok bool 33 K int 34 V []string 35 Unknown bool 36 } 37 38 var ( 39 _ core.UnknownResponse = (*seqResponse)(nil) 40 41 queue = struct { 42 mu sync.Mutex 43 q []int 44 count int 45 r *rand.Rand 46 }{ 47 mu: sync.Mutex{}, 48 q: make([]int, 0, 10), // TODO: make the cap configurable. 49 r: rand.New(rand.NewSource(time.Now().UnixNano())), 50 } 51 ) 52 53 // IsUnknown implements UnknownResponse interface 54 func (r seqResponse) IsUnknown() bool { 55 return r.Unknown 56 } 57 58 type sequentialClient struct { 59 db *sql.DB 60 tableCount int 61 keyCount int 62 gen generator.Generator 63 } 64 65 // SequentialClientCreator creates a bank test client for tidb. 66 type SequentialClientCreator struct { 67 } 68 69 func genRequest() interface{} { 70 queue.mu.Lock() 71 defer queue.mu.Unlock() 72 c := cap(queue.q) 73 l := len(queue.q) 74 k := queue.count 75 // The first len(queue.q) requests are all write requests. 76 if k < c { 77 goto WRITE 78 } else if l == c { 79 // Read if the queue is full 80 goto READ 81 } else if l <= 3 { 82 // Write if the queue is empty 83 goto WRITE 84 } else if queue.r.Int()%2 == 1 { 85 goto READ 86 } else { 87 goto WRITE 88 } 89 90 READ: 91 // Read 92 // Pop the front 93 k, queue.q = queue.q[0], append([]int{}, queue.q[1:]...) 94 return seqRequest{Tp: tpRead, K: k} 95 WRITE: 96 // Write 97 // Push the end 98 queue.count++ 99 queue.q = append(queue.q, k) 100 return seqRequest{Tp: tpWrite, K: k} 101 } 102 103 // Create creates a new SequentialClient. 104 func (SequentialClientCreator) Create(node string) core.Client { 105 return &sequentialClient{ 106 tableCount: 3, 107 keyCount: 5, 108 gen: generator.Stagger(time.Millisecond*10, genRequest), 109 } 110 } 111 112 func (c *sequentialClient) tableNames() []string { 113 names := make([]string, 0, c.tableCount) 114 for i := 0; i < c.tableCount; i++ { 115 names = append(names, fmt.Sprintf("%s%d", tablePrefix, i)) 116 } 117 return names 118 } 119 120 func subkeys(keyCount int, k int) []string { 121 ks := make([]string, 0, keyCount) 122 for i := 0; i < keyCount; i++ { 123 ks = append(ks, fmt.Sprintf("%d_%d", k, i)) 124 } 125 return ks 126 } 127 128 func hash(s string) int { 129 h := fnv.New32a() 130 h.Write([]byte(s)) 131 return int(h.Sum32()) 132 } 133 134 func key2table(tableCount int, key string) string { 135 return fmt.Sprintf("%s%d", tablePrefix, hash(key)%tableCount) 136 } 137 138 // SetUp sets up the client. 139 func (c *sequentialClient) SetUp(ctx context.Context, nodes []string, node string) error { 140 db, err := sql.Open("mysql", fmt.Sprintf("root@tcp(%s:4000)/test", node)) 141 if err != nil { 142 return err 143 } 144 c.db = db 145 146 db.SetMaxIdleConns(1 + c.tableCount) 147 148 // Do SetUp in the first node 149 if node != nodes[0] { 150 return nil 151 } 152 153 tableNames := c.tableNames() 154 log.Printf("begin to create table %v on node %s", tableNames, node) 155 for _, tableName := range tableNames { 156 log.Printf("try to drop table %s", tableName) 157 if _, err = db.ExecContext(ctx, 158 fmt.Sprintf(" drop table if exists %s", tableName)); err != nil { 159 return err 160 } 161 sql := `create table if not exists %s (tkey varchar(255) primary key)` 162 if _, err = db.ExecContext(ctx, fmt.Sprintf(sql, tableName)); err != nil { 163 return err 164 } 165 log.Printf("created table %s", tableName) 166 } 167 168 return nil 169 } 170 171 // TearDown tears down the client. 172 func (c *sequentialClient) TearDown(ctx context.Context, nodes []string, node string) error { 173 return c.db.Close() 174 } 175 176 // Invoke invokes a request to the database. 177 // Mostly, the return Response should implement UnknownResponse interface 178 func (c *sequentialClient) Invoke(ctx context.Context, node string, r interface{}) interface{} { 179 req := r.(seqRequest) 180 ks := subkeys(c.keyCount, req.K) 181 if req.Tp == tpWrite { 182 for _, k := range ks { 183 sql := fmt.Sprintf("insert into %s values (?)", key2table(c.tableCount, k)) 184 _, err := c.db.ExecContext(ctx, sql, k) 185 if err != nil { 186 // TODO: retry on conflict 187 log.Println(err) 188 return seqResponse{Ok: false} 189 } 190 } 191 return seqResponse{Ok: true} 192 } else if req.Tp == tpRead { 193 vs := make([]string, 0, len(ks)) 194 for i := len(ks) - 1; i >= 0; i-- { 195 k := ks[i] 196 sql := fmt.Sprintf("select tkey from %s where tkey = ?", key2table(c.tableCount, k)) 197 var v string 198 err := c.db.QueryRowContext(ctx, sql, k).Scan(&v) 199 if err != nil { 200 // TODO: retry on conflict 201 log.Println(err) 202 return seqResponse{Ok: false} 203 } 204 vs = append(vs, v) 205 } 206 resp := seqResponse{ 207 Ok: true, 208 K: req.K, 209 V: vs, 210 } 211 return resp 212 } else { 213 panic(fmt.Sprintf("unknown req %v", req)) 214 } 215 } 216 217 // NextRequest generates a request for latter Invoke. 218 func (c *sequentialClient) NextRequest() interface{} { 219 return c.gen() 220 } 221 222 // DumpState the database state(also the model's state) 223 func (c *sequentialClient) DumpState(ctx context.Context) (interface{}, error) { 224 return nil, nil 225 } 226 227 // NewSequentialChecker returns a new sequentialChecker. 228 func NewSequentialChecker() core.Checker { 229 return sequentialChecker{} 230 } 231 232 // sequentialChecker checks whether a history is sequential. 233 type sequentialChecker struct{} 234 235 // Check checks the sequential history. 236 func (sequentialChecker) Check(_ core.Model, ops []core.Operation) (bool, error) { 237 for _, op := range ops { 238 if op.Action == core.ReturnOperation { 239 resp := op.Data.(seqResponse) 240 if !resp.Unknown && resp.Ok { 241 foundNoneEmpty := false 242 for _, s := range resp.V { 243 if !foundNoneEmpty { 244 foundNoneEmpty = s != "" 245 } else if s == "" { 246 log.Printf("Find no sequential op %+v", resp) 247 return false, nil 248 } 249 } 250 } 251 } 252 } 253 return true, nil 254 } 255 256 // Name returns the name of the verifier. 257 func (sequentialChecker) Name() string { 258 return "sequential_checker" 259 } 260 261 type parser struct{} 262 263 // OnRequest impls history.RecordParser.OnRequest 264 func (p parser) OnRequest(data json.RawMessage) (interface{}, error) { 265 r := seqRequest{} 266 err := json.Unmarshal(data, &r) 267 return r, err 268 } 269 270 // OnResponse impls history.RecordParser.OnRequest 271 func (p parser) OnResponse(data json.RawMessage) (interface{}, error) { 272 r := seqResponse{} 273 err := json.Unmarshal(data, &r) 274 if r.Unknown { 275 return nil, err 276 } 277 return r, err 278 } 279 280 // OnNoopResponse impls history.RecordParser.OnRequest 281 func (p parser) OnNoopResponse() interface{} { 282 return seqResponse{Unknown: true} 283 } 284 285 func (p parser) OnState(data json.RawMessage) (interface{}, error) { 286 return nil, nil 287 } 288 289 // NewSequentialParser returns a parser parses a history of bank operations. 290 func NewSequentialParser() history.RecordParser { 291 return parser{} 292 }