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 }