vitess.io/vitess@v0.16.2/examples/compose/vtcompose/vtcompose.go (about) 1 /* 2 * Copyright 2020 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 main 18 19 import ( 20 "bytes" 21 "encoding/json" 22 "fmt" 23 "math" 24 "os" 25 "regexp" 26 "strconv" 27 "strings" 28 29 jsonpatch "github.com/evanphx/json-patch" 30 yamlpatch "github.com/krishicks/yaml-patch" 31 "github.com/spf13/pflag" 32 33 "vitess.io/vitess/go/vt/log" 34 ) 35 36 const ( 37 DefaultWebPort = 8080 38 webPortUsage = "Web port to be used" 39 DefaultGrpcPort = 15999 40 gRpcPortUsage = "gRPC port to be used" 41 DefaultMysqlPort = 15306 42 mySqlPortUsage = "mySql port to be used" 43 DefaultKeyspaceData = "test_keyspace:2:1:create_messages.sql,create_tokens.sql;unsharded_keyspace:0:0:create_dinosaurs.sql,create_eggs.sql" 44 keyspaceDataUsage = "List of keyspace_name/external_db_name:num_of_shards:num_of_replica_tablets:schema_files:<optional>lookup_keyspace_name separated by ';'" 45 DefaultCell = "test" 46 cellUsage = "Vitess Cell name" 47 DefaultExternalDbData = "" 48 externalDbDataUsage = "List of Data corresponding to external DBs. List of <external_db_name>,<DB_HOST>,<DB_PORT>,<DB_USER>,<DB_PASS>,<DB_CHARSET> separated by ';'" 49 DefaultTopologyFlags = "--topo_implementation consul --topo_global_server_address consul1:8500 --topo_global_root vitess/global" 50 topologyFlagsUsage = "Vitess Topology Flags config" 51 ) 52 53 var ( 54 tabletsUsed = 0 55 tablesPath = "tables/" 56 baseDockerComposeFile = pflag.String("base_yaml", "vtcompose/docker-compose.base.yml", "Starting docker-compose yaml") 57 baseVschemaFile = pflag.String("base_vschema", "vtcompose/base_vschema.json", "Starting vschema json") 58 59 topologyFlags = pflag.String("topologyFlags", DefaultTopologyFlags, topologyFlagsUsage) 60 webPort = pflag.Int("webPort", DefaultWebPort, webPortUsage) 61 gRpcPort = pflag.Int("gRpcPort", DefaultGrpcPort, gRpcPortUsage) 62 mySqlPort = pflag.Int("mySqlPort", DefaultMysqlPort, mySqlPortUsage) 63 cell = pflag.String("cell", DefaultCell, cellUsage) 64 keyspaceData = pflag.String("keyspaceData", DefaultKeyspaceData, keyspaceDataUsage) 65 externalDbData = pflag.String("externalDbData", DefaultExternalDbData, externalDbDataUsage) 66 ) 67 68 type vtOptions struct { 69 webPort int 70 gRpcPort int 71 mySqlPort int 72 topologyFlags string 73 cell string 74 } 75 76 type keyspaceInfo struct { 77 keyspace string 78 shards int 79 replicaTablets int 80 lookupKeyspace string 81 useLookups bool 82 schemaFile *os.File 83 schemaFileNames []string 84 } 85 86 type externalDbInfo struct { 87 dbName string 88 dbHost string 89 dbPort string 90 dbUser string 91 dbPass string 92 dbCharset string 93 } 94 95 func newKeyspaceInfo( 96 keyspace string, 97 shards int, 98 replicaTablets int, 99 schemaFiles []string, 100 lookupKeyspace string, 101 ) keyspaceInfo { 102 k := keyspaceInfo{ 103 keyspace: keyspace, 104 shards: shards, 105 replicaTablets: replicaTablets, 106 schemaFileNames: schemaFiles, 107 lookupKeyspace: lookupKeyspace, 108 } 109 if len(strings.TrimSpace(lookupKeyspace)) == 0 { 110 k.useLookups = false 111 } else { 112 k.useLookups = true 113 } 114 115 k.schemaFile = nil 116 return k 117 } 118 119 func newExternalDbInfo(dbName, dbHost, dbPort, dbUser, dbPass, dbCharset string) externalDbInfo { 120 return externalDbInfo{ 121 dbName: dbName, 122 dbHost: dbHost, 123 dbPort: dbPort, 124 dbUser: dbUser, 125 dbPass: dbPass, 126 dbCharset: dbCharset, 127 } 128 } 129 130 func parseKeyspaceInfo(keyspaceData string) map[string]keyspaceInfo { 131 keyspaceInfoMap := make(map[string]keyspaceInfo) 132 133 for _, v := range strings.Split(keyspaceData, ";") { 134 tokens := strings.Split(v, ":") 135 shards, _ := strconv.Atoi(tokens[1]) 136 replicaTablets, _ := strconv.Atoi(tokens[2]) 137 schemaFileNames := []string{} 138 // Make schemafiles argument optional 139 if len(tokens) > 3 { 140 f := func(c rune) bool { 141 return c == ',' 142 } 143 schemaFileNames = strings.FieldsFunc(tokens[3], f) 144 } 145 146 if len(tokens) > 4 { 147 keyspaceInfoMap[tokens[0]] = newKeyspaceInfo(tokens[0], shards, replicaTablets, schemaFileNames, tokens[4]) 148 } else { 149 keyspaceInfoMap[tokens[0]] = newKeyspaceInfo(tokens[0], shards, replicaTablets, schemaFileNames, "") 150 } 151 } 152 153 return keyspaceInfoMap 154 } 155 156 func parseExternalDbData(externalDbData string) map[string]externalDbInfo { 157 externalDbInfoMap := make(map[string]externalDbInfo) 158 for _, v := range strings.Split(externalDbData, ";") { 159 tokens := strings.Split(v, ":") 160 if len(tokens) > 1 { 161 externalDbInfoMap[tokens[0]] = 162 newExternalDbInfo(tokens[0], tokens[1], tokens[2], tokens[3], tokens[4], tokens[5]) 163 } 164 } 165 166 return externalDbInfoMap 167 } 168 169 func main() { 170 pflag.Parse() 171 keyspaceInfoMap := parseKeyspaceInfo(*keyspaceData) 172 externalDbInfoMap := parseExternalDbData(*externalDbData) 173 vtOpts := vtOptions{ 174 webPort: *webPort, 175 gRpcPort: *gRpcPort, 176 mySqlPort: *mySqlPort, 177 topologyFlags: *topologyFlags, 178 cell: *cell, 179 } 180 181 // Write schemaFile. 182 for k, v := range keyspaceInfoMap { 183 if _, ok := externalDbInfoMap[k]; !ok { 184 v.schemaFile = createFile(fmt.Sprintf("%s%s_schema_file.sql", tablesPath, v.keyspace)) 185 appendToSqlFile(v.schemaFileNames, v.schemaFile) 186 closeFile(v.schemaFile) 187 } 188 } 189 190 // Vschema Patching 191 for k, keyspaceData := range keyspaceInfoMap { 192 vSchemaFile := readFile(*baseVschemaFile) 193 if keyspaceData.shards == 0 { 194 vSchemaFile = applyJsonInMemoryPatch(vSchemaFile, `[{"op": "replace","path": "/sharded", "value": false}]`) 195 } 196 197 // Check if it is an external_db 198 if _, ok := externalDbInfoMap[k]; ok { 199 //This is no longer necessary, but we'll keep it for reference 200 //https://github.com/vitessio/vitess/pull/4868, https://github.com/vitessio/vitess/pull/5010 201 //vSchemaFile = applyJsonInMemoryPatch(vSchemaFile,`[{"op": "add","path": "/tables/*", "value": {}}]`) 202 } else { 203 var primaryTableColumns map[string]string 204 vSchemaFile, primaryTableColumns = addTablesVschemaPatch(vSchemaFile, keyspaceData.schemaFileNames) 205 206 if keyspaceData.useLookups { 207 lookup := keyspaceInfoMap[keyspaceData.lookupKeyspace] 208 vSchemaFile = addLookupDataToVschema(vSchemaFile, lookup.schemaFileNames, primaryTableColumns, lookup.keyspace) 209 } 210 } 211 212 writeVschemaFile(vSchemaFile, fmt.Sprintf("%s_vschema.json", keyspaceData.keyspace)) 213 } 214 215 // Docker Compose File Patches 216 dockerComposeFile := readFile(*baseDockerComposeFile) 217 dockerComposeFile = applyDockerComposePatches(dockerComposeFile, keyspaceInfoMap, externalDbInfoMap, vtOpts) 218 writeFile(dockerComposeFile, "docker-compose.yml") 219 } 220 221 func applyFilePatch(dockerYaml []byte, patchFile string) []byte { 222 yamlPatch, err := os.ReadFile(patchFile) 223 if err != nil { 224 log.Fatalf("reading yaml patch file %s: %s", patchFile, err) 225 } 226 227 patch, err := yamlpatch.DecodePatch(yamlPatch) 228 if err != nil { 229 log.Fatalf("decoding patch failed: %s", err) 230 } 231 232 bs, err := patch.Apply(dockerYaml) 233 if err != nil { 234 log.Fatalf("applying patch failed: %s", err) 235 } 236 return bs 237 } 238 239 func applyJsonInMemoryPatch(vSchemaFile []byte, patchString string) []byte { 240 patch, err := jsonpatch.DecodePatch([]byte(patchString)) 241 if err != nil { 242 log.Fatalf("decoding vschema patch failed: %s", err) 243 } 244 245 modified, err := patch.Apply(vSchemaFile) 246 if err != nil { 247 log.Fatalf("applying vschema patch failed: %s", err) 248 } 249 return modified 250 } 251 252 func applyInMemoryPatch(dockerYaml []byte, patchString string) []byte { 253 patch, err := yamlpatch.DecodePatch([]byte(patchString)) 254 if err != nil { 255 log.Fatalf("decoding patch failed: %s", err) 256 } 257 258 bs, err := patch.Apply(dockerYaml) 259 if err != nil { 260 log.Fatalf("applying patch failed: %s", err) 261 } 262 return bs 263 } 264 265 func createFile(filePath string) *os.File { 266 f, err := os.Create(filePath) 267 if err != nil { 268 log.Fatalf("creating %s %s", filePath, err) 269 } 270 return f 271 } 272 273 func readFile(filePath string) []byte { 274 file, err := os.ReadFile(filePath) 275 276 if err != nil { 277 log.Fatalf("reading %s: %s", filePath, err) 278 } 279 280 return file 281 } 282 283 func closeFile(file *os.File) { 284 err := file.Close() 285 if err != nil { 286 log.Fatalf("Closing schema_file.sql %s", err) 287 } 288 } 289 290 func handleError(err error) { 291 if err != nil { 292 log.Fatalf("Error: %s", err) 293 } 294 } 295 296 func appendToSqlFile(schemaFileNames []string, f *os.File) { 297 for _, file := range schemaFileNames { 298 data, err := os.ReadFile(tablesPath + file) 299 if err != nil { 300 log.Fatalf("reading %s: %s", tablesPath+file, err) 301 } 302 303 _, err = f.Write(data) 304 handleError(err) 305 306 _, err = f.WriteString("\n\n") 307 handleError(err) 308 309 err = f.Sync() 310 handleError(err) 311 } 312 } 313 314 func getTableName(sqlFile string) string { 315 sqlFileData, err := os.ReadFile(sqlFile) 316 if err != nil { 317 log.Fatalf("reading sqlFile file %s: %s", sqlFile, err) 318 } 319 320 r, _ := regexp.Compile("CREATE TABLE ([a-z_-]*) \\(") 321 rs := r.FindStringSubmatch(string(sqlFileData)) 322 // replace all ` from table name if exists 323 return strings.ReplaceAll(rs[1], "`", "") 324 } 325 326 func getPrimaryKey(sqlFile string) string { 327 sqlFileData, err := os.ReadFile(sqlFile) 328 if err != nil { 329 log.Fatalf("reading sqlFile file %s: %s", sqlFile, err) 330 } 331 332 r, _ := regexp.Compile("PRIMARY KEY \\((.*)\\).*") 333 rs := r.FindStringSubmatch(string(sqlFileData)) 334 335 return rs[1] 336 } 337 338 func getKeyColumns(sqlFile string) string { 339 sqlFileData, err := os.ReadFile(sqlFile) 340 if err != nil { 341 log.Fatalf("reading sqlFile file %s: %s", sqlFile, err) 342 } 343 344 r, _ := regexp.Compile("[^PRIMARY] (KEY|UNIQUE KEY) .*\\((.*)\\).*") 345 rs := r.FindStringSubmatch(string(sqlFileData)) 346 // replace all ` from column names if exists 347 return strings.ReplaceAll(rs[2], "`", "") 348 } 349 350 func addTablesVschemaPatch(vSchemaFile []byte, schemaFileNames []string) ([]byte, map[string]string) { 351 indexedColumns := "" 352 primaryTableColumns := make(map[string]string) 353 for _, fileName := range schemaFileNames { 354 tableName := getTableName(tablesPath + fileName) 355 indexedColumns = getPrimaryKey(tablesPath + fileName) 356 firstColumnName := strings.Split(indexedColumns, ", ")[0] 357 vSchemaFile = applyJsonInMemoryPatch(vSchemaFile, generatePrimaryVIndex(tableName, firstColumnName, "hash")) 358 primaryTableColumns[tableName] = firstColumnName 359 } 360 361 return vSchemaFile, primaryTableColumns 362 } 363 364 func addLookupDataToVschema( 365 vSchemaFile []byte, 366 schemaFileNames []string, 367 primaryTableColumns map[string]string, 368 keyspace string, 369 ) []byte { 370 for _, fileName := range schemaFileNames { 371 tableName := fileName[7 : len(fileName)-4] 372 lookupTableOwner := "" 373 374 // Find owner of lookup table 375 for primaryTableName := range primaryTableColumns { 376 if strings.HasPrefix(tableName, primaryTableName) && len(primaryTableName) > len(lookupTableOwner) { 377 lookupTableOwner = primaryTableName 378 } 379 } 380 381 indexedColumns := getKeyColumns(tablesPath + fileName) 382 firstColumnName := strings.Split(indexedColumns, ", ")[0] 383 384 // Lookup patch under "tables" 385 vSchemaFile = applyJsonInMemoryPatch(vSchemaFile, addToColumnVIndexes(lookupTableOwner, firstColumnName, tableName)) 386 387 // Generate Vschema lookup hash types 388 lookupHash := generateVschemaLookupHash(tableName, keyspace, firstColumnName, primaryTableColumns[lookupTableOwner], lookupTableOwner) 389 vSchemaFile = applyJsonInMemoryPatch(vSchemaFile, lookupHash) 390 } 391 392 return vSchemaFile 393 } 394 395 func writeVschemaFile(file []byte, fileName string) { 396 // Format json file 397 var buf bytes.Buffer 398 err := json.Indent(&buf, file, "", "\t") 399 handleError(err) 400 file = buf.Bytes() 401 402 writeFile(file, fileName) 403 } 404 405 func writeFile(file []byte, fileName string) { 406 err := os.WriteFile(fileName, file, 0644) 407 if err != nil { 408 log.Fatalf("writing %s %s", fileName, err) 409 } 410 } 411 412 func applyKeyspaceDependentPatches( 413 dockerComposeFile []byte, 414 keyspaceData keyspaceInfo, 415 externalDbInfoMap map[string]externalDbInfo, 416 opts vtOptions, 417 ) []byte { 418 var externalDbInfo externalDbInfo 419 if val, ok := externalDbInfoMap[keyspaceData.keyspace]; ok { 420 externalDbInfo = val 421 } 422 tabAlias := 0 + tabletsUsed*100 423 shard := "-" 424 var primaryTablets []string 425 if tabletsUsed == 0 { 426 primaryTablets = append(primaryTablets, "101") 427 } else { 428 primaryTablets = append(primaryTablets, strconv.Itoa((tabletsUsed+1)*100+1)) 429 } 430 interval := int(math.Floor(256 / float64(keyspaceData.shards))) 431 432 for i := 1; i < keyspaceData.shards; i++ { 433 primaryTablets = append(primaryTablets, strconv.Itoa((i+1)*100+1)) 434 } 435 436 setKeyspaceDurabilityPolicy := generateSetKeyspaceDurabilityPolicy(primaryTablets, keyspaceData.keyspace, opts) 437 dockerComposeFile = applyInMemoryPatch(dockerComposeFile, setKeyspaceDurabilityPolicy) 438 439 schemaLoad := generateSchemaload(primaryTablets, "", keyspaceData.keyspace, externalDbInfo, opts) 440 dockerComposeFile = applyInMemoryPatch(dockerComposeFile, schemaLoad) 441 442 // Append Primary and Replica Tablets 443 if keyspaceData.shards < 2 { 444 tabAlias = tabAlias + 100 445 dockerComposeFile = applyTabletPatches(dockerComposeFile, tabAlias, shard, keyspaceData, externalDbInfoMap, opts) 446 dockerComposeFile = applyShardPatches(dockerComposeFile, tabAlias, shard, keyspaceData, externalDbInfoMap, opts) 447 } else { 448 // Determine shard range 449 for i := 0; i < keyspaceData.shards; i++ { 450 if i == 0 { 451 shard = fmt.Sprintf("-%x", interval) 452 } else if i == (keyspaceData.shards - 1) { 453 shard = fmt.Sprintf("%x-", interval*i) 454 } else { 455 shard = fmt.Sprintf("%x-%x", interval*(i), interval*(i+1)) 456 } 457 tabAlias = tabAlias + 100 458 dockerComposeFile = applyTabletPatches(dockerComposeFile, tabAlias, shard, keyspaceData, externalDbInfoMap, opts) 459 dockerComposeFile = applyShardPatches(dockerComposeFile, tabAlias, shard, keyspaceData, externalDbInfoMap, opts) 460 } 461 } 462 463 tabletsUsed += len(primaryTablets) 464 return dockerComposeFile 465 } 466 467 func applyDefaultDockerPatches( 468 dockerComposeFile []byte, 469 keyspaceInfoMap map[string]keyspaceInfo, 470 externalDbInfoMap map[string]externalDbInfo, 471 opts vtOptions, 472 ) []byte { 473 474 var dbInfo externalDbInfo 475 // This is a workaround to check if there are any externalDBs defined 476 for _, keyspaceData := range keyspaceInfoMap { 477 if val, ok := externalDbInfoMap[keyspaceData.keyspace]; ok { 478 dbInfo = val 479 } 480 } 481 482 dockerComposeFile = applyInMemoryPatch(dockerComposeFile, generateVtctld(opts)) 483 dockerComposeFile = applyInMemoryPatch(dockerComposeFile, generateVtgate(opts)) 484 dockerComposeFile = applyInMemoryPatch(dockerComposeFile, generateVreplication(dbInfo, opts)) 485 dockerComposeFile = applyInMemoryPatch(dockerComposeFile, generateVTOrc(dbInfo, keyspaceInfoMap, opts)) 486 return dockerComposeFile 487 } 488 489 func applyDockerComposePatches( 490 dockerComposeFile []byte, 491 keyspaceInfoMap map[string]keyspaceInfo, 492 externalDbInfoMap map[string]externalDbInfo, 493 vtOpts vtOptions, 494 ) []byte { 495 // Vtctld, vtgate, vtwork patches. 496 dockerComposeFile = applyDefaultDockerPatches(dockerComposeFile, keyspaceInfoMap, externalDbInfoMap, vtOpts) 497 for _, keyspaceData := range keyspaceInfoMap { 498 dockerComposeFile = applyKeyspaceDependentPatches(dockerComposeFile, keyspaceData, externalDbInfoMap, vtOpts) 499 } 500 501 return dockerComposeFile 502 } 503 504 func applyShardPatches( 505 dockerComposeFile []byte, 506 tabAlias int, 507 shard string, 508 keyspaceData keyspaceInfo, 509 externalDbInfoMap map[string]externalDbInfo, 510 opts vtOptions, 511 ) []byte { 512 var dbInfo externalDbInfo 513 if val, ok := externalDbInfoMap[keyspaceData.keyspace]; ok { 514 dbInfo = val 515 } 516 dockerComposeFile = applyInMemoryPatch(dockerComposeFile, generateExternalPrimary(tabAlias, shard, keyspaceData, dbInfo, opts)) 517 return dockerComposeFile 518 } 519 520 func generateDefaultShard(tabAlias int, shard string, keyspaceData keyspaceInfo, opts vtOptions) string { 521 aliases := []int{tabAlias + 1} // primary alias, e.g. 201 522 for i := 0; i < keyspaceData.replicaTablets; i++ { 523 aliases = append(aliases, tabAlias+2+i) // replica aliases, e.g. 202, 203, ... 524 } 525 tabletDepends := make([]string, len(aliases)) 526 for i, tabletId := range aliases { 527 tabletDepends[i] = fmt.Sprintf("vttablet%d: {condition : service_healthy}", tabletId) 528 } 529 // Wait on all shard tablets to be healthy 530 dependsOn := "depends_on: {" + strings.Join(tabletDepends, ", ") + "}" 531 532 return fmt.Sprintf(` 533 - op: add 534 path: /services/init_shard_primary%[2]d 535 value: 536 image: vitess/lite:v16.0.2 537 command: ["sh", "-c", "/vt/bin/vtctlclient %[5]s InitShardPrimary -force %[4]s/%[3]s %[6]s-%[2]d "] 538 %[1]s 539 `, dependsOn, aliases[0], shard, keyspaceData.keyspace, opts.topologyFlags, opts.cell) 540 } 541 542 func generateExternalPrimary( 543 tabAlias int, 544 shard string, 545 keyspaceData keyspaceInfo, 546 dbInfo externalDbInfo, 547 opts vtOptions, 548 ) string { 549 550 aliases := []int{tabAlias + 1} // primary alias, e.g. 201 551 for i := 0; i < keyspaceData.replicaTablets; i++ { 552 aliases = append(aliases, tabAlias+2+i) // replica aliases, e.g. 202, 203, ... 553 } 554 555 externalPrimaryTab := tabAlias 556 externalDb := "0" 557 558 if dbInfo.dbName != "" { 559 externalDb = "1" 560 } else { 561 return fmt.Sprintf(``) 562 } 563 564 return fmt.Sprintf(` 565 - op: add 566 path: /services/vttablet%[1]d 567 value: 568 image: vitess/lite:v16.0.2 569 ports: 570 - "15%[1]d:%[3]d" 571 - "%[4]d" 572 - "3306" 573 volumes: 574 - ".:/script" 575 environment: 576 - TOPOLOGY_FLAGS=%[2]s 577 - WEB_PORT=%[3]d 578 - GRPC_PORT=%[4]d 579 - CELL=%[5]s 580 - SHARD=%[6]s 581 - KEYSPACE=%[7]s 582 - ROLE=primary 583 - VTHOST=vttablet%[1]d 584 - EXTERNAL_DB=%[8]s 585 - DB_PORT=%[9]s 586 - DB_HOST=%[10]s 587 - DB_USER=%[11]s 588 - DB_PASS=%[12]s 589 - DB_CHARSET=%[13]s 590 command: ["sh", "-c", "[ $$EXTERNAL_DB -eq 1 ] && /script/vttablet-up.sh %[1]d || exit 0"] 591 depends_on: 592 - vtctld 593 healthcheck: 594 test: ["CMD-SHELL","curl -s --fail --show-error localhost:%[3]d/debug/health"] 595 interval: 30s 596 timeout: 10s 597 retries: 15 598 `, externalPrimaryTab, opts.topologyFlags, opts.webPort, opts.gRpcPort, opts.cell, shard, keyspaceData.keyspace, externalDb, dbInfo.dbPort, dbInfo.dbHost, dbInfo.dbUser, dbInfo.dbPass, dbInfo.dbCharset) 599 } 600 601 func applyTabletPatches( 602 dockerComposeFile []byte, 603 tabAlias int, 604 shard string, 605 keyspaceData keyspaceInfo, 606 externalDbInfoMap map[string]externalDbInfo, 607 opts vtOptions, 608 ) []byte { 609 var dbInfo externalDbInfo 610 if val, ok := externalDbInfoMap[keyspaceData.keyspace]; ok { 611 dbInfo = val 612 } 613 dockerComposeFile = applyInMemoryPatch(dockerComposeFile, generateDefaultTablet(tabAlias+1, shard, "primary", keyspaceData.keyspace, dbInfo, opts)) 614 for i := 0; i < keyspaceData.replicaTablets; i++ { 615 dockerComposeFile = applyInMemoryPatch(dockerComposeFile, generateDefaultTablet(tabAlias+2+i, shard, "replica", keyspaceData.keyspace, dbInfo, opts)) 616 } 617 return dockerComposeFile 618 } 619 620 func generateDefaultTablet(tabAlias int, shard, role, keyspace string, dbInfo externalDbInfo, opts vtOptions) string { 621 externalDb := "0" 622 if dbInfo.dbName != "" { 623 externalDb = "1" 624 } 625 626 return fmt.Sprintf(` 627 - op: add 628 path: /services/vttablet%[1]d 629 value: 630 image: vitess/lite:v16.0.2 631 ports: 632 - "15%[1]d:%[4]d" 633 - "%[5]d" 634 - "3306" 635 volumes: 636 - ".:/script" 637 environment: 638 - TOPOLOGY_FLAGS=%[7]s 639 - WEB_PORT=%[4]d 640 - GRPC_PORT=%[5]d 641 - CELL=%[8]s 642 - KEYSPACE=%[6]s 643 - SHARD=%[2]s 644 - ROLE=%[3]s 645 - VTHOST=vttablet%[1]d 646 - EXTERNAL_DB=%[9]s 647 - DB_PORT=%[10]s 648 - DB_HOST=%[11]s 649 - DB_USER=%[12]s 650 - DB_PASS=%[13]s 651 - DB_CHARSET=%[14]s 652 command: ["sh", "-c", "/script/vttablet-up.sh %[1]d"] 653 depends_on: 654 - vtctld 655 healthcheck: 656 test: ["CMD-SHELL","curl -s --fail --show-error localhost:%[4]d/debug/health"] 657 interval: 30s 658 timeout: 10s 659 retries: 15 660 `, tabAlias, shard, role, opts.webPort, opts.gRpcPort, keyspace, opts.topologyFlags, opts.cell, externalDb, dbInfo.dbPort, dbInfo.dbHost, dbInfo.dbUser, dbInfo.dbPass, dbInfo.dbCharset) 661 } 662 663 func generateVtctld(opts vtOptions) string { 664 return fmt.Sprintf(` 665 - op: add 666 path: /services/vtctld 667 value: 668 image: vitess/lite:v16.0.2 669 ports: 670 - "15000:%[1]d" 671 - "%[2]d" 672 command: ["sh", "-c", " /vt/bin/vtctld \ 673 %[3]s \ 674 --cell %[4]s \ 675 --service_map 'grpc-vtctl,grpc-vtctld' \ 676 --backup_storage_implementation file \ 677 --file_backup_storage_root /vt/vtdataroot/backups \ 678 --logtostderr=true \ 679 --port %[1]d \ 680 --grpc_port %[2]d \ 681 "] 682 volumes: 683 - .:/script 684 depends_on: 685 - consul1 686 - consul2 687 - consul3 688 depends_on: 689 external_db_host: 690 condition: service_healthy 691 `, opts.webPort, opts.gRpcPort, opts.topologyFlags, opts.cell) 692 } 693 694 func generateVtgate(opts vtOptions) string { 695 return fmt.Sprintf(` 696 - op: add 697 path: /services/vtgate 698 value: 699 image: vitess/lite:v16.0.2 700 ports: 701 - "15099:%[1]d" 702 - "%[2]d" 703 - "15306:%[3]d" 704 command: ["sh", "-c", "/script/run-forever.sh /vt/bin/vtgate \ 705 %[4]s \ 706 --logtostderr=true \ 707 --port %[1]d \ 708 --grpc_port %[2]d \ 709 --mysql_server_port %[3]d \ 710 --mysql_auth_server_impl none \ 711 --cell %[5]s \ 712 --cells_to_watch %[5]s \ 713 --tablet_types_to_wait PRIMARY,REPLICA,RDONLY \ 714 --service_map 'grpc-vtgateservice' \ 715 --normalize_queries=true \ 716 "] 717 volumes: 718 - .:/script 719 depends_on: 720 - vtctld 721 `, opts.webPort, opts.gRpcPort, opts.mySqlPort, opts.topologyFlags, opts.cell) 722 } 723 724 func generateVTOrc(dbInfo externalDbInfo, keyspaceInfoMap map[string]keyspaceInfo, opts vtOptions) string { 725 externalDb := "0" 726 if dbInfo.dbName != "" { 727 externalDb = "1" 728 } 729 730 var depends []string 731 for _, keyspaceData := range keyspaceInfoMap { 732 depends = append(depends, "set_keyspace_durability_policy_"+keyspaceData.keyspace) 733 } 734 depends = append(depends, "vtctld") 735 dependsOn := "depends_on: [" + strings.Join(depends, ", ") + "]" 736 737 return fmt.Sprintf(` 738 - op: add 739 path: /services/vtorc 740 value: 741 image: vitess/lite:v16.0.2 742 volumes: 743 - ".:/script" 744 environment: 745 - WEB_PORT=%[1]d 746 - TOPOLOGY_FLAGS=%[2]s 747 - EXTERNAL_DB=%[3]s 748 - DB_USER=%[4]s 749 - DB_PASS=%[5]s 750 ports: 751 - "13000:%[1]d" 752 command: ["sh", "-c", "/script/vtorc-up.sh"] 753 %[6]s 754 `, opts.webPort, opts.topologyFlags, externalDb, dbInfo.dbUser, dbInfo.dbPass, dependsOn) 755 } 756 757 func generateVreplication(dbInfo externalDbInfo, opts vtOptions) string { 758 externalDb := "0" 759 if dbInfo.dbName != "" { 760 externalDb = "1" 761 } 762 return fmt.Sprintf(` 763 - op: add 764 path: /services/vreplication 765 value: 766 image: vitess/lite:v16.0.2 767 volumes: 768 - ".:/script" 769 environment: 770 - TOPOLOGY_FLAGS=%[1]s 771 - EXTERNAL_DB=%[2]s 772 command: ["sh", "-c", "[ $$EXTERNAL_DB -eq 1 ] && /script/externaldb_vreplication.sh || exit 0"] 773 depends_on: 774 - vtctld 775 `, opts.topologyFlags, externalDb) 776 } 777 778 func generateSetKeyspaceDurabilityPolicy( 779 tabletAliases []string, 780 keyspace string, 781 opts vtOptions, 782 ) string { 783 // Formatting for list in yaml 784 var aliases []string 785 for _, tabletId := range tabletAliases { 786 aliases = append(aliases, "vttablet"+tabletId) 787 } 788 dependsOn := "depends_on: [" + strings.Join(aliases, ", ") + "]" 789 790 return fmt.Sprintf(` 791 - op: add 792 path: /services/set_keyspace_durability_policy_%[3]s 793 value: 794 image: vitess/lite:v16.0.2 795 volumes: 796 - ".:/script" 797 environment: 798 - GRPC_PORT=%[2]d 799 - KEYSPACES=%[3]s 800 command: ["sh", "-c", "/script/set_keyspace_durability_policy.sh"] 801 %[1]s 802 `, dependsOn, opts.gRpcPort, keyspace) 803 } 804 805 func generateSchemaload( 806 tabletAliases []string, 807 postLoadFile string, 808 keyspace string, 809 dbInfo externalDbInfo, 810 opts vtOptions, 811 ) string { 812 targetTab := tabletAliases[0] 813 schemaFileName := fmt.Sprintf("%s_schema_file.sql", keyspace) 814 externalDb := "0" 815 816 if dbInfo.dbName != "" { 817 schemaFileName = "" 818 externalDb = "1" 819 } 820 821 // Formatting for list in yaml 822 for i, tabletId := range tabletAliases { 823 tabletAliases[i] = "vttablet" + tabletId + ": " + "{condition : service_healthy}" 824 } 825 dependsOn := "depends_on: {" + strings.Join(tabletAliases, ", ") + "}" 826 827 return fmt.Sprintf(` 828 - op: add 829 path: /services/schemaload_%[7]s 830 value: 831 image: vitess/lite:v16.0.2 832 volumes: 833 - ".:/script" 834 environment: 835 - TOPOLOGY_FLAGS=%[3]s 836 - WEB_PORT=%[4]d 837 - GRPC_PORT=%[5]d 838 - CELL=%[6]s 839 - KEYSPACE=%[7]s 840 - TARGETTAB=%[6]s-0000000%[2]s 841 - SLEEPTIME=15 842 - VSCHEMA_FILE=%[7]s_vschema.json 843 - SCHEMA_FILES=%[9]s 844 - POST_LOAD_FILE=%[8]s 845 - EXTERNAL_DB=%[10]s 846 command: ["sh", "-c", "/script/schemaload.sh"] 847 %[1]s 848 `, dependsOn, targetTab, opts.topologyFlags, opts.webPort, opts.gRpcPort, opts.cell, keyspace, postLoadFile, schemaFileName, externalDb) 849 } 850 851 func generatePrimaryVIndex(tableName, column, name string) string { 852 return fmt.Sprintf(` 853 [{"op": "add", 854 "path": "/tables/%[1]s", 855 "value": 856 {"column_vindexes": [ 857 { 858 "column": "%[2]s", 859 "name": "%[3]s" 860 } 861 ]} 862 }] 863 `, tableName, column, name) 864 } 865 866 func generateVschemaLookupHash(tableName, tableKeyspace, from, to, owner string) string { 867 return fmt.Sprintf(` 868 [{"op": "add", 869 "path": "/vindexes/%[1]s", 870 "value": 871 {"type": "lookup_hash", 872 "params": { 873 "table": "%[2]s.%[1]s", 874 "from": "%[3]s", 875 "to": "%[4]s", 876 "autocommit": "true" 877 }, 878 "owner": "%[5]s" 879 } 880 }] 881 `, tableName, tableKeyspace, from, to, owner) 882 } 883 884 func addToColumnVIndexes(tableName, column, referenceName string) string { 885 return fmt.Sprintf(` 886 [{"op": "add", 887 "path": "/tables/%[1]s/column_vindexes/-", 888 "value": 889 { 890 "column": "%[2]s", 891 "name": "%[3]s" 892 } 893 }] 894 `, tableName, column, referenceName) 895 }