github.com/pingcap/br@v5.3.0-alpha.0.20220125034240-ec59c7b6ce30+incompatible/tests/br_key_locked/locker.go (about) 1 // Copyright 2019 PingCAP, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 // Test backup with key locked errors. 15 // 16 // This file is copied from pingcap/schrodinger-test#428 https://git.io/Je1md 17 18 package main 19 20 import ( 21 "bytes" 22 "context" 23 "encoding/json" 24 "flag" 25 "fmt" 26 "io" 27 "math/rand" 28 "net" 29 "net/http" 30 "time" 31 32 "github.com/pingcap/errors" 33 "github.com/pingcap/kvproto/pkg/kvrpcpb" 34 "github.com/pingcap/log" 35 "github.com/pingcap/parser/model" 36 "github.com/pingcap/tidb/config" 37 "github.com/pingcap/tidb/kv" 38 "github.com/pingcap/tidb/store/driver" 39 "github.com/pingcap/tidb/tablecodec" 40 "github.com/tikv/client-go/v2/oracle" 41 "github.com/tikv/client-go/v2/tikv" 42 "github.com/tikv/client-go/v2/tikvrpc" 43 pd "github.com/tikv/pd/client" 44 "go.uber.org/zap" 45 46 "github.com/pingcap/br/pkg/httputil" 47 "github.com/pingcap/br/pkg/task" 48 ) 49 50 var ( 51 ca = flag.String("ca", "", "CA certificate path for TLS connection") 52 cert = flag.String("cert", "", "certificate path for TLS connection") 53 key = flag.String("key", "", "private key path for TLS connection") 54 tidbStatusAddr = flag.String("tidb", "", "TiDB status address") 55 pdAddr = flag.String("pd", "", "PD address") 56 dbName = flag.String("db", "", "Database name") 57 tableName = flag.String("table", "", "Table name") 58 tableSize = flag.Int64("table-size", 10000, "Table size, row count") 59 timeout = flag.Duration("run-timeout", time.Second*10, "The total time it executes") 60 lockTTL = flag.Duration("lock-ttl", time.Second*10, "The TTL of locks") 61 ) 62 63 func main() { 64 flag.Parse() 65 if *tidbStatusAddr == "" { 66 log.Panic("tidb status address is empty") 67 } 68 if *pdAddr == "" { 69 log.Panic("pd address is empty") 70 } 71 if *dbName == "" { 72 log.Panic("database name is empty") 73 } 74 if *tableName == "" { 75 log.Panic("table name is empty") 76 } 77 78 ctx, cancel := context.WithTimeout(context.Background(), *timeout) 79 defer cancel() 80 http.DefaultClient.Timeout = *timeout 81 82 tableID, err := getTableID(ctx, *tidbStatusAddr, *dbName, *tableName) 83 if err != nil { 84 log.Panic("get table id failed", zap.Error(err)) 85 } 86 87 pdclient, err := pd.NewClient([]string{*pdAddr}, pd.SecurityOption{ 88 CAPath: *ca, 89 CertPath: *cert, 90 KeyPath: *key, 91 }) 92 if err != nil { 93 log.Panic("create pd client failed", zap.Error(err)) 94 } 95 pdcli := &codecPDClient{Client: pdclient} 96 97 if len(*ca) != 0 { 98 tidbCfg := config.NewConfig() 99 tidbCfg.Security.ClusterSSLCA = *ca 100 tidbCfg.Security.ClusterSSLCert = *cert 101 tidbCfg.Security.ClusterSSLKey = *key 102 config.StoreGlobalConfig(tidbCfg) 103 } 104 driver := driver.TiKVDriver{} 105 store, err := driver.Open(fmt.Sprintf("tikv://%s?disableGC=true", *pdAddr)) 106 if err != nil { 107 log.Panic("create tikv client failed", zap.Error(err)) 108 } 109 110 locker := Locker{ 111 tableID: tableID, 112 tableSize: *tableSize, 113 lockTTL: *lockTTL, 114 pdcli: pdcli, 115 kv: store.(tikv.Storage), 116 } 117 err = locker.generateLocks(ctx) 118 if err != nil { 119 log.Panic("generate locks failed", zap.Error(err)) 120 } 121 } 122 123 func newHTTPClient() *http.Client { 124 if len(*ca) != 0 { 125 tlsCfg := &task.TLSConfig{ 126 CA: *ca, 127 Cert: *cert, 128 Key: *key, 129 } 130 cfg, err := tlsCfg.ToTLSConfig() 131 if err != nil { 132 log.Panic("fail to parse TLS config", zap.Error(err)) 133 } 134 return httputil.NewClient(cfg) 135 } 136 return http.DefaultClient 137 } 138 139 // getTableID of the table with specified table name. 140 func getTableID(ctx context.Context, dbAddr, dbName, table string) (int64, error) { 141 dbHost, _, err := net.SplitHostPort(dbAddr) 142 if err != nil { 143 return 0, errors.Trace(err) 144 } 145 dbStatusAddr := net.JoinHostPort(dbHost, "10080") 146 url := fmt.Sprintf("https://%s/schema/%s/%s", dbStatusAddr, dbName, table) 147 148 client := newHTTPClient() 149 req, err := http.NewRequestWithContext(ctx, "GET", url, nil) 150 if err != nil { 151 return 0, errors.Trace(err) 152 } 153 resp, err := client.Do(req) 154 if err != nil { 155 return 0, errors.Trace(err) 156 } 157 defer resp.Body.Close() 158 159 body, err := io.ReadAll(resp.Body) 160 if err != nil { 161 return 0, errors.Trace(err) 162 } 163 164 if resp.StatusCode != 200 { 165 return 0, errors.Errorf("HTTP request to TiDB status reporter returns %v. Body: %v", resp.StatusCode, string(body)) 166 } 167 168 var data model.TableInfo 169 err = json.Unmarshal(body, &data) 170 if err != nil { 171 return 0, errors.Trace(err) 172 } 173 return data.ID, nil 174 } 175 176 // Locker leaves locks on a table. 177 type Locker struct { 178 tableID int64 179 tableSize int64 180 lockTTL time.Duration 181 182 pdcli pd.Client 183 kv tikv.Storage 184 } 185 186 // generateLocks sends Prewrite requests to TiKV to generate locks, without committing and rolling back. 187 func (c *Locker) generateLocks(pctx context.Context) error { 188 log.Info("genLock started") 189 190 const maxTxnSize = 1000 191 192 // How many keys should be in the next transaction. 193 nextTxnSize := rand.Intn(maxTxnSize) + 1 // 0 is not allowed. 194 195 // How many keys has been scanned since last time sending request. 196 scannedKeys := 0 197 var batch []int64 198 199 ctx, cancel := context.WithCancel(context.Background()) 200 defer cancel() 201 for rowID := int64(0); ; rowID = (rowID + 1) % c.tableSize { 202 select { 203 case <-pctx.Done(): 204 log.Info("genLock done") 205 return nil 206 default: 207 } 208 209 scannedKeys++ 210 211 // Randomly decide whether to lock current key. 212 lockThis := rand.Intn(2) == 0 213 214 if lockThis { 215 batch = append(batch, rowID) 216 217 if len(batch) >= nextTxnSize { 218 // The batch is large enough to start the transaction 219 err := c.lockKeys(ctx, batch) 220 if err != nil { 221 return errors.Annotate(err, "lock keys failed") 222 } 223 224 // Start the next loop 225 batch = batch[:0] 226 scannedKeys = 0 227 nextTxnSize = rand.Intn(maxTxnSize) + 1 228 } 229 } 230 } 231 } 232 233 func (c *Locker) lockKeys(ctx context.Context, rowIDs []int64) error { 234 keys := make([][]byte, 0, len(rowIDs)) 235 236 keyPrefix := tablecodec.GenTableRecordPrefix(c.tableID) 237 for _, rowID := range rowIDs { 238 key := tablecodec.EncodeRecordKey(keyPrefix, kv.IntHandle(rowID)) 239 keys = append(keys, key) 240 } 241 242 primary := keys[0] 243 244 for len(keys) > 0 { 245 lockedKeys, err := c.lockBatch(ctx, keys, primary) 246 if err != nil { 247 return errors.Trace(err) 248 } 249 keys = keys[lockedKeys:] 250 } 251 return nil 252 } 253 254 func (c *Locker) lockBatch(ctx context.Context, keys [][]byte, primary []byte) (int, error) { 255 const maxBatchSize = 16 * 1024 256 257 // TiKV client doesn't expose Prewrite interface directly. We need to manually locate the region and send the 258 // Prewrite requests. 259 260 bo := tikv.NewBackoffer(ctx, 20000) 261 for { 262 loc, err := c.kv.GetRegionCache().LocateKey(bo, keys[0]) 263 if err != nil { 264 return 0, errors.Trace(err) 265 } 266 267 // Get a timestamp to use as the startTs 268 physical, logical, err := c.pdcli.GetTS(ctx) 269 if err != nil { 270 return 0, errors.Trace(err) 271 } 272 startTS := oracle.ComposeTS(physical, logical) 273 274 // Pick a batch of keys and make up the mutations 275 var mutations []*kvrpcpb.Mutation 276 batchSize := 0 277 278 for _, key := range keys { 279 if len(loc.EndKey) > 0 && bytes.Compare(key, loc.EndKey) >= 0 { 280 break 281 } 282 if bytes.Compare(key, loc.StartKey) < 0 { 283 break 284 } 285 286 value := randStr() 287 mutations = append(mutations, &kvrpcpb.Mutation{ 288 Op: kvrpcpb.Op_Put, 289 Key: key, 290 Value: []byte(value), 291 }) 292 batchSize += len(key) + len(value) 293 294 if batchSize >= maxBatchSize { 295 break 296 } 297 } 298 299 lockedKeys := len(mutations) 300 if lockedKeys == 0 { 301 return 0, nil 302 } 303 304 prewrite := &kvrpcpb.PrewriteRequest{ 305 Mutations: mutations, 306 PrimaryLock: primary, 307 StartVersion: startTS, 308 LockTtl: uint64(c.lockTTL.Milliseconds()), 309 } 310 req := tikvrpc.NewRequest(tikvrpc.CmdPrewrite, prewrite) 311 312 // Send the requests 313 resp, err := c.kv.SendReq(bo, req, loc.Region, time.Second*20) 314 if err != nil { 315 return 0, errors.Annotatef( 316 err, 317 "send request failed. region: %+v [%+q, %+q), keys: %+q", 318 loc.Region, loc.StartKey, loc.EndKey, keys[0:lockedKeys]) 319 } 320 regionErr, err := resp.GetRegionError() 321 if err != nil { 322 return 0, errors.Trace(err) 323 } 324 if regionErr != nil { 325 err = bo.Backoff(tikv.BoRegionMiss(), errors.New(regionErr.String())) 326 if err != nil { 327 return 0, errors.Trace(err) 328 } 329 continue 330 } 331 332 prewriteResp := resp.Resp 333 if prewriteResp == nil { 334 return 0, errors.Errorf("response body missing") 335 } 336 337 // Ignore key errors since we never commit the transaction and we don't need to keep consistency here. 338 return lockedKeys, nil 339 } 340 } 341 342 func randStr() string { 343 length := rand.Intn(128) 344 res := "" 345 for i := 0; i < length; i++ { 346 res += fmt.Sprint(rand.Intn(10)) 347 } 348 return res 349 }