github.com/pingcap/br@v5.3.0-alpha.0.20220125034240-ec59c7b6ce30+incompatible/pkg/mock/mock_cluster.go (about)

     1  // Copyright 2020 PingCAP, Inc. Licensed under Apache-2.0.
     2  
     3  package mock
     4  
     5  import (
     6  	"database/sql"
     7  	"fmt"
     8  	"io"
     9  	"net/http"
    10  	"net/http/pprof"
    11  	"net/url"
    12  	"strconv"
    13  	"strings"
    14  	"sync"
    15  	"time"
    16  
    17  	"github.com/go-sql-driver/mysql"
    18  	"github.com/pingcap/errors"
    19  	"github.com/pingcap/log"
    20  	"github.com/pingcap/tidb/config"
    21  	"github.com/pingcap/tidb/domain"
    22  	"github.com/pingcap/tidb/kv"
    23  	"github.com/pingcap/tidb/server"
    24  	"github.com/pingcap/tidb/session"
    25  	"github.com/pingcap/tidb/store/mockstore"
    26  	"github.com/tikv/client-go/v2/testutils"
    27  	"github.com/tikv/client-go/v2/tikv"
    28  	pd "github.com/tikv/pd/client"
    29  	"github.com/tikv/pd/pkg/tempurl"
    30  	"go.uber.org/zap"
    31  )
    32  
    33  var pprofOnce sync.Once
    34  
    35  // Cluster is mock tidb cluster, includes tikv and pd.
    36  type Cluster struct {
    37  	*server.Server
    38  	testutils.Cluster
    39  	kv.Storage
    40  	*server.TiDBDriver
    41  	*domain.Domain
    42  	DSN      string
    43  	PDClient pd.Client
    44  }
    45  
    46  // NewCluster create a new mock cluster.
    47  func NewCluster() (*Cluster, error) {
    48  	pprofOnce.Do(func() {
    49  		go func() {
    50  			// Make sure pprof is registered.
    51  			_ = pprof.Handler
    52  			addr := "0.0.0.0:12235"
    53  			log.Info("start pprof", zap.String("addr", addr))
    54  			if e := http.ListenAndServe(addr, nil); e != nil {
    55  				log.Warn("fail to start pprof", zap.String("addr", addr), zap.Error(e))
    56  			}
    57  		}()
    58  	})
    59  
    60  	var mockCluster testutils.Cluster
    61  	storage, err := mockstore.NewMockStore(
    62  		mockstore.WithClusterInspector(func(c testutils.Cluster) {
    63  			mockstore.BootstrapWithSingleStore(c)
    64  			mockCluster = c
    65  		}),
    66  	)
    67  	if err != nil {
    68  		return nil, errors.Trace(err)
    69  	}
    70  	session.SetSchemaLease(0)
    71  	session.DisableStats4Test()
    72  	dom, err := session.BootstrapSession(storage)
    73  	if err != nil {
    74  		return nil, errors.Trace(err)
    75  	}
    76  	return &Cluster{
    77  		Storage:  storage,
    78  		Cluster:  mockCluster,
    79  		Domain:   dom,
    80  		PDClient: storage.(tikv.Storage).GetRegionCache().PDClient(),
    81  	}, nil
    82  }
    83  
    84  // Start runs a mock cluster.
    85  func (mock *Cluster) Start() error {
    86  	statusURL, err := url.Parse(tempurl.Alloc())
    87  	if err != nil {
    88  		return errors.Trace(err)
    89  	}
    90  	statusPort, err := strconv.ParseInt(statusURL.Port(), 10, 32)
    91  	if err != nil {
    92  		return errors.Trace(err)
    93  	}
    94  
    95  	addrURL, err := url.Parse(tempurl.Alloc())
    96  	if err != nil {
    97  		return errors.Trace(err)
    98  	}
    99  	addrPort, err := strconv.ParseInt(addrURL.Port(), 10, 32)
   100  	if err != nil {
   101  		return errors.Trace(err)
   102  	}
   103  	_ = addrPort
   104  
   105  	mock.TiDBDriver = server.NewTiDBDriver(mock.Storage)
   106  	cfg := config.NewConfig()
   107  	cfg.Port = uint(addrPort)
   108  	cfg.Store = "tikv"
   109  	cfg.Status.StatusPort = uint(statusPort)
   110  	cfg.Status.ReportStatus = true
   111  
   112  	svr, err := server.NewServer(cfg, mock.TiDBDriver)
   113  	if err != nil {
   114  		return errors.Trace(err)
   115  	}
   116  	mock.Server = svr
   117  	go func() {
   118  		if err1 := svr.Run(); err != nil {
   119  			panic(err1)
   120  		}
   121  	}()
   122  	mock.DSN = waitUntilServerOnline(addrURL.Host, cfg.Status.StatusPort)
   123  	return nil
   124  }
   125  
   126  // Stop stops a mock cluster.
   127  func (mock *Cluster) Stop() {
   128  	if mock.Domain != nil {
   129  		mock.Domain.Close()
   130  	}
   131  	if mock.Storage != nil {
   132  		mock.Storage.Close()
   133  	}
   134  	if mock.Server != nil {
   135  		mock.Server.Close()
   136  	}
   137  }
   138  
   139  type configOverrider func(*mysql.Config)
   140  
   141  const retryTime = 100
   142  
   143  var defaultDSNConfig = mysql.Config{
   144  	User: "root",
   145  	Net:  "tcp",
   146  	Addr: "127.0.0.1:4001",
   147  }
   148  
   149  // getDSN generates a DSN string for MySQL connection.
   150  func getDSN(overriders ...configOverrider) string {
   151  	cfg := defaultDSNConfig
   152  	for _, overrider := range overriders {
   153  		if overrider != nil {
   154  			overrider(&cfg)
   155  		}
   156  	}
   157  	return cfg.FormatDSN()
   158  }
   159  
   160  func waitUntilServerOnline(addr string, statusPort uint) string {
   161  	// connect server
   162  	retry := 0
   163  	dsn := getDSN(func(cfg *mysql.Config) {
   164  		cfg.Addr = addr
   165  	})
   166  	for ; retry < retryTime; retry++ {
   167  		time.Sleep(time.Millisecond * 10)
   168  		db, err := sql.Open("mysql", dsn)
   169  		if err == nil {
   170  			db.Close()
   171  			break
   172  		}
   173  	}
   174  	if retry == retryTime {
   175  		log.Panic("failed to connect DB in every 10 ms", zap.Int("retryTime", retryTime))
   176  	}
   177  	// connect http status
   178  	statusURL := fmt.Sprintf("http://127.0.0.1:%d/status", statusPort)
   179  	for retry = 0; retry < retryTime; retry++ {
   180  		resp, err := http.Get(statusURL) // nolint:noctx
   181  		if err == nil {
   182  			// Ignore errors.
   183  			_, _ = io.ReadAll(resp.Body)
   184  			_ = resp.Body.Close()
   185  			break
   186  		}
   187  		time.Sleep(time.Millisecond * 10)
   188  	}
   189  	if retry == retryTime {
   190  		log.Panic("failed to connect HTTP status in every 10 ms",
   191  			zap.Int("retryTime", retryTime))
   192  	}
   193  	return strings.SplitAfter(dsn, "/")[0]
   194  }