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  }