vitess.io/vitess@v0.16.2/go/test/endtoend/cluster/cluster_process.go (about)

     1  /*
     2  Copyright 2019 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 cluster
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"flag"
    23  	"fmt"
    24  	"io"
    25  	"math/rand"
    26  	"net"
    27  	"os"
    28  	"os/exec"
    29  	"os/signal"
    30  	"path"
    31  	"regexp"
    32  	"strconv"
    33  	"strings"
    34  	"sync"
    35  	"syscall"
    36  	"testing"
    37  	"time"
    38  
    39  	"vitess.io/vitess/go/json2"
    40  	"vitess.io/vitess/go/mysql"
    41  	"vitess.io/vitess/go/sqltypes"
    42  	"vitess.io/vitess/go/test/endtoend/filelock"
    43  	"vitess.io/vitess/go/vt/grpcclient"
    44  	"vitess.io/vitess/go/vt/log"
    45  	"vitess.io/vitess/go/vt/vtgate/planbuilder/plancontext"
    46  	"vitess.io/vitess/go/vt/vtgate/vtgateconn"
    47  	"vitess.io/vitess/go/vt/vttablet/tabletconn"
    48  
    49  	querypb "vitess.io/vitess/go/vt/proto/query"
    50  	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
    51  
    52  	// Ensure dialers are registered (needed by ExecOnTablet and ExecOnVTGate).
    53  	_ "vitess.io/vitess/go/vt/vtgate/grpcvtgateconn"
    54  	_ "vitess.io/vitess/go/vt/vttablet/grpctabletconn"
    55  )
    56  
    57  // DefaultCell : If no cell name is passed, then use following
    58  const (
    59  	DefaultCell      = "zone1"
    60  	DefaultStartPort = 6700
    61  )
    62  
    63  var (
    64  	keepData           = flag.Bool("keep-data", true, "don't delete the per-test VTDATAROOT subfolders")
    65  	topoFlavor         = flag.String("topo-flavor", "etcd2", "choose a topo server from etcd2, zk2 or consul")
    66  	isCoverage         = flag.Bool("is-coverage", false, "whether coverage is required")
    67  	forceVTDATAROOT    = flag.String("force-vtdataroot", "", "force path for VTDATAROOT, which may already be populated")
    68  	forcePortStart     = flag.Int("force-port-start", 0, "force assigning ports based on this seed")
    69  	forceBaseTabletUID = flag.Int("force-base-tablet-uid", 0, "force assigning tablet ports based on this seed")
    70  	partialKeyspace    = flag.Bool("partial-keyspace", false, "add a second keyspace for sharded tests and mark first shard as moved to this keyspace in the shard routing rules")
    71  
    72  	// PerfTest controls whether to run the slower end-to-end tests that check the system's performance
    73  	PerfTest = flag.Bool("perf-test", false, "include end-to-end performance tests")
    74  )
    75  
    76  // LocalProcessCluster Testcases need to use this to iniate a cluster
    77  type LocalProcessCluster struct {
    78  	Keyspaces          []Keyspace
    79  	Cell               string
    80  	DefaultCharset     string
    81  	BaseTabletUID      int
    82  	Hostname           string
    83  	TopoFlavor         string
    84  	TopoPort           int
    85  	TmpDirectory       string
    86  	OriginalVTDATAROOT string
    87  	CurrentVTDATAROOT  string
    88  	ReusingVTDATAROOT  bool
    89  
    90  	VtgateMySQLPort int
    91  	VtgateGrpcPort  int
    92  	VtctldHTTPPort  int
    93  
    94  	// major version numbers
    95  	VtTabletMajorVersion int
    96  	VtctlMajorVersion    int
    97  
    98  	// standalone executable
    99  	VtctlclientProcess  VtctlClientProcess
   100  	VtctldClientProcess VtctldClientProcess
   101  	VtctlProcess        VtctlProcess
   102  
   103  	// background executable processes
   104  	TopoProcess     TopoProcess
   105  	VtctldProcess   VtctldProcess
   106  	VtgateProcess   VtgateProcess
   107  	VtbackupProcess VtbackupProcess
   108  	VTOrcProcesses  []*VTOrcProcess
   109  
   110  	nextPortForProcess int
   111  
   112  	// Extra arguments for vtTablet
   113  	VtTabletExtraArgs []string
   114  
   115  	// Extra arguments for vtGate
   116  	VtGateExtraArgs      []string
   117  	VtGatePlannerVersion plancontext.PlannerVersion
   118  
   119  	VtctldExtraArgs []string
   120  
   121  	// mutex added to handle the parallel teardowns
   122  	mx                *sync.Mutex
   123  	teardownCompleted bool
   124  
   125  	context.Context
   126  	context.CancelFunc
   127  
   128  	HasPartialKeyspaces bool
   129  }
   130  
   131  // Vttablet stores the properties needed to start a vttablet process
   132  type Vttablet struct {
   133  	Type      string
   134  	TabletUID int
   135  	HTTPPort  int
   136  	GrpcPort  int
   137  	MySQLPort int
   138  	Alias     string
   139  	Cell      string
   140  
   141  	// background executable processes
   142  	MysqlctlProcess  MysqlctlProcess
   143  	MysqlctldProcess MysqlctldProcess
   144  	VttabletProcess  *VttabletProcess
   145  	VtgrProcess      *VtgrProcess
   146  }
   147  
   148  // Keyspace : Cluster accepts keyspace to launch it
   149  type Keyspace struct {
   150  	Name      string
   151  	SchemaSQL string
   152  	VSchema   string
   153  	Shards    []Shard
   154  }
   155  
   156  // Shard with associated vttablets
   157  type Shard struct {
   158  	Name      string
   159  	Vttablets []*Vttablet
   160  }
   161  
   162  // PrimaryTablet get the 1st tablet which is always elected as primary
   163  func (shard *Shard) PrimaryTablet() *Vttablet {
   164  	return shard.Vttablets[0]
   165  }
   166  
   167  // Rdonly get the last tablet which is rdonly
   168  func (shard *Shard) Rdonly() *Vttablet {
   169  	for idx, tablet := range shard.Vttablets {
   170  		if tablet.Type == "rdonly" {
   171  			return shard.Vttablets[idx]
   172  		}
   173  	}
   174  	return nil
   175  }
   176  
   177  // Replica get the last but one tablet which is replica
   178  // Mostly we have either 3 tablet setup [primary, replica, rdonly]
   179  func (shard *Shard) Replica() *Vttablet {
   180  	for idx, tablet := range shard.Vttablets {
   181  		if tablet.Type == "replica" && idx > 0 {
   182  			return shard.Vttablets[idx]
   183  		}
   184  	}
   185  	return nil
   186  }
   187  
   188  // CtrlCHandler handles the teardown for the ctrl-c.
   189  func (cluster *LocalProcessCluster) CtrlCHandler() {
   190  	cluster.Context, cluster.CancelFunc = context.WithCancel(context.Background())
   191  
   192  	c := make(chan os.Signal, 2)
   193  	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
   194  	select {
   195  	case <-c:
   196  		cluster.Teardown()
   197  		os.Exit(0)
   198  	case <-cluster.Done():
   199  	}
   200  }
   201  
   202  // StartTopo starts topology server
   203  func (cluster *LocalProcessCluster) StartTopo() (err error) {
   204  	if cluster.Cell == "" {
   205  		cluster.Cell = DefaultCell
   206  	}
   207  
   208  	topoFlavor = cluster.TopoFlavorString()
   209  	cluster.TopoPort = cluster.GetAndReservePort()
   210  	cluster.TmpDirectory = path.Join(os.Getenv("VTDATAROOT"), fmt.Sprintf("/tmp_%d", cluster.GetAndReservePort()))
   211  	cluster.TopoProcess = *TopoProcessInstance(cluster.TopoPort, cluster.GetAndReservePort(), cluster.Hostname, *topoFlavor, "global")
   212  
   213  	log.Infof("Starting topo server %v on port: %d", *topoFlavor, cluster.TopoPort)
   214  	if err = cluster.TopoProcess.Setup(*topoFlavor, cluster); err != nil {
   215  		log.Error(err.Error())
   216  		return
   217  	}
   218  
   219  	if *topoFlavor == "etcd2" {
   220  		log.Info("Creating global and cell topo dirs")
   221  		if err = cluster.TopoProcess.ManageTopoDir("mkdir", "/vitess/global"); err != nil {
   222  			log.Error(err.Error())
   223  			return
   224  		}
   225  
   226  		if err = cluster.TopoProcess.ManageTopoDir("mkdir", "/vitess/"+cluster.Cell); err != nil {
   227  			log.Error(err.Error())
   228  			return
   229  		}
   230  	}
   231  
   232  	if !cluster.ReusingVTDATAROOT {
   233  		cluster.VtctlProcess = *VtctlProcessInstance(cluster.TopoProcess.Port, cluster.Hostname)
   234  		if err = cluster.VtctlProcess.AddCellInfo(cluster.Cell); err != nil {
   235  			log.Error(err)
   236  			return
   237  		}
   238  		cluster.VtctlProcess.LogDir = cluster.TmpDirectory
   239  	}
   240  
   241  	cluster.VtctldProcess = *VtctldProcessInstance(cluster.GetAndReservePort(), cluster.GetAndReservePort(),
   242  		cluster.TopoProcess.Port, cluster.Hostname, cluster.TmpDirectory)
   243  	log.Infof("Starting vtctld server on port: %d", cluster.VtctldProcess.Port)
   244  	cluster.VtctldHTTPPort = cluster.VtctldProcess.Port
   245  	if err = cluster.VtctldProcess.Setup(cluster.Cell, cluster.VtctldExtraArgs...); err != nil {
   246  		log.Error(err.Error())
   247  		return
   248  	}
   249  
   250  	cluster.VtctlclientProcess = *VtctlClientProcessInstance("localhost", cluster.VtctldProcess.GrpcPort, cluster.TmpDirectory)
   251  	return
   252  }
   253  
   254  // StartVTOrc starts a VTOrc instance
   255  func (cluster *LocalProcessCluster) StartVTOrc(keyspace string) error {
   256  	// Start vtorc
   257  	vtorcProcess := cluster.NewVTOrcProcess(VTOrcConfiguration{})
   258  	err := vtorcProcess.Setup()
   259  	if err != nil {
   260  		log.Error(err.Error())
   261  		return err
   262  	}
   263  	if keyspace != "" {
   264  		vtorcProcess.ExtraArgs = append(vtorcProcess.ExtraArgs, fmt.Sprintf(`--clusters_to_watch="%s"`, keyspace))
   265  	}
   266  	cluster.VTOrcProcesses = append(cluster.VTOrcProcesses, vtorcProcess)
   267  	return nil
   268  }
   269  
   270  // StartUnshardedKeyspace starts unshared keyspace with shard name as "0"
   271  func (cluster *LocalProcessCluster) StartUnshardedKeyspace(keyspace Keyspace, replicaCount int, rdonly bool) error {
   272  	return cluster.StartKeyspace(keyspace, []string{"0"}, replicaCount, rdonly)
   273  }
   274  
   275  func (cluster *LocalProcessCluster) startPartialKeyspace(keyspace Keyspace, shardNames []string, movedShard string, replicaCount int, rdonly bool, customizers ...any) (err error) {
   276  
   277  	cluster.HasPartialKeyspaces = true
   278  	routedKeyspace := &Keyspace{
   279  		Name:      fmt.Sprintf("%s_routed", keyspace.Name),
   280  		SchemaSQL: keyspace.SchemaSQL,
   281  		VSchema:   keyspace.VSchema,
   282  	}
   283  
   284  	err = cluster.startKeyspace(*routedKeyspace, shardNames, replicaCount, rdonly, customizers...)
   285  	if err != nil {
   286  		return err
   287  	}
   288  	shardRoutingRulesTemplate := `{"rules":[{"from_keyspace":"%s","to_keyspace":"%s","shards":"%s"}]}`
   289  	shardRoutingRules := fmt.Sprintf(shardRoutingRulesTemplate, keyspace.Name, routedKeyspace.Name, movedShard)
   290  	cmd := exec.Command("vtctldclient", "--server",
   291  		net.JoinHostPort("localhost", strconv.Itoa(cluster.VtctldProcess.GrpcPort)),
   292  		"ApplyShardRoutingRules", "--rules", shardRoutingRules)
   293  	_, err = cmd.Output()
   294  	if err != nil {
   295  		return err
   296  	}
   297  	return nil
   298  }
   299  
   300  func (cluster *LocalProcessCluster) StartKeyspace(keyspace Keyspace, shardNames []string, replicaCount int, rdonly bool, customizers ...any) (err error) {
   301  	err = cluster.startKeyspace(keyspace, shardNames, replicaCount, rdonly, customizers...)
   302  	if err != nil {
   303  		return err
   304  	}
   305  
   306  	if *partialKeyspace && len(shardNames) > 1 {
   307  		movedShard := shardNames[0]
   308  		return cluster.startPartialKeyspace(keyspace, shardNames, movedShard, replicaCount, rdonly, customizers...)
   309  	}
   310  	return nil
   311  }
   312  
   313  // StartKeyspace starts required number of shard and the corresponding tablets
   314  // keyspace : struct containing keyspace name, Sqlschema to apply, VSchema to apply
   315  // shardName : list of shard names
   316  // replicaCount: total number of replicas excluding shard primary and rdonly
   317  // rdonly: whether readonly tablets needed
   318  // customizers: functions like "func(*VttabletProcess)" that can modify settings of various objects
   319  // after they're created.
   320  func (cluster *LocalProcessCluster) startKeyspace(keyspace Keyspace, shardNames []string, replicaCount int, rdonly bool, customizers ...any) (err error) {
   321  	totalTabletsRequired := replicaCount + 1 // + 1 is for primary
   322  	if rdonly {
   323  		totalTabletsRequired = totalTabletsRequired + 1 // + 1 for rdonly
   324  	}
   325  
   326  	log.Infof("Starting keyspace: %v", keyspace.Name)
   327  	if !cluster.ReusingVTDATAROOT {
   328  		_ = cluster.VtctlProcess.CreateKeyspace(keyspace.Name)
   329  	}
   330  	var mysqlctlProcessList []*exec.Cmd
   331  	for _, shardName := range shardNames {
   332  		shard := &Shard{
   333  			Name: shardName,
   334  		}
   335  		log.Infof("Starting shard: %v", shardName)
   336  		mysqlctlProcessList = []*exec.Cmd{}
   337  		for i := 0; i < totalTabletsRequired; i++ {
   338  			// instantiate vttablet object with reserved ports
   339  			tabletUID := cluster.GetAndReserveTabletUID()
   340  			tablet := &Vttablet{
   341  				TabletUID: tabletUID,
   342  				Type:      "replica",
   343  				HTTPPort:  cluster.GetAndReservePort(),
   344  				GrpcPort:  cluster.GetAndReservePort(),
   345  				MySQLPort: cluster.GetAndReservePort(),
   346  				Alias:     fmt.Sprintf("%s-%010d", cluster.Cell, tabletUID),
   347  			}
   348  			if i == 0 { // Make the first one as primary
   349  				tablet.Type = "primary"
   350  			} else if i == totalTabletsRequired-1 && rdonly { // Make the last one as rdonly if rdonly flag is passed
   351  				tablet.Type = "rdonly"
   352  			}
   353  			// Start Mysqlctl process
   354  			log.Infof("Starting mysqlctl for table uid %d, mysql port %d", tablet.TabletUID, tablet.MySQLPort)
   355  			tablet.MysqlctlProcess = *MysqlCtlProcessInstanceOptionalInit(tablet.TabletUID, tablet.MySQLPort, cluster.TmpDirectory, !cluster.ReusingVTDATAROOT)
   356  			proc, err := tablet.MysqlctlProcess.StartProcess()
   357  			if err != nil {
   358  				log.Errorf("error starting mysqlctl process: %v, %v", tablet.MysqlctldProcess, err)
   359  				return err
   360  			}
   361  			mysqlctlProcessList = append(mysqlctlProcessList, proc)
   362  
   363  			// start vttablet process
   364  			tablet.VttabletProcess = VttabletProcessInstance(
   365  				tablet.HTTPPort,
   366  				tablet.GrpcPort,
   367  				tablet.TabletUID,
   368  				cluster.Cell,
   369  				shardName,
   370  				keyspace.Name,
   371  				cluster.VtctldProcess.Port,
   372  				tablet.Type,
   373  				cluster.TopoProcess.Port,
   374  				cluster.Hostname,
   375  				cluster.TmpDirectory,
   376  				cluster.VtTabletExtraArgs,
   377  				cluster.DefaultCharset)
   378  			tablet.Alias = tablet.VttabletProcess.TabletPath
   379  			if cluster.ReusingVTDATAROOT {
   380  				tablet.VttabletProcess.ServingStatus = "SERVING"
   381  			}
   382  			shard.Vttablets = append(shard.Vttablets, tablet)
   383  			// Apply customizations
   384  			for _, customizer := range customizers {
   385  				if f, ok := customizer.(func(*VttabletProcess)); ok {
   386  					f(tablet.VttabletProcess)
   387  				} else {
   388  					return fmt.Errorf("type mismatch on customizer: %T", customizer)
   389  				}
   390  			}
   391  		}
   392  
   393  		// wait till all mysqlctl is instantiated
   394  		for _, proc := range mysqlctlProcessList {
   395  			if err = proc.Wait(); err != nil {
   396  				log.Errorf("unable to start mysql process %v: %v", proc, err)
   397  				return err
   398  			}
   399  		}
   400  		for _, tablet := range shard.Vttablets {
   401  			log.Infof("Starting vttablet for tablet uid %d, grpc port %d", tablet.TabletUID, tablet.GrpcPort)
   402  
   403  			if err = tablet.VttabletProcess.Setup(); err != nil {
   404  				log.Errorf("error starting vttablet for tablet uid %d, grpc port %d: %v", tablet.TabletUID, tablet.GrpcPort, err)
   405  				return
   406  			}
   407  		}
   408  
   409  		// Make first tablet as primary
   410  		if err = cluster.VtctlclientProcess.InitializeShard(keyspace.Name, shardName, cluster.Cell, shard.Vttablets[0].TabletUID); err != nil {
   411  			log.Errorf("error running InitializeShard on keyspace %v, shard %v: %v", keyspace.Name, shardName, err)
   412  			return
   413  		}
   414  		keyspace.Shards = append(keyspace.Shards, *shard)
   415  	}
   416  	// if the keyspace is present then append the shard info
   417  	existingKeyspace := false
   418  	for idx, ks := range cluster.Keyspaces {
   419  		if ks.Name == keyspace.Name {
   420  			cluster.Keyspaces[idx].Shards = append(cluster.Keyspaces[idx].Shards, keyspace.Shards...)
   421  			existingKeyspace = true
   422  		}
   423  	}
   424  	if !existingKeyspace {
   425  		cluster.Keyspaces = append(cluster.Keyspaces, keyspace)
   426  	}
   427  
   428  	// Apply Schema SQL
   429  	if keyspace.SchemaSQL != "" {
   430  		if err = cluster.VtctlclientProcess.ApplySchema(keyspace.Name, keyspace.SchemaSQL); err != nil {
   431  			log.Errorf("error applying schema: %v, %v", keyspace.SchemaSQL, err)
   432  			return
   433  		}
   434  	}
   435  
   436  	// Apply VSchema
   437  	if keyspace.VSchema != "" {
   438  		if err = cluster.VtctlclientProcess.ApplyVSchema(keyspace.Name, keyspace.VSchema); err != nil {
   439  			log.Errorf("error applying vschema: %v, %v", keyspace.VSchema, err)
   440  			return
   441  		}
   442  	}
   443  
   444  	log.Infof("Done creating keyspace: %v ", keyspace.Name)
   445  
   446  	err = cluster.StartVTOrc(keyspace.Name)
   447  	if err != nil {
   448  		log.Errorf("Error starting VTOrc - %v", err)
   449  		return err
   450  	}
   451  
   452  	return
   453  }
   454  
   455  // StartUnshardedKeyspaceLegacy starts unshared keyspace with shard name as "0"
   456  func (cluster *LocalProcessCluster) StartUnshardedKeyspaceLegacy(keyspace Keyspace, replicaCount int, rdonly bool) error {
   457  	return cluster.StartKeyspaceLegacy(keyspace, []string{"0"}, replicaCount, rdonly)
   458  }
   459  
   460  // StartKeyspaceLegacy starts required number of shard and the corresponding tablets
   461  // keyspace : struct containing keyspace name, Sqlschema to apply, VSchema to apply
   462  // shardName : list of shard names
   463  // replicaCount: total number of replicas excluding shard primary and rdonly
   464  // rdonly: whether readonly tablets needed
   465  // customizers: functions like "func(*VttabletProcess)" that can modify settings of various objects
   466  // after they're created.
   467  func (cluster *LocalProcessCluster) StartKeyspaceLegacy(keyspace Keyspace, shardNames []string, replicaCount int, rdonly bool, customizers ...any) (err error) {
   468  	totalTabletsRequired := replicaCount + 1 // + 1 is for primary
   469  	if rdonly {
   470  		totalTabletsRequired = totalTabletsRequired + 1 // + 1 for rdonly
   471  	}
   472  
   473  	log.Infof("Starting keyspace: %v", keyspace.Name)
   474  	if !cluster.ReusingVTDATAROOT {
   475  		_ = cluster.VtctlProcess.CreateKeyspace(keyspace.Name)
   476  	}
   477  	var mysqlctlProcessList []*exec.Cmd
   478  	for _, shardName := range shardNames {
   479  		shard := &Shard{
   480  			Name: shardName,
   481  		}
   482  		log.Infof("Starting shard: %v", shardName)
   483  		mysqlctlProcessList = []*exec.Cmd{}
   484  		for i := 0; i < totalTabletsRequired; i++ {
   485  			// instantiate vttablet object with reserved ports
   486  			tabletUID := cluster.GetAndReserveTabletUID()
   487  			tablet := &Vttablet{
   488  				TabletUID: tabletUID,
   489  				Type:      "replica",
   490  				HTTPPort:  cluster.GetAndReservePort(),
   491  				GrpcPort:  cluster.GetAndReservePort(),
   492  				MySQLPort: cluster.GetAndReservePort(),
   493  				Alias:     fmt.Sprintf("%s-%010d", cluster.Cell, tabletUID),
   494  			}
   495  			if i == 0 { // Make the first one as primary
   496  				tablet.Type = "primary"
   497  			} else if i == totalTabletsRequired-1 && rdonly { // Make the last one as rdonly if rdonly flag is passed
   498  				tablet.Type = "rdonly"
   499  			}
   500  			// Start Mysqlctl process
   501  			log.Infof("Starting mysqlctl for table uid %d, mysql port %d", tablet.TabletUID, tablet.MySQLPort)
   502  			tablet.MysqlctlProcess = *MysqlCtlProcessInstanceOptionalInit(tablet.TabletUID, tablet.MySQLPort, cluster.TmpDirectory, !cluster.ReusingVTDATAROOT)
   503  			proc, err := tablet.MysqlctlProcess.StartProcess()
   504  			if err != nil {
   505  				log.Errorf("error starting mysqlctl process: %v, %v", tablet.MysqlctldProcess, err)
   506  				return err
   507  			}
   508  			mysqlctlProcessList = append(mysqlctlProcessList, proc)
   509  
   510  			// start vttablet process
   511  			tablet.VttabletProcess = VttabletProcessInstance(
   512  				tablet.HTTPPort,
   513  				tablet.GrpcPort,
   514  				tablet.TabletUID,
   515  				cluster.Cell,
   516  				shardName,
   517  				keyspace.Name,
   518  				cluster.VtctldProcess.Port,
   519  				tablet.Type,
   520  				cluster.TopoProcess.Port,
   521  				cluster.Hostname,
   522  				cluster.TmpDirectory,
   523  				cluster.VtTabletExtraArgs,
   524  				cluster.DefaultCharset)
   525  			tablet.Alias = tablet.VttabletProcess.TabletPath
   526  			if cluster.ReusingVTDATAROOT {
   527  				tablet.VttabletProcess.ServingStatus = "SERVING"
   528  			}
   529  			shard.Vttablets = append(shard.Vttablets, tablet)
   530  			// Apply customizations
   531  			for _, customizer := range customizers {
   532  				if f, ok := customizer.(func(*VttabletProcess)); ok {
   533  					f(tablet.VttabletProcess)
   534  				} else {
   535  					return fmt.Errorf("type mismatch on customizer: %T", customizer)
   536  				}
   537  			}
   538  		}
   539  
   540  		// wait till all mysqlctl is instantiated
   541  		for _, proc := range mysqlctlProcessList {
   542  			if err = proc.Wait(); err != nil {
   543  				log.Errorf("unable to start mysql process %v: %v", proc, err)
   544  				return err
   545  			}
   546  		}
   547  		for _, tablet := range shard.Vttablets {
   548  			if !cluster.ReusingVTDATAROOT {
   549  				if _, err = tablet.VttabletProcess.QueryTablet(fmt.Sprintf("create database vt_%s", keyspace.Name), keyspace.Name, false); err != nil {
   550  					log.Errorf("error creating database for keyspace %v: %v", keyspace.Name, err)
   551  					return
   552  				}
   553  			}
   554  
   555  			log.Infof("Starting vttablet for tablet uid %d, grpc port %d", tablet.TabletUID, tablet.GrpcPort)
   556  
   557  			if err = tablet.VttabletProcess.Setup(); err != nil {
   558  				log.Errorf("error starting vttablet for tablet uid %d, grpc port %d: %v", tablet.TabletUID, tablet.GrpcPort, err)
   559  				return
   560  			}
   561  		}
   562  
   563  		// Make first tablet as primary
   564  		if err = cluster.VtctlclientProcess.InitShardPrimary(keyspace.Name, shardName, cluster.Cell, shard.Vttablets[0].TabletUID); err != nil {
   565  			log.Errorf("error running ISM on keyspace %v, shard %v: %v", keyspace.Name, shardName, err)
   566  			return
   567  		}
   568  		keyspace.Shards = append(keyspace.Shards, *shard)
   569  	}
   570  	// if the keyspace is present then append the shard info
   571  	existingKeyspace := false
   572  	for idx, ks := range cluster.Keyspaces {
   573  		if ks.Name == keyspace.Name {
   574  			cluster.Keyspaces[idx].Shards = append(cluster.Keyspaces[idx].Shards, keyspace.Shards...)
   575  			existingKeyspace = true
   576  		}
   577  	}
   578  	if !existingKeyspace {
   579  		cluster.Keyspaces = append(cluster.Keyspaces, keyspace)
   580  	}
   581  
   582  	// Apply Schema SQL
   583  	if keyspace.SchemaSQL != "" {
   584  		if err = cluster.VtctlclientProcess.ApplySchema(keyspace.Name, keyspace.SchemaSQL); err != nil {
   585  			log.Errorf("error applying schema: %v, %v", keyspace.SchemaSQL, err)
   586  			return
   587  		}
   588  	}
   589  
   590  	// Apply VSchema
   591  	if keyspace.VSchema != "" {
   592  		if err = cluster.VtctlclientProcess.ApplyVSchema(keyspace.Name, keyspace.VSchema); err != nil {
   593  			log.Errorf("error applying vschema: %v, %v", keyspace.VSchema, err)
   594  			return
   595  		}
   596  	}
   597  
   598  	log.Infof("Done creating keyspace: %v ", keyspace.Name)
   599  	return
   600  }
   601  
   602  // SetupCluster creates the skeleton for a cluster by creating keyspace
   603  // shards and initializing tablets and mysqlctl processes.
   604  // This does not start any process and user have to explicitly start all
   605  // the required services (ex topo, vtgate, mysql and vttablet)
   606  func (cluster *LocalProcessCluster) SetupCluster(keyspace *Keyspace, shards []Shard) (err error) {
   607  	log.Infof("Starting keyspace: %v", keyspace.Name)
   608  
   609  	if !cluster.ReusingVTDATAROOT {
   610  		// Create Keyspace
   611  		err = cluster.VtctlProcess.CreateKeyspace(keyspace.Name)
   612  		if err != nil {
   613  			log.Error(err)
   614  			return
   615  		}
   616  	}
   617  
   618  	// Create shard
   619  	for _, shard := range shards {
   620  		for _, tablet := range shard.Vttablets {
   621  			// Setup MysqlctlProcess
   622  			tablet.MysqlctlProcess = *MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, cluster.TmpDirectory)
   623  			// Setup VttabletProcess
   624  			tablet.VttabletProcess = VttabletProcessInstance(
   625  				tablet.HTTPPort,
   626  				tablet.GrpcPort,
   627  				tablet.TabletUID,
   628  				tablet.Cell,
   629  				shard.Name,
   630  				keyspace.Name,
   631  				cluster.VtctldProcess.Port,
   632  				tablet.Type,
   633  				cluster.TopoProcess.Port,
   634  				cluster.Hostname,
   635  				cluster.TmpDirectory,
   636  				cluster.VtTabletExtraArgs,
   637  				cluster.DefaultCharset)
   638  		}
   639  
   640  		keyspace.Shards = append(keyspace.Shards, shard)
   641  	}
   642  
   643  	// if the keyspace is present then append the shard info
   644  	existingKeyspace := false
   645  	for idx, ks := range cluster.Keyspaces {
   646  		if ks.Name == keyspace.Name {
   647  			cluster.Keyspaces[idx].Shards = append(cluster.Keyspaces[idx].Shards, keyspace.Shards...)
   648  			existingKeyspace = true
   649  		}
   650  	}
   651  	if !existingKeyspace {
   652  		cluster.Keyspaces = append(cluster.Keyspaces, *keyspace)
   653  	}
   654  
   655  	log.Infof("Done launching keyspace: %v", keyspace.Name)
   656  	return err
   657  }
   658  
   659  // StartVtgate starts vtgate
   660  func (cluster *LocalProcessCluster) StartVtgate() (err error) {
   661  	if cluster.HasPartialKeyspaces {
   662  		cluster.VtGateExtraArgs = append(cluster.VtGateExtraArgs, "--enable-partial-keyspace-migration")
   663  	}
   664  	vtgateInstance := *cluster.NewVtgateInstance()
   665  	cluster.VtgateProcess = vtgateInstance
   666  	log.Infof("Starting vtgate on port %d", vtgateInstance.Port)
   667  	log.Infof("Vtgate started, connect to mysql using : mysql -h 127.0.0.1 -P %d", cluster.VtgateMySQLPort)
   668  	return cluster.VtgateProcess.Setup()
   669  }
   670  
   671  // NewVtgateInstance returns an instance of vtgateprocess
   672  func (cluster *LocalProcessCluster) NewVtgateInstance() *VtgateProcess {
   673  	vtgateHTTPPort := cluster.GetAndReservePort()
   674  	cluster.VtgateGrpcPort = cluster.GetAndReservePort()
   675  	cluster.VtgateMySQLPort = cluster.GetAndReservePort()
   676  	vtgateProcInstance := VtgateProcessInstance(
   677  		vtgateHTTPPort,
   678  		cluster.VtgateGrpcPort,
   679  		cluster.VtgateMySQLPort,
   680  		cluster.Cell,
   681  		cluster.Cell,
   682  		cluster.Hostname,
   683  		"PRIMARY,REPLICA",
   684  		cluster.TopoProcess.Port,
   685  		cluster.TmpDirectory,
   686  		cluster.VtGateExtraArgs,
   687  		cluster.VtGatePlannerVersion)
   688  	return vtgateProcInstance
   689  }
   690  
   691  // NewBareCluster instantiates a new cluster and does not assume existence of any of the vitess processes
   692  func NewBareCluster(cell string, hostname string) *LocalProcessCluster {
   693  	cluster := &LocalProcessCluster{Cell: cell, Hostname: hostname, mx: new(sync.Mutex), DefaultCharset: "utf8mb4"}
   694  	go cluster.CtrlCHandler()
   695  
   696  	cluster.OriginalVTDATAROOT = os.Getenv("VTDATAROOT")
   697  	cluster.CurrentVTDATAROOT = path.Join(os.Getenv("VTDATAROOT"), fmt.Sprintf("vtroot_%d", cluster.GetAndReservePort()))
   698  	cluster.VtGatePlannerVersion = defaultVtGatePlannerVersion
   699  	if *forceVTDATAROOT != "" {
   700  		cluster.CurrentVTDATAROOT = *forceVTDATAROOT
   701  	}
   702  	if _, err := os.Stat(cluster.CurrentVTDATAROOT); err == nil {
   703  		// path/to/whatever exists
   704  		cluster.ReusingVTDATAROOT = true
   705  	} else {
   706  		_ = createDirectory(cluster.CurrentVTDATAROOT, 0700)
   707  	}
   708  	_ = os.Setenv("VTDATAROOT", cluster.CurrentVTDATAROOT)
   709  	log.Infof("Created cluster on %s. ReusingVTDATAROOT=%v", cluster.CurrentVTDATAROOT, cluster.ReusingVTDATAROOT)
   710  
   711  	rand.Seed(time.Now().UTC().UnixNano())
   712  	return cluster
   713  }
   714  
   715  // NewCluster instantiates a new cluster
   716  func NewCluster(cell string, hostname string) *LocalProcessCluster {
   717  	cluster := NewBareCluster(cell, hostname)
   718  
   719  	err := cluster.populateVersionInfo()
   720  	if err != nil {
   721  		log.Errorf("Error populating version information - %v", err)
   722  	}
   723  	return cluster
   724  }
   725  
   726  // populateVersionInfo is used to populate the version information for the binaries used to setup the cluster.
   727  func (cluster *LocalProcessCluster) populateVersionInfo() error {
   728  	var err error
   729  	cluster.VtTabletMajorVersion, err = GetMajorVersion("vttablet")
   730  	if err != nil {
   731  		return err
   732  	}
   733  	cluster.VtctlMajorVersion, err = GetMajorVersion("vtctl")
   734  	return err
   735  }
   736  
   737  func GetMajorVersion(binaryName string) (int, error) {
   738  	version, err := exec.Command(binaryName, "--version").Output()
   739  	if err != nil {
   740  		return 0, err
   741  	}
   742  	versionRegex := regexp.MustCompile(`Version: ([0-9]+)\.([0-9]+)\.([0-9]+)`)
   743  	v := versionRegex.FindStringSubmatch(string(version))
   744  	if len(v) != 4 {
   745  		return 0, fmt.Errorf("could not parse server version from: %s", version)
   746  	}
   747  	if err != nil {
   748  		return 0, fmt.Errorf("could not parse server version from: %s", version)
   749  	}
   750  	return strconv.Atoi(v[1])
   751  }
   752  
   753  // RestartVtgate starts vtgate with updated configs
   754  func (cluster *LocalProcessCluster) RestartVtgate() (err error) {
   755  	err = cluster.VtgateProcess.TearDown()
   756  	if err != nil {
   757  		log.Errorf("error stopping vtgate %v: %v", cluster.VtgateProcess, err)
   758  		return
   759  	}
   760  	err = cluster.StartVtgate()
   761  	if err != nil {
   762  		log.Errorf("error starting vtgate %v: %v", cluster.VtgateProcess, err)
   763  		return
   764  	}
   765  	return err
   766  }
   767  
   768  // WaitForTabletsToHealthyInVtgate waits for all tablets in all shards to be seen as
   769  // healthy and serving in vtgate.
   770  // For each shard:
   771  //   - It must have 1 (and only 1) healthy primary tablet so we always wait for that
   772  //   - For replica and rdonly tablets, which are optional, we wait for as many as we
   773  //     should have based on how the cluster was defined.
   774  func (cluster *LocalProcessCluster) WaitForTabletsToHealthyInVtgate() (err error) {
   775  	for _, keyspace := range cluster.Keyspaces {
   776  		for _, shard := range keyspace.Shards {
   777  			rdonlyTabletCount, replicaTabletCount := 0, 0
   778  			for _, tablet := range shard.Vttablets {
   779  				switch strings.ToLower(tablet.Type) {
   780  				case "replica":
   781  					replicaTabletCount++
   782  				case "rdonly":
   783  					rdonlyTabletCount++
   784  				}
   785  			}
   786  			if err = cluster.VtgateProcess.WaitForStatusOfTabletInShard(fmt.Sprintf("%s.%s.primary", keyspace.Name, shard.Name), 1, 2*time.Minute); err != nil {
   787  				return err
   788  			}
   789  			if replicaTabletCount > 0 {
   790  				err = cluster.VtgateProcess.WaitForStatusOfTabletInShard(fmt.Sprintf("%s.%s.replica", keyspace.Name, shard.Name), replicaTabletCount, 2*time.Minute)
   791  			}
   792  			if rdonlyTabletCount > 0 {
   793  				err = cluster.VtgateProcess.WaitForStatusOfTabletInShard(fmt.Sprintf("%s.%s.rdonly", keyspace.Name, shard.Name), rdonlyTabletCount, 2*time.Minute)
   794  			}
   795  			if err != nil {
   796  				return err
   797  			}
   798  		}
   799  	}
   800  	return nil
   801  }
   802  
   803  // ExecOnTablet executes a query on the local cluster Vttablet and returns the
   804  // result.
   805  func (cluster *LocalProcessCluster) ExecOnTablet(ctx context.Context, vttablet *Vttablet, sql string, binds map[string]any, opts *querypb.ExecuteOptions) (*sqltypes.Result, error) {
   806  	bindvars, err := sqltypes.BuildBindVariables(binds)
   807  	if err != nil {
   808  		return nil, err
   809  	}
   810  
   811  	tablet, err := cluster.VtctlclientGetTablet(vttablet)
   812  	if err != nil {
   813  		return nil, err
   814  	}
   815  
   816  	conn, err := tabletconn.GetDialer()(tablet, grpcclient.FailFast(false))
   817  	if err != nil {
   818  		return nil, err
   819  	}
   820  	defer conn.Close(ctx)
   821  
   822  	txID, reservedID := 0, 0
   823  
   824  	return conn.Execute(ctx, &querypb.Target{
   825  		Keyspace:   tablet.Keyspace,
   826  		Shard:      tablet.Shard,
   827  		TabletType: tablet.Type,
   828  	}, sql, bindvars, int64(txID), int64(reservedID), opts)
   829  }
   830  
   831  // ExecOnVTGate executes a query on a local cluster VTGate with the provided
   832  // target, bindvars, and execute options.
   833  func (cluster *LocalProcessCluster) ExecOnVTGate(ctx context.Context, addr string, target string, sql string, binds map[string]any, opts *querypb.ExecuteOptions) (*sqltypes.Result, error) {
   834  	bindvars, err := sqltypes.BuildBindVariables(binds)
   835  	if err != nil {
   836  		return nil, err
   837  	}
   838  
   839  	conn, err := vtgateconn.Dial(ctx, addr)
   840  	if err != nil {
   841  		return nil, err
   842  	}
   843  
   844  	session := conn.Session(target, opts)
   845  	defer conn.Close()
   846  
   847  	return session.Execute(ctx, sql, bindvars)
   848  }
   849  
   850  // StreamTabletHealth invokes a HealthStream on a local cluster Vttablet and
   851  // returns the responses. It returns an error if the stream ends with fewer than
   852  // `count` responses.
   853  func (cluster *LocalProcessCluster) StreamTabletHealth(ctx context.Context, vttablet *Vttablet, count int) (responses []*querypb.StreamHealthResponse, err error) {
   854  	tablet, err := cluster.VtctlclientGetTablet(vttablet)
   855  	if err != nil {
   856  		return nil, err
   857  	}
   858  
   859  	conn, err := tabletconn.GetDialer()(tablet, grpcclient.FailFast(false))
   860  	if err != nil {
   861  		return nil, err
   862  	}
   863  
   864  	i := 0
   865  	err = conn.StreamHealth(ctx, func(shr *querypb.StreamHealthResponse) error {
   866  		responses = append(responses, shr)
   867  
   868  		i++
   869  		if i >= count {
   870  			return io.EOF
   871  		}
   872  
   873  		return nil
   874  	})
   875  
   876  	switch {
   877  	case err != nil:
   878  		return nil, err
   879  	case len(responses) < count:
   880  		return nil, errors.New("stream ended early")
   881  	}
   882  
   883  	return responses, nil
   884  }
   885  
   886  func (cluster *LocalProcessCluster) VtctlclientGetTablet(tablet *Vttablet) (*topodatapb.Tablet, error) {
   887  	result, err := cluster.VtctlclientProcess.ExecuteCommandWithOutput("GetTablet", "--", tablet.Alias)
   888  	if err != nil {
   889  		return nil, err
   890  	}
   891  
   892  	var ti topodatapb.Tablet
   893  	if err := json2.Unmarshal([]byte(result), &ti); err != nil {
   894  		return nil, err
   895  	}
   896  
   897  	return &ti, nil
   898  }
   899  
   900  // Teardown brings down the cluster by invoking teardown for individual processes
   901  func (cluster *LocalProcessCluster) Teardown() {
   902  	PanicHandler(nil)
   903  	cluster.mx.Lock()
   904  	defer cluster.mx.Unlock()
   905  	if cluster.teardownCompleted {
   906  		return
   907  	}
   908  	if cluster.CancelFunc != nil {
   909  		cluster.CancelFunc()
   910  	}
   911  	if err := cluster.VtgateProcess.TearDown(); err != nil {
   912  		log.Errorf("Error in vtgate teardown: %v", err)
   913  	}
   914  
   915  	for _, vtorcProcess := range cluster.VTOrcProcesses {
   916  		if err := vtorcProcess.TearDown(); err != nil {
   917  			log.Errorf("Error in vtorc teardown: %v", err)
   918  		}
   919  	}
   920  
   921  	var mysqlctlProcessList []*exec.Cmd
   922  	var mysqlctlTabletUIDs []int
   923  	for _, keyspace := range cluster.Keyspaces {
   924  		for _, shard := range keyspace.Shards {
   925  			for _, tablet := range shard.Vttablets {
   926  				if tablet.MysqlctlProcess.TabletUID > 0 {
   927  					if proc, err := tablet.MysqlctlProcess.StopProcess(); err != nil {
   928  						log.Errorf("Error in mysqlctl teardown: %v", err)
   929  					} else {
   930  						mysqlctlProcessList = append(mysqlctlProcessList, proc)
   931  						mysqlctlTabletUIDs = append(mysqlctlTabletUIDs, tablet.MysqlctlProcess.TabletUID)
   932  					}
   933  				}
   934  				if tablet.MysqlctldProcess.TabletUID > 0 {
   935  					if err := tablet.MysqlctldProcess.Stop(); err != nil {
   936  						log.Errorf("Error in mysqlctl teardown: %v", err)
   937  					}
   938  				}
   939  
   940  				if err := tablet.VttabletProcess.TearDown(); err != nil {
   941  					log.Errorf("Error in vttablet teardown: %v", err)
   942  				}
   943  			}
   944  		}
   945  	}
   946  
   947  	// On the CI it was noticed that MySQL shutdown hangs sometimes and
   948  	// on local investigation it was waiting on SEMI_SYNC acks for an internal command
   949  	// of Vitess even after closing the socket file.
   950  	// To prevent this process for hanging for 5 minutes, we will add a 30-second timeout.
   951  	cluster.waitForMySQLProcessToExit(mysqlctlProcessList, mysqlctlTabletUIDs)
   952  
   953  	if err := cluster.VtctldProcess.TearDown(); err != nil {
   954  		log.Errorf("Error in vtctld teardown: %v", err)
   955  	}
   956  
   957  	if err := cluster.TopoProcess.TearDown(cluster.Cell, cluster.OriginalVTDATAROOT, cluster.CurrentVTDATAROOT, *keepData, *topoFlavor); err != nil {
   958  		log.Errorf("Error in topo server teardown: %v", err)
   959  	}
   960  
   961  	// reset the VTDATAROOT path.
   962  	os.Setenv("VTDATAROOT", cluster.OriginalVTDATAROOT)
   963  
   964  	cluster.teardownCompleted = true
   965  }
   966  
   967  func (cluster *LocalProcessCluster) waitForMySQLProcessToExit(mysqlctlProcessList []*exec.Cmd, mysqlctlTabletUIDs []int) {
   968  	wg := sync.WaitGroup{}
   969  	for i, cmd := range mysqlctlProcessList {
   970  		wg.Add(1)
   971  		go func(cmd *exec.Cmd, tabletUID int) {
   972  			defer func() {
   973  				wg.Done()
   974  			}()
   975  			exit := make(chan error)
   976  			go func() {
   977  				exit <- cmd.Wait()
   978  			}()
   979  			select {
   980  			case <-time.After(30 * time.Second):
   981  				break
   982  			case err := <-exit:
   983  				if err == nil {
   984  					return
   985  				}
   986  				log.Errorf("Error in mysqlctl teardown wait: %v", err)
   987  				break
   988  			}
   989  			pidFile := path.Join(os.Getenv("VTDATAROOT"), fmt.Sprintf("/vt_%010d/mysql.pid", tabletUID))
   990  			pidBytes, err := os.ReadFile(pidFile)
   991  			if err != nil {
   992  				// We can't read the file which means the PID file does not exist
   993  				// The server must have stopped
   994  				return
   995  			}
   996  			pid, err := strconv.Atoi(strings.TrimSpace(string(pidBytes)))
   997  			if err != nil {
   998  				log.Errorf("Error in conversion to integer: %v", err)
   999  				return
  1000  			}
  1001  			err = syscall.Kill(pid, syscall.SIGKILL)
  1002  			if err != nil {
  1003  				log.Errorf("Error in killing process: %v", err)
  1004  			}
  1005  		}(cmd, mysqlctlTabletUIDs[i])
  1006  	}
  1007  	wg.Wait()
  1008  }
  1009  
  1010  // StartVtbackup starts a vtbackup
  1011  func (cluster *LocalProcessCluster) StartVtbackup(newInitDBFile string, initialBackup bool,
  1012  	keyspace string, shard string, cell string, extraArgs ...string) error {
  1013  	log.Info("Starting vtbackup")
  1014  	cluster.VtbackupProcess = *VtbackupProcessInstance(
  1015  		cluster.GetAndReserveTabletUID(),
  1016  		cluster.GetAndReservePort(),
  1017  		newInitDBFile,
  1018  		keyspace,
  1019  		shard,
  1020  		cell,
  1021  		cluster.Hostname,
  1022  		cluster.TmpDirectory,
  1023  		cluster.TopoPort,
  1024  		initialBackup)
  1025  	cluster.VtbackupProcess.ExtraArgs = extraArgs
  1026  	return cluster.VtbackupProcess.Setup()
  1027  
  1028  }
  1029  
  1030  // GetAndReservePort gives port for required process
  1031  func (cluster *LocalProcessCluster) GetAndReservePort() int {
  1032  	if cluster.nextPortForProcess == 0 {
  1033  		if *forcePortStart > 0 {
  1034  			cluster.nextPortForProcess = *forcePortStart
  1035  		} else {
  1036  			cluster.nextPortForProcess = getPort()
  1037  		}
  1038  	}
  1039  	for {
  1040  		cluster.nextPortForProcess = cluster.nextPortForProcess + 1
  1041  		log.Infof("Attempting to reserve port: %v", cluster.nextPortForProcess)
  1042  		ln, err := net.Listen("tcp", fmt.Sprintf(":%v", cluster.nextPortForProcess))
  1043  
  1044  		if err != nil {
  1045  			log.Errorf("Can't listen on port %v: %s, trying next port", cluster.nextPortForProcess, err)
  1046  			continue
  1047  		}
  1048  
  1049  		log.Infof("Port %v is available, reserving..", cluster.nextPortForProcess)
  1050  		ln.Close()
  1051  		break
  1052  	}
  1053  	return cluster.nextPortForProcess
  1054  }
  1055  
  1056  // portFileTimeout determines when we see the content of a port file as
  1057  // stale. After this time, we assume we can start with the default base
  1058  // port again.
  1059  const portFileTimeout = 1 * time.Hour
  1060  
  1061  // getPort checks if we have recent used port info in /tmp/todaytime.port
  1062  // If no, then use a random port and save that port + 200 in the above file
  1063  // If yes, then return that port, and save port + 200 in the same file
  1064  // here, assumptions is 200 ports might be consumed for all tests in a package
  1065  func getPort() int {
  1066  	portFile, err := os.OpenFile(path.Join(os.TempDir(), "endtoend.port"), os.O_CREATE|os.O_RDWR, 0644)
  1067  	if err != nil {
  1068  		panic(err)
  1069  	}
  1070  
  1071  	filelock.Lock(portFile)
  1072  	defer filelock.Unlock(portFile)
  1073  
  1074  	fileInfo, err := portFile.Stat()
  1075  	if err != nil {
  1076  		panic(err)
  1077  	}
  1078  
  1079  	portBytes, err := io.ReadAll(portFile)
  1080  	if err != nil {
  1081  		panic(err)
  1082  	}
  1083  
  1084  	var port int
  1085  	if len(portBytes) == 0 || time.Now().After(fileInfo.ModTime().Add(portFileTimeout)) {
  1086  		port = getVtStartPort()
  1087  	} else {
  1088  		parsedPort, err := strconv.ParseInt(string(portBytes), 10, 64)
  1089  		if err != nil {
  1090  			panic(err)
  1091  		}
  1092  		port = int(parsedPort)
  1093  	}
  1094  
  1095  	portFile.Truncate(0)
  1096  	portFile.Seek(0, 0)
  1097  	portFile.WriteString(fmt.Sprintf("%v", port+200))
  1098  	portFile.Close()
  1099  	return port
  1100  }
  1101  
  1102  // GetAndReserveTabletUID gives tablet uid
  1103  func (cluster *LocalProcessCluster) GetAndReserveTabletUID() int {
  1104  	if cluster.BaseTabletUID == 0 {
  1105  		if *forceBaseTabletUID > 0 {
  1106  			cluster.BaseTabletUID = *forceBaseTabletUID
  1107  		} else {
  1108  			cluster.BaseTabletUID = getRandomNumber(10000, 0)
  1109  		}
  1110  	}
  1111  	cluster.BaseTabletUID = cluster.BaseTabletUID + 1
  1112  	return cluster.BaseTabletUID
  1113  }
  1114  
  1115  func getRandomNumber(maxNumber int32, baseNumber int) int {
  1116  	return int(rand.Int31n(maxNumber)) + baseNumber
  1117  }
  1118  
  1119  func getVtStartPort() int {
  1120  	osVtPort := os.Getenv("VTPORTSTART")
  1121  	if osVtPort != "" {
  1122  		cport, err := strconv.Atoi(osVtPort)
  1123  		if err == nil {
  1124  			return cport
  1125  		}
  1126  	}
  1127  	return DefaultStartPort
  1128  }
  1129  
  1130  // NewVttabletInstance creates a new vttablet object
  1131  func (cluster *LocalProcessCluster) NewVttabletInstance(tabletType string, UID int, cell string) *Vttablet {
  1132  	if UID == 0 {
  1133  		UID = cluster.GetAndReserveTabletUID()
  1134  	}
  1135  	if cell == "" {
  1136  		cell = cluster.Cell
  1137  	}
  1138  	return &Vttablet{
  1139  		TabletUID: UID,
  1140  		HTTPPort:  cluster.GetAndReservePort(),
  1141  		GrpcPort:  cluster.GetAndReservePort(),
  1142  		MySQLPort: cluster.GetAndReservePort(),
  1143  		Type:      tabletType,
  1144  		Cell:      cell,
  1145  		Alias:     fmt.Sprintf("%s-%010d", cell, UID),
  1146  	}
  1147  }
  1148  
  1149  // NewVTOrcProcess creates a new VTOrcProcess object
  1150  func (cluster *LocalProcessCluster) NewVTOrcProcess(config VTOrcConfiguration) *VTOrcProcess {
  1151  	base := VtctlProcessInstance(cluster.TopoProcess.Port, cluster.Hostname)
  1152  	base.Binary = "vtorc"
  1153  	return &VTOrcProcess{
  1154  		VtctlProcess: *base,
  1155  		LogDir:       cluster.TmpDirectory,
  1156  		Config:       config,
  1157  		WebPort:      cluster.GetAndReservePort(),
  1158  		Port:         cluster.GetAndReservePort(),
  1159  	}
  1160  }
  1161  
  1162  // NewVtgrProcess creates a new VtgrProcess object
  1163  func (cluster *LocalProcessCluster) NewVtgrProcess(clusters []string, config string, grPort int) *VtgrProcess {
  1164  	base := VtctlProcessInstance(cluster.TopoProcess.Port, cluster.Hostname)
  1165  	base.Binary = "vtgr"
  1166  	return &VtgrProcess{
  1167  		VtctlProcess: *base,
  1168  		LogDir:       cluster.TmpDirectory,
  1169  		clusters:     clusters,
  1170  		config:       config,
  1171  		grPort:       grPort,
  1172  	}
  1173  }
  1174  
  1175  // VtprocessInstanceFromVttablet creates a new vttablet object
  1176  func (cluster *LocalProcessCluster) VtprocessInstanceFromVttablet(tablet *Vttablet, shardName string, ksName string) *VttabletProcess {
  1177  	return VttabletProcessInstance(
  1178  		tablet.HTTPPort,
  1179  		tablet.GrpcPort,
  1180  		tablet.TabletUID,
  1181  		cluster.Cell,
  1182  		shardName,
  1183  		ksName,
  1184  		cluster.VtctldProcess.Port,
  1185  		tablet.Type,
  1186  		cluster.TopoProcess.Port,
  1187  		cluster.Hostname,
  1188  		cluster.TmpDirectory,
  1189  		cluster.VtTabletExtraArgs,
  1190  		cluster.DefaultCharset)
  1191  }
  1192  
  1193  // StartVttablet starts a new tablet
  1194  func (cluster *LocalProcessCluster) StartVttablet(tablet *Vttablet, servingStatus string,
  1195  	supportBackup bool, cell string, keyspaceName string, hostname string, shardName string) error {
  1196  	tablet.VttabletProcess = VttabletProcessInstance(
  1197  		tablet.HTTPPort,
  1198  		tablet.GrpcPort,
  1199  		tablet.TabletUID,
  1200  		cell,
  1201  		shardName,
  1202  		keyspaceName,
  1203  		cluster.VtctldProcess.Port,
  1204  		tablet.Type,
  1205  		cluster.TopoProcess.Port,
  1206  		hostname,
  1207  		cluster.TmpDirectory,
  1208  		cluster.VtTabletExtraArgs,
  1209  		cluster.DefaultCharset)
  1210  
  1211  	tablet.VttabletProcess.SupportsBackup = supportBackup
  1212  	tablet.VttabletProcess.ServingStatus = servingStatus
  1213  	return tablet.VttabletProcess.Setup()
  1214  }
  1215  
  1216  // TopoFlavorString returns the topo flavor
  1217  func (cluster *LocalProcessCluster) TopoFlavorString() *string {
  1218  	if cluster.TopoFlavor != "" {
  1219  		return &cluster.TopoFlavor
  1220  	}
  1221  	return topoFlavor
  1222  }
  1223  
  1224  func getCoveragePath(fileName string) string {
  1225  	covDir := os.Getenv("COV_DIR")
  1226  	if covDir == "" {
  1227  		covDir = os.TempDir()
  1228  	}
  1229  	return path.Join(covDir, fileName)
  1230  }
  1231  
  1232  // PrintMysqlctlLogFiles prints all the log files associated with the mysqlctl binary
  1233  func (cluster *LocalProcessCluster) PrintMysqlctlLogFiles() {
  1234  	logDir := cluster.TmpDirectory
  1235  	files, _ := os.ReadDir(logDir)
  1236  	for _, fileInfo := range files {
  1237  		if !fileInfo.IsDir() && strings.Contains(fileInfo.Name(), "mysqlctl") {
  1238  			log.Errorf("Printing the log file - " + fileInfo.Name())
  1239  			logOut, _ := os.ReadFile(path.Join(logDir, fileInfo.Name()))
  1240  			log.Errorf(string(logOut))
  1241  		}
  1242  	}
  1243  }
  1244  
  1245  // GetVTParams returns mysql.ConnParams with host and port only for regular tests enabling global routing,
  1246  // and also dbname if we are testing for a cluster with a partially moved keyspace
  1247  func (cluster *LocalProcessCluster) GetVTParams(dbname string) mysql.ConnParams {
  1248  	params := mysql.ConnParams{
  1249  		Host: cluster.Hostname,
  1250  		Port: cluster.VtgateMySQLPort,
  1251  	}
  1252  	if cluster.HasPartialKeyspaces {
  1253  		params.DbName = dbname
  1254  	}
  1255  	return params
  1256  }
  1257  
  1258  // DisableVTOrcRecoveries stops all VTOrcs from running any recoveries
  1259  func (cluster *LocalProcessCluster) DisableVTOrcRecoveries(t *testing.T) {
  1260  	for _, vtorc := range cluster.VTOrcProcesses {
  1261  		vtorc.DisableGlobalRecoveries(t)
  1262  	}
  1263  }
  1264  
  1265  // EnableVTOrcRecoveries allows all VTOrcs to run any recoveries
  1266  func (cluster *LocalProcessCluster) EnableVTOrcRecoveries(t *testing.T) {
  1267  	for _, vtorc := range cluster.VTOrcProcesses {
  1268  		vtorc.EnableGlobalRecoveries(t)
  1269  	}
  1270  }