vitess.io/vitess@v0.16.2/go/test/endtoend/vreplication/cluster_test.go (about)

     1  /*
     2  Copyright 2022 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package vreplication
    18  
    19  import (
    20  	"fmt"
    21  	"io"
    22  	"math/rand"
    23  	"net/http"
    24  	"os"
    25  	"os/exec"
    26  	"path"
    27  	"runtime"
    28  	"strings"
    29  	"sync"
    30  	"testing"
    31  	"time"
    32  
    33  	"vitess.io/vitess/go/vt/mysqlctl"
    34  	"vitess.io/vitess/go/vt/vtgate/planbuilder/plancontext"
    35  
    36  	"github.com/stretchr/testify/require"
    37  
    38  	"vitess.io/vitess/go/test/endtoend/cluster"
    39  	"vitess.io/vitess/go/vt/log"
    40  )
    41  
    42  var (
    43  	debugMode = false // set to true for local debugging: this uses the local env vtdataroot and does not teardown clusters
    44  
    45  	originalVtdataroot    string
    46  	vtdataroot            string
    47  	mainClusterConfig     *ClusterConfig
    48  	externalClusterConfig *ClusterConfig
    49  	extraVTGateArgs       = []string{"--tablet_refresh_interval", "10ms"}
    50  	extraVtctldArgs       = []string{"--remote_operation_timeout", "600s", "--topo_etcd_lease_ttl", "120"}
    51  	// This variable can be used within specific tests to alter vttablet behavior
    52  	extraVTTabletArgs = []string{}
    53  
    54  	parallelInsertWorkers = "--vreplication-parallel-insert-workers=4"
    55  )
    56  
    57  // ClusterConfig defines the parameters like ports, tmpDir, tablet types which uniquely define a vitess cluster
    58  type ClusterConfig struct {
    59  	charset              string
    60  	hostname             string
    61  	topoPort             int
    62  	vtctldPort           int
    63  	vtctldGrpcPort       int
    64  	vtdataroot           string
    65  	tmpDir               string
    66  	vtgatePort           int
    67  	vtgateGrpcPort       int
    68  	vtgateMySQLPort      int
    69  	vtgatePlannerVersion plancontext.PlannerVersion
    70  	tabletTypes          string
    71  	tabletPortBase       int
    72  	tabletGrpcPortBase   int
    73  	tabletMysqlPortBase  int
    74  	vtorcPort            int
    75  
    76  	vreplicationCompressGTID bool
    77  }
    78  
    79  // VitessCluster represents all components within the test cluster
    80  type VitessCluster struct {
    81  	ClusterConfig *ClusterConfig
    82  	Name          string
    83  	Cells         map[string]*Cell
    84  	Topo          *cluster.TopoProcess
    85  	Vtctld        *cluster.VtctldProcess
    86  	Vtctl         *cluster.VtctlProcess
    87  	VtctlClient   *cluster.VtctlClientProcess
    88  	VtctldClient  *cluster.VtctldClientProcess
    89  	VTOrcProcess  *cluster.VTOrcProcess
    90  }
    91  
    92  // Cell represents a Vitess cell within the test cluster
    93  type Cell struct {
    94  	Name      string
    95  	Keyspaces map[string]*Keyspace
    96  	Vtgates   []*cluster.VtgateProcess
    97  }
    98  
    99  // Keyspace represents a Vitess keyspace contained by a cell within the test cluster
   100  type Keyspace struct {
   101  	Name    string
   102  	Shards  map[string]*Shard
   103  	VSchema string
   104  	Schema  string
   105  }
   106  
   107  // Shard represents a Vitess shard in a keyspace
   108  type Shard struct {
   109  	Name      string
   110  	IsSharded bool
   111  	Tablets   map[string]*Tablet
   112  }
   113  
   114  // Tablet represents a vttablet within a shard
   115  type Tablet struct {
   116  	Name     string
   117  	Vttablet *cluster.VttabletProcess
   118  	DbServer *cluster.MysqlctlProcess
   119  }
   120  
   121  func setTempVtDataRoot() string {
   122  	dirSuffix := 100000 + rand.Intn(999999-100000) // 6 digits
   123  	if debugMode {
   124  		vtdataroot = originalVtdataroot
   125  	} else {
   126  		vtdataroot = path.Join(originalVtdataroot, fmt.Sprintf("vreple2e_%d", dirSuffix))
   127  	}
   128  	if _, err := os.Stat(vtdataroot); os.IsNotExist(err) {
   129  		os.Mkdir(vtdataroot, 0700)
   130  	}
   131  	_ = os.Setenv("VTDATAROOT", vtdataroot)
   132  	fmt.Printf("VTDATAROOT is %s\n", vtdataroot)
   133  	return vtdataroot
   134  }
   135  
   136  // StartVTOrc starts a VTOrc instance
   137  func (vc *VitessCluster) StartVTOrc() error {
   138  	// Start vtorc if not already running
   139  	if vc.VTOrcProcess != nil {
   140  		return nil
   141  	}
   142  	base := cluster.VtctlProcessInstance(vc.ClusterConfig.topoPort, vc.ClusterConfig.hostname)
   143  	base.Binary = "vtorc"
   144  	vtorcProcess := &cluster.VTOrcProcess{
   145  		VtctlProcess: *base,
   146  		LogDir:       vc.ClusterConfig.tmpDir,
   147  		Config:       cluster.VTOrcConfiguration{},
   148  		Port:         vc.ClusterConfig.vtorcPort,
   149  	}
   150  	err := vtorcProcess.Setup()
   151  	if err != nil {
   152  		log.Error(err.Error())
   153  		return err
   154  	}
   155  	vc.VTOrcProcess = vtorcProcess
   156  	return nil
   157  }
   158  
   159  // setVtMySQLRoot creates the root directory if it does not exist
   160  // and saves the directory in the VT_MYSQL_ROOT OS env var.
   161  // mysqlctl will then look for the mysql related binaries in the
   162  // ./bin, ./sbin, and ./libexec subdirectories of VT_MYSQL_ROOT.
   163  func setVtMySQLRoot(mysqlRoot string) error {
   164  	if _, err := os.Stat(mysqlRoot); os.IsNotExist(err) {
   165  		os.Mkdir(mysqlRoot, 0700)
   166  	}
   167  	err := os.Setenv("VT_MYSQL_ROOT", mysqlRoot)
   168  	if err != nil {
   169  		return err
   170  	}
   171  	fmt.Printf("VT_MYSQL_ROOT is %s\n", mysqlRoot)
   172  	return nil
   173  }
   174  
   175  // setDBFlavor sets the MYSQL_FLAVOR OS env var.
   176  // You should call this after calling setVtMySQLRoot() to ensure that the
   177  // correct flavor is used by mysqlctl based on the current mysqld version
   178  // in the path. If you don't do this then mysqlctl will use the incorrect
   179  // config/mycnf/<flavor>.cnf file and mysqld may fail to start.
   180  func setDBFlavor() error {
   181  	versionStr, err := mysqlctl.GetVersionString()
   182  	if err != nil {
   183  		return err
   184  	}
   185  	f, v, err := mysqlctl.ParseVersionString(versionStr)
   186  	if err != nil {
   187  		return err
   188  	}
   189  	flavor := fmt.Sprintf("%s%d%d", f, v.Major, v.Minor)
   190  	err = os.Setenv("MYSQL_FLAVOR", string(flavor))
   191  	if err != nil {
   192  		return err
   193  	}
   194  	fmt.Printf("MYSQL_FLAVOR is %s\n", string(flavor))
   195  	return nil
   196  }
   197  
   198  func unsetVtMySQLRoot() {
   199  	_ = os.Unsetenv("VT_MYSQL_ROOT")
   200  }
   201  
   202  func unsetDBFlavor() {
   203  	_ = os.Unsetenv("MYSQL_FLAVOR")
   204  }
   205  
   206  // getDBTypeVersionInUse checks the major DB version of the mysqld binary
   207  // that mysqlctl would currently use, e.g. 5.7 or 8.0 (in semantic versioning
   208  // this would be major.minor but in MySQL it's effectively the major version).
   209  func getDBTypeVersionInUse() (string, error) {
   210  	var dbTypeMajorVersion string
   211  	versionStr, err := mysqlctl.GetVersionString()
   212  	if err != nil {
   213  		return dbTypeMajorVersion, err
   214  	}
   215  	flavor, version, err := mysqlctl.ParseVersionString(versionStr)
   216  	if err != nil {
   217  		return dbTypeMajorVersion, err
   218  	}
   219  	majorVersion := fmt.Sprintf("%d.%d", version.Major, version.Minor)
   220  	if flavor == mysqlctl.FlavorMySQL || flavor == mysqlctl.FlavorPercona {
   221  		dbTypeMajorVersion = fmt.Sprintf("mysql-%s", majorVersion)
   222  	} else {
   223  		dbTypeMajorVersion = fmt.Sprintf("%s-%s", strings.ToLower(string(flavor)), majorVersion)
   224  	}
   225  	return dbTypeMajorVersion, nil
   226  }
   227  
   228  // downloadDBTypeVersion downloads a recent major version release build for the specified
   229  // DB type.
   230  // If the file already exists, it will not download it again.
   231  // The artifact will be downloaded and extracted in the specified path. So e.g. if you
   232  // pass /tmp as the path and 5.7 as the majorVersion, mysqld will be installed in:
   233  // /tmp/mysql-5.7/bin/mysqld
   234  // You should then call setVtMySQLRoot() and setDBFlavor() to ensure that this new
   235  // binary is used by mysqlctl along with the correct flavor specific config file.
   236  func downloadDBTypeVersion(dbType string, majorVersion string, path string) error {
   237  	client := http.Client{
   238  		Timeout: 10 * time.Minute,
   239  	}
   240  	var url, file, versionFile string
   241  	dbType = strings.ToLower(dbType)
   242  
   243  	// This currently only supports x86_64 linux
   244  	if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" {
   245  		return fmt.Errorf("downloadDBTypeVersion() only supports x86_64 linux, current test environment is %s %s", runtime.GOARCH, runtime.GOOS)
   246  	}
   247  
   248  	if dbType == "mysql" && majorVersion == "5.7" {
   249  		versionFile = "mysql-5.7.37-linux-glibc2.12-x86_64.tar.gz"
   250  		url = "https://dev.mysql.com/get/Downloads/MySQL-5.7/" + versionFile
   251  	} else if dbType == "mysql" && majorVersion == "8.0" {
   252  		versionFile = "mysql-8.0.28-linux-glibc2.17-x86_64-minimal.tar.xz"
   253  		url = "https://dev.mysql.com/get/Downloads/MySQL-8.0/" + versionFile
   254  	} else if dbType == "mariadb" && majorVersion == "10.10" {
   255  		versionFile = "mariadb-10.10.3-linux-systemd-x86_64.tar.gz"
   256  		url = "https://github.com/vitessio/vitess-resources/releases/download/v4.0/" + versionFile
   257  	} else {
   258  		return fmt.Errorf("invalid/unsupported major version: %s for database: %s", majorVersion, dbType)
   259  	}
   260  	file = fmt.Sprintf("%s/%s", path, versionFile)
   261  	// Let's not download the file again if we already have it
   262  	if _, err := os.Stat(file); err == nil {
   263  		return nil
   264  	}
   265  	resp, err := client.Get(url)
   266  	if err != nil {
   267  		return fmt.Errorf("error downloading contents of %s to %s. Error: %v", url, file, err)
   268  	}
   269  	defer resp.Body.Close()
   270  	out, err := os.Create(file)
   271  	if err != nil {
   272  		return fmt.Errorf("error creating file %s to save the contents of %s. Error: %v", file, url, err)
   273  	}
   274  	defer out.Close()
   275  	_, err = io.Copy(out, resp.Body)
   276  	if err != nil {
   277  		return fmt.Errorf("error saving contents of %s to %s. Error: %v", url, file, err)
   278  	}
   279  
   280  	untarCmd := exec.Command("/bin/sh", "-c", fmt.Sprintf("tar xvf %s -C %s --strip-components=1", file, path))
   281  	output, err := untarCmd.CombinedOutput()
   282  	if err != nil {
   283  		return fmt.Errorf("exec: %v failed: %v output: %s", untarCmd, err, string(output))
   284  	}
   285  
   286  	return nil
   287  }
   288  
   289  func getClusterConfig(idx int, dataRootDir string) *ClusterConfig {
   290  	basePort := 15000
   291  	etcdPort := 2379
   292  
   293  	basePort += idx * 10000
   294  	etcdPort += idx * 10000
   295  	if _, err := os.Stat(dataRootDir); os.IsNotExist(err) {
   296  		os.Mkdir(dataRootDir, 0700)
   297  	}
   298  
   299  	return &ClusterConfig{
   300  		// The ipv4 loopback address is used with the mysql client so that tcp is used in the test ("localhost" causes the socket file to be used, which fails)
   301  		hostname:            "127.0.0.1",
   302  		topoPort:            etcdPort,
   303  		vtctldPort:          basePort,
   304  		vtctldGrpcPort:      basePort + 999,
   305  		tmpDir:              dataRootDir + "/tmp",
   306  		vtgatePort:          basePort + 1,
   307  		vtgateGrpcPort:      basePort + 991,
   308  		vtgateMySQLPort:     basePort + 306,
   309  		tabletTypes:         "primary",
   310  		vtdataroot:          dataRootDir,
   311  		tabletPortBase:      basePort + 1000,
   312  		tabletGrpcPortBase:  basePort + 1991,
   313  		tabletMysqlPortBase: basePort + 1306,
   314  		vtorcPort:           basePort + 2639,
   315  		charset:             "utf8mb4",
   316  	}
   317  }
   318  
   319  func init() {
   320  	// for local debugging set this variable so that each run uses VTDATAROOT instead of a random dir
   321  	// and also does not teardown the cluster for inspecting logs and the databases
   322  	if os.Getenv("VREPLICATION_E2E_DEBUG") != "" {
   323  		debugMode = true
   324  	}
   325  	rand.Seed(time.Now().UTC().UnixNano())
   326  	originalVtdataroot = os.Getenv("VTDATAROOT")
   327  	var mainVtDataRoot string
   328  	if debugMode {
   329  		mainVtDataRoot = originalVtdataroot
   330  	} else {
   331  		mainVtDataRoot = setTempVtDataRoot()
   332  	}
   333  	mainClusterConfig = getClusterConfig(0, mainVtDataRoot)
   334  	externalClusterConfig = getClusterConfig(1, mainVtDataRoot+"/ext")
   335  }
   336  
   337  // NewVitessCluster starts a basic cluster with vtgate, vtctld and the topo
   338  func NewVitessCluster(t *testing.T, name string, cellNames []string, clusterConfig *ClusterConfig) *VitessCluster {
   339  	vc := &VitessCluster{Name: name, Cells: make(map[string]*Cell), ClusterConfig: clusterConfig}
   340  	require.NotNil(t, vc)
   341  	topo := cluster.TopoProcessInstance(vc.ClusterConfig.topoPort, vc.ClusterConfig.topoPort+1, vc.ClusterConfig.hostname, "etcd2", "global")
   342  
   343  	require.NotNil(t, topo)
   344  	require.Nil(t, topo.Setup("etcd2", nil))
   345  	err := topo.ManageTopoDir("mkdir", "/vitess/global")
   346  	require.NoError(t, err)
   347  	vc.Topo = topo
   348  	for _, cellName := range cellNames {
   349  		err := topo.ManageTopoDir("mkdir", "/vitess/"+cellName)
   350  		require.NoError(t, err)
   351  	}
   352  
   353  	vtctld := cluster.VtctldProcessInstance(vc.ClusterConfig.vtctldPort, vc.ClusterConfig.vtctldGrpcPort,
   354  		vc.ClusterConfig.topoPort, vc.ClusterConfig.hostname, vc.ClusterConfig.tmpDir)
   355  	vc.Vtctld = vtctld
   356  	require.NotNil(t, vc.Vtctld)
   357  	// use first cell as `-cell`
   358  	vc.Vtctld.Setup(cellNames[0], extraVtctldArgs...)
   359  
   360  	vc.Vtctl = cluster.VtctlProcessInstance(vc.ClusterConfig.topoPort, vc.ClusterConfig.hostname)
   361  	require.NotNil(t, vc.Vtctl)
   362  	for _, cellName := range cellNames {
   363  		vc.Vtctl.AddCellInfo(cellName)
   364  		cell, err := vc.AddCell(t, cellName)
   365  		require.NoError(t, err)
   366  		require.NotNil(t, cell)
   367  	}
   368  
   369  	vc.VtctlClient = cluster.VtctlClientProcessInstance(vc.ClusterConfig.hostname, vc.Vtctld.GrpcPort, vc.ClusterConfig.tmpDir)
   370  	require.NotNil(t, vc.VtctlClient)
   371  	vc.VtctldClient = cluster.VtctldClientProcessInstance(vc.ClusterConfig.hostname, vc.Vtctld.GrpcPort, vc.ClusterConfig.tmpDir)
   372  	require.NotNil(t, vc.VtctldClient)
   373  	return vc
   374  }
   375  
   376  // AddKeyspace creates a keyspace with specified shard keys and number of replica/read-only tablets.
   377  // You can pass optional key value pairs (opts) if you want conditional behavior.
   378  func (vc *VitessCluster) AddKeyspace(t *testing.T, cells []*Cell, ksName string, shards string, vschema string, schema string, numReplicas int, numRdonly int, tabletIDBase int, opts map[string]string) (*Keyspace, error) {
   379  	keyspace := &Keyspace{
   380  		Name:   ksName,
   381  		Shards: make(map[string]*Shard),
   382  	}
   383  
   384  	if err := vc.Vtctl.CreateKeyspace(keyspace.Name); err != nil {
   385  		t.Fatalf(err.Error())
   386  	}
   387  	cellsToWatch := ""
   388  	for i, cell := range cells {
   389  		if i > 0 {
   390  			cellsToWatch = cellsToWatch + ","
   391  		}
   392  		cell.Keyspaces[ksName] = keyspace
   393  		cellsToWatch = cellsToWatch + cell.Name
   394  	}
   395  	require.NoError(t, vc.AddShards(t, cells, keyspace, shards, numReplicas, numRdonly, tabletIDBase, opts))
   396  
   397  	if schema != "" {
   398  		if err := vc.VtctlClient.ApplySchema(ksName, schema); err != nil {
   399  			t.Fatalf(err.Error())
   400  		}
   401  	}
   402  	keyspace.Schema = schema
   403  	if vschema != "" {
   404  		if err := vc.VtctlClient.ApplyVSchema(ksName, vschema); err != nil {
   405  			t.Fatalf(err.Error())
   406  		}
   407  	}
   408  	keyspace.VSchema = vschema
   409  	for _, cell := range cells {
   410  		if len(cell.Vtgates) == 0 {
   411  			log.Infof("Starting vtgate")
   412  			vc.StartVtgate(t, cell, cellsToWatch)
   413  		}
   414  	}
   415  	_ = vc.VtctlClient.ExecuteCommand("RebuildKeyspaceGraph", ksName)
   416  	return keyspace, nil
   417  }
   418  
   419  // AddTablet creates new tablet with specified attributes
   420  func (vc *VitessCluster) AddTablet(t testing.TB, cell *Cell, keyspace *Keyspace, shard *Shard, tabletType string, tabletID int) (*Tablet, *exec.Cmd, error) {
   421  	tablet := &Tablet{}
   422  
   423  	options := []string{
   424  		"--queryserver-config-schema-reload-time", "5",
   425  		"--enable-lag-throttler",
   426  		"--heartbeat_enable",
   427  		"--heartbeat_interval", "250ms",
   428  	} // FIXME: for multi-cell initial schema doesn't seem to load without "--queryserver-config-schema-reload-time"
   429  	options = append(options, extraVTTabletArgs...)
   430  
   431  	if mainClusterConfig.vreplicationCompressGTID {
   432  		options = append(options, "--vreplication_store_compressed_gtid=true")
   433  	}
   434  
   435  	vttablet := cluster.VttabletProcessInstance(
   436  		vc.ClusterConfig.tabletPortBase+tabletID,
   437  		vc.ClusterConfig.tabletGrpcPortBase+tabletID,
   438  		tabletID,
   439  		cell.Name,
   440  		shard.Name,
   441  		keyspace.Name,
   442  		vc.ClusterConfig.vtctldPort,
   443  		tabletType,
   444  		vc.Topo.Port,
   445  		vc.ClusterConfig.hostname,
   446  		vc.ClusterConfig.tmpDir,
   447  		options,
   448  		vc.ClusterConfig.charset)
   449  
   450  	require.NotNil(t, vttablet)
   451  	vttablet.SupportsBackup = false
   452  
   453  	tablet.DbServer = cluster.MysqlCtlProcessInstance(tabletID, vc.ClusterConfig.tabletMysqlPortBase+tabletID, vc.ClusterConfig.tmpDir)
   454  	require.NotNil(t, tablet.DbServer)
   455  	tablet.DbServer.InitMysql = true
   456  	proc, err := tablet.DbServer.StartProcess()
   457  	if err != nil {
   458  		t.Fatal(err.Error())
   459  	}
   460  	require.NotNil(t, proc)
   461  	tablet.Name = fmt.Sprintf("%s-%d", cell.Name, tabletID)
   462  	vttablet.Name = tablet.Name
   463  	tablet.Vttablet = vttablet
   464  	shard.Tablets[tablet.Name] = tablet
   465  
   466  	return tablet, proc, nil
   467  }
   468  
   469  // AddShards creates shards given list of comma-separated keys with specified tablets in each shard
   470  func (vc *VitessCluster) AddShards(t *testing.T, cells []*Cell, keyspace *Keyspace, names string, numReplicas int, numRdonly int, tabletIDBase int, opts map[string]string) error {
   471  	// Add a VTOrc instance if one is not already running
   472  	if err := vc.StartVTOrc(); err != nil {
   473  		return err
   474  	}
   475  	// Disable global recoveries until the shard has been added.
   476  	// We need this because we run ISP in the end. Running ISP after VTOrc has already run PRS
   477  	// causes issues.
   478  	vc.VTOrcProcess.DisableGlobalRecoveries(t)
   479  	defer vc.VTOrcProcess.EnableGlobalRecoveries(t)
   480  
   481  	if value, exists := opts["DBTypeVersion"]; exists {
   482  		if resetFunc := setupDBTypeVersion(t, value); resetFunc != nil {
   483  			defer resetFunc()
   484  		}
   485  	}
   486  
   487  	arrNames := strings.Split(names, ",")
   488  	log.Infof("Addshards got %d shards with %+v", len(arrNames), arrNames)
   489  	isSharded := len(arrNames) > 1
   490  	primaryTabletUID := 0
   491  	for ind, shardName := range arrNames {
   492  		tabletID := tabletIDBase + ind*100
   493  		tabletIndex := 0
   494  		shard := &Shard{Name: shardName, IsSharded: isSharded, Tablets: make(map[string]*Tablet, 1)}
   495  		if _, ok := keyspace.Shards[shardName]; ok {
   496  			log.Infof("Shard %s already exists, not adding", shardName)
   497  		} else {
   498  			log.Infof("Adding Shard %s", shardName)
   499  			if err := vc.VtctlClient.ExecuteCommand("CreateShard", keyspace.Name+"/"+shardName); err != nil {
   500  				t.Fatalf("CreateShard command failed with %+v\n", err)
   501  			}
   502  			keyspace.Shards[shardName] = shard
   503  		}
   504  		for i, cell := range cells {
   505  			dbProcesses := make([]*exec.Cmd, 0)
   506  			tablets := make([]*Tablet, 0)
   507  			if i == 0 {
   508  				// only add primary tablet for first cell, so first time CreateShard is called
   509  				log.Infof("Adding Primary tablet")
   510  				primary, proc, err := vc.AddTablet(t, cell, keyspace, shard, "replica", tabletID+tabletIndex)
   511  				require.NoError(t, err)
   512  				require.NotNil(t, primary)
   513  				tabletIndex++
   514  				primary.Vttablet.VreplicationTabletType = "PRIMARY"
   515  				tablets = append(tablets, primary)
   516  				dbProcesses = append(dbProcesses, proc)
   517  				primaryTabletUID = primary.Vttablet.TabletUID
   518  			}
   519  
   520  			for i := 0; i < numReplicas; i++ {
   521  				log.Infof("Adding Replica tablet")
   522  				tablet, proc, err := vc.AddTablet(t, cell, keyspace, shard, "replica", tabletID+tabletIndex)
   523  				require.NoError(t, err)
   524  				require.NotNil(t, tablet)
   525  				tabletIndex++
   526  				tablets = append(tablets, tablet)
   527  				dbProcesses = append(dbProcesses, proc)
   528  			}
   529  			// Only create RDONLY tablets in the default cell
   530  			if cell.Name == cluster.DefaultCell {
   531  				for i := 0; i < numRdonly; i++ {
   532  					log.Infof("Adding RdOnly tablet")
   533  					tablet, proc, err := vc.AddTablet(t, cell, keyspace, shard, "rdonly", tabletID+tabletIndex)
   534  					require.NoError(t, err)
   535  					require.NotNil(t, tablet)
   536  					tabletIndex++
   537  					tablets = append(tablets, tablet)
   538  					dbProcesses = append(dbProcesses, proc)
   539  				}
   540  			}
   541  
   542  			for ind, proc := range dbProcesses {
   543  				log.Infof("Waiting for mysql process for tablet %s", tablets[ind].Name)
   544  				if err := proc.Wait(); err != nil {
   545  					t.Fatalf("%v :: Unable to start mysql server for %v", err, tablets[ind].Vttablet)
   546  				}
   547  			}
   548  			for ind, tablet := range tablets {
   549  				log.Infof("Running Setup() for vttablet %s", tablets[ind].Name)
   550  				if err := tablet.Vttablet.Setup(); err != nil {
   551  					t.Fatalf(err.Error())
   552  				}
   553  			}
   554  		}
   555  		require.NotEqual(t, 0, primaryTabletUID, "Should have created a primary tablet")
   556  		log.Infof("InitializeShard and make %d primary", primaryTabletUID)
   557  		require.NoError(t, vc.VtctlClient.InitializeShard(keyspace.Name, shardName, cells[0].Name, primaryTabletUID))
   558  		log.Infof("Finished creating shard %s", shard.Name)
   559  	}
   560  
   561  	return nil
   562  }
   563  
   564  // DeleteShard deletes a shard
   565  func (vc *VitessCluster) DeleteShard(t testing.TB, cellName string, ksName string, shardName string) {
   566  	shard := vc.Cells[cellName].Keyspaces[ksName].Shards[shardName]
   567  	require.NotNil(t, shard)
   568  	for _, tab := range shard.Tablets {
   569  		log.Infof("Shutting down tablet %s", tab.Name)
   570  		tab.Vttablet.TearDown()
   571  	}
   572  	log.Infof("Deleting Shard %s", shardName)
   573  	// TODO how can we avoid the use of even_if_serving?
   574  	if output, err := vc.VtctlClient.ExecuteCommandWithOutput("DeleteShard", "--", "--recursive", "--even_if_serving", ksName+"/"+shardName); err != nil {
   575  		t.Fatalf("DeleteShard command failed with error %+v and output %s\n", err, output)
   576  	}
   577  
   578  }
   579  
   580  // StartVtgate starts a vtgate process
   581  func (vc *VitessCluster) StartVtgate(t testing.TB, cell *Cell, cellsToWatch string) {
   582  	vtgate := cluster.VtgateProcessInstance(
   583  		vc.ClusterConfig.vtgatePort,
   584  		vc.ClusterConfig.vtgateGrpcPort,
   585  		vc.ClusterConfig.vtgateMySQLPort,
   586  		cell.Name,
   587  		cellsToWatch,
   588  		vc.ClusterConfig.hostname,
   589  		vc.ClusterConfig.tabletTypes,
   590  		vc.ClusterConfig.topoPort,
   591  		vc.ClusterConfig.tmpDir,
   592  		extraVTGateArgs,
   593  		vc.ClusterConfig.vtgatePlannerVersion)
   594  	require.NotNil(t, vtgate)
   595  	if err := vtgate.Setup(); err != nil {
   596  		t.Fatalf(err.Error())
   597  	}
   598  	cell.Vtgates = append(cell.Vtgates, vtgate)
   599  }
   600  
   601  // AddCell adds a new cell to the cluster
   602  func (vc *VitessCluster) AddCell(t testing.TB, name string) (*Cell, error) {
   603  	cell := &Cell{Name: name, Keyspaces: make(map[string]*Keyspace), Vtgates: make([]*cluster.VtgateProcess, 0)}
   604  	vc.Cells[name] = cell
   605  	return cell, nil
   606  }
   607  
   608  func (vc *VitessCluster) teardown(t testing.TB) {
   609  	for _, cell := range vc.Cells {
   610  		for _, vtgate := range cell.Vtgates {
   611  			if err := vtgate.TearDown(); err != nil {
   612  				log.Errorf("Error in vtgate teardown - %s", err.Error())
   613  			} else {
   614  				log.Infof("vtgate teardown successful")
   615  			}
   616  		}
   617  	}
   618  	// collect unique keyspaces across cells
   619  	keyspaces := make(map[string]*Keyspace)
   620  	for _, cell := range vc.Cells {
   621  		for _, keyspace := range cell.Keyspaces {
   622  			keyspaces[keyspace.Name] = keyspace
   623  		}
   624  	}
   625  
   626  	var wg sync.WaitGroup
   627  
   628  	for _, keyspace := range keyspaces {
   629  		for _, shard := range keyspace.Shards {
   630  			for _, tablet := range shard.Tablets {
   631  				wg.Add(1)
   632  				go func(tablet2 *Tablet) {
   633  					defer wg.Done()
   634  					if tablet2.DbServer != nil && tablet2.DbServer.TabletUID > 0 {
   635  						if _, err := tablet2.DbServer.StopProcess(); err != nil {
   636  							log.Infof("Error stopping mysql process: %s", err.Error())
   637  						}
   638  					}
   639  					if err := tablet2.Vttablet.TearDown(); err != nil {
   640  						log.Infof("Error stopping vttablet %s %s", tablet2.Name, err.Error())
   641  					} else {
   642  						log.Infof("Successfully stopped vttablet %s", tablet2.Name)
   643  					}
   644  				}(tablet)
   645  			}
   646  		}
   647  	}
   648  	wg.Wait()
   649  	if err := vc.Vtctld.TearDown(); err != nil {
   650  		log.Infof("Error stopping Vtctld:  %s", err.Error())
   651  	} else {
   652  		log.Info("Successfully stopped vtctld")
   653  	}
   654  
   655  	for _, cell := range vc.Cells {
   656  		if err := vc.Topo.TearDown(cell.Name, originalVtdataroot, vtdataroot, false, "etcd2"); err != nil {
   657  			log.Infof("Error in etcd teardown - %s", err.Error())
   658  		} else {
   659  			log.Infof("Successfully tore down topo %s", vc.Topo.Name)
   660  		}
   661  	}
   662  
   663  	if vc.VTOrcProcess != nil {
   664  		if err := vc.VTOrcProcess.TearDown(); err != nil {
   665  			log.Infof("Error stopping VTOrc: %s", err.Error())
   666  		}
   667  	}
   668  }
   669  
   670  // TearDown brings down a cluster, deleting processes, removing topo keys
   671  func (vc *VitessCluster) TearDown(t testing.TB) {
   672  	if debugMode {
   673  		return
   674  	}
   675  	done := make(chan bool)
   676  	go func() {
   677  		vc.teardown(t)
   678  		done <- true
   679  	}()
   680  	select {
   681  	case <-done:
   682  		log.Infof("TearDown() was successful")
   683  	case <-time.After(1 * time.Minute):
   684  		log.Infof("TearDown() timed out")
   685  	}
   686  	// some processes seem to hang around for a bit
   687  	time.Sleep(5 * time.Second)
   688  }
   689  
   690  func (vc *VitessCluster) getVttabletsInKeyspace(t *testing.T, cell *Cell, ksName string, tabletType string) map[string]*cluster.VttabletProcess {
   691  	keyspace := cell.Keyspaces[ksName]
   692  	tablets := make(map[string]*cluster.VttabletProcess)
   693  	for _, shard := range keyspace.Shards {
   694  		for _, tablet := range shard.Tablets {
   695  			if tablet.Vttablet.GetTabletStatus() == "SERVING" && strings.EqualFold(tablet.Vttablet.VreplicationTabletType, tabletType) {
   696  				log.Infof("Serving status of tablet %s is %s, %s", tablet.Name, tablet.Vttablet.ServingStatus, tablet.Vttablet.GetTabletStatus())
   697  				tablets[tablet.Name] = tablet.Vttablet
   698  			}
   699  		}
   700  	}
   701  	return tablets
   702  }
   703  
   704  func (vc *VitessCluster) getPrimaryTablet(t *testing.T, ksName, shardName string) *cluster.VttabletProcess {
   705  	for _, cell := range vc.Cells {
   706  		keyspace := cell.Keyspaces[ksName]
   707  		if keyspace == nil {
   708  			continue
   709  		}
   710  		for _, shard := range keyspace.Shards {
   711  			if shard.Name != shardName {
   712  				continue
   713  			}
   714  			for _, tablet := range shard.Tablets {
   715  				if tablet.Vttablet.GetTabletStatus() == "SERVING" && strings.EqualFold(tablet.Vttablet.VreplicationTabletType, "primary") {
   716  					return tablet.Vttablet
   717  				}
   718  			}
   719  		}
   720  	}
   721  	require.FailNow(t, "no primary found for %s:%s", ksName, shardName)
   722  	return nil
   723  }
   724  
   725  func (vc *VitessCluster) startQuery(t *testing.T, query string) (func(t *testing.T), func(t *testing.T)) {
   726  	conn := getConnection(t, vc.ClusterConfig.hostname, vc.ClusterConfig.vtgateMySQLPort)
   727  	_, err := conn.ExecuteFetch("begin", 1000, false)
   728  	require.NoError(t, err)
   729  	_, err = conn.ExecuteFetch(query, 1000, false)
   730  	require.NoError(t, err)
   731  
   732  	commit := func(t *testing.T) {
   733  		_, err = conn.ExecuteFetch("commit", 1000, false)
   734  		log.Infof("startQuery:commit:err: %+v", err)
   735  		conn.Close()
   736  		log.Infof("startQuery:after closing connection")
   737  	}
   738  	rollback := func(t *testing.T) {
   739  		defer conn.Close()
   740  		_, err = conn.ExecuteFetch("rollback", 1000, false)
   741  		log.Infof("startQuery:rollback:err: %+v", err)
   742  	}
   743  	return commit, rollback
   744  }
   745  
   746  // setupDBTypeVersion will perform any work needed to enable a specific
   747  // database type and version if not already installed. It returns a
   748  // function to reset any environment changes made.
   749  func setupDBTypeVersion(t *testing.T, value string) func() {
   750  	details := strings.Split(value, "-")
   751  	if len(details) != 2 {
   752  		t.Fatalf("Invalid database details: %s", value)
   753  	}
   754  	dbType := strings.ToLower(details[0])
   755  	majorVersion := details[1]
   756  	dbTypeMajorVersion := fmt.Sprintf("%s-%s", dbType, majorVersion)
   757  	// Do nothing if this version is already installed
   758  	dbVersionInUse, err := getDBTypeVersionInUse()
   759  	if err != nil {
   760  		t.Fatalf("Could not get details of database to be used for the keyspace: %v", err)
   761  	}
   762  	if dbTypeMajorVersion == dbVersionInUse {
   763  		t.Logf("Requsted database version %s is already installed, doing nothing.", dbTypeMajorVersion)
   764  		return func() {}
   765  	}
   766  	path := fmt.Sprintf("/tmp/%s", dbTypeMajorVersion)
   767  	// Set the root path and create it if needed
   768  	if err := setVtMySQLRoot(path); err != nil {
   769  		t.Fatalf("Could not set VT_MYSQL_ROOT to %s, error: %v", path, err)
   770  	}
   771  	// Download and extract the version artifact if needed
   772  	if err := downloadDBTypeVersion(dbType, majorVersion, path); err != nil {
   773  		t.Fatalf("Could not download %s, error: %v", majorVersion, err)
   774  	}
   775  	// Set the MYSQL_FLAVOR OS ENV var for mysqlctl to use the correct config file
   776  	if err := setDBFlavor(); err != nil {
   777  		t.Fatalf("Could not set MYSQL_FLAVOR: %v", err)
   778  	}
   779  	return func() {
   780  		unsetDBFlavor()
   781  		unsetVtMySQLRoot()
   782  	}
   783  }