From dddaac99c4531d88a8f1e1a4aff3f32f77e5be78 Mon Sep 17 00:00:00 2001 From: Andrew Mason Date: Thu, 21 Sep 2023 07:50:21 -0400 Subject: [PATCH 01/11] sort testdata map so i can make sure i have them all Signed-off-by: Andrew Mason --- go/flags/endtoend/flags_test.go | 2 +- go/flags/endtoend/topo2topo.txt | 35 +++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 go/flags/endtoend/topo2topo.txt diff --git a/go/flags/endtoend/flags_test.go b/go/flags/endtoend/flags_test.go index 713d305fc61..89e884c73ed 100644 --- a/go/flags/endtoend/flags_test.go +++ b/go/flags/endtoend/flags_test.go @@ -92,8 +92,8 @@ var ( "vtaclcheck": vtaclcheckTxt, "vtbackup": vtbackupTxt, "vtcombo": vtcomboTxt, - "vtctld": vtctldTxt, "vtctlclient": vtctlclientTxt, + "vtctld": vtctldTxt, "vtctldclient": vtctldclientTxt, "vtexplain": vtexplainTxt, "vtgate": vtgateTxt, diff --git a/go/flags/endtoend/topo2topo.txt b/go/flags/endtoend/topo2topo.txt new file mode 100644 index 00000000000..45641e30a04 --- /dev/null +++ b/go/flags/endtoend/topo2topo.txt @@ -0,0 +1,35 @@ +Usage of topo2topo: + --alsologtostderr log to standard error as well as files + --compare compares data between topologies + --config-file string Full path of the config file (with extension) to use. If set, --config-path, --config-type, and --config-name are ignored. + --config-file-not-found-handling ConfigFileNotFoundHandling Behavior when a config file is not found. (Options: error, exit, ignore, warn) (default warn) + --config-name string Name of the config file (without extension) to search for. (default "vtconfig") + --config-path strings Paths to search for config files in. (default [{{ .Workdir }}]) + --config-persistence-min-interval duration minimum interval between persisting dynamic config changes back to disk (if no change has occurred, nothing is done). (default 1s) + --config-type string Config file type (omit to infer config type from file extension). + --do-keyspaces copies the keyspace information + --do-routing-rules copies the routing rules + --do-shard-replications copies the shard replication information + --do-shards copies the shard information + --do-tablets copies the tablet information + --from_implementation string topology implementation to copy data from + --from_root string topology server root to copy data from + --from_server string topology server address to copy data from + -h, --help display usage and exit + --keep_logs duration keep logs for this long (using ctime) (zero to keep forever) + --keep_logs_by_mtime duration keep logs for this long (using mtime) (zero to keep forever) + --log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0) + --log_dir string If non-empty, write log files in this directory + --log_err_stacks log stack traces for errors + --log_rotate_max_size uint size in bytes at which logs are rotated (glog.MaxSize) (default 1887436800) + --logtostderr log to standard error instead of files + --pprof strings enable profiling + --purge_logs_interval duration how often try to remove old logs (default 1h0m0s) + --security_policy string the name of a registered security policy to use for controlling access to URLs - empty means allow all for anyone (built-in policies: deny-all, read-only) + --stderrthreshold severity logs at or above this threshold go to stderr (default 1) + --to_implementation string topology implementation to copy data to + --to_root string topology server root to copy data to + --to_server string topology server address to copy data to + --v Level log level for V logs + -v, --version print binary version + --vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging From 2e19df745e36e5acae22ac6ca58241140c9921af Mon Sep 17 00:00:00 2001 From: Andrew Mason Date: Thu, 21 Sep 2023 07:53:16 -0400 Subject: [PATCH 02/11] add topo2topo flags Signed-off-by: Andrew Mason --- go/flags/endtoend/flags_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/go/flags/endtoend/flags_test.go b/go/flags/endtoend/flags_test.go index 89e884c73ed..eceab3de6fd 100644 --- a/go/flags/endtoend/flags_test.go +++ b/go/flags/endtoend/flags_test.go @@ -41,6 +41,9 @@ var ( //go:embed mysqlctld.txt mysqlctldTxt string + //go:embed topo2topo.txt + topo2topoTxt string + //go:embed vtaclcheck.txt vtaclcheckTxt string @@ -89,6 +92,7 @@ var ( helpOutput = map[string]string{ "mysqlctl": mysqlctlTxt, "mysqlctld": mysqlctldTxt, + "topo2topo": topo2topoTxt, "vtaclcheck": vtaclcheckTxt, "vtbackup": vtbackupTxt, "vtcombo": vtcomboTxt, From 5e634afc2648d25ae332fce3bace98154f2736c1 Mon Sep 17 00:00:00 2001 From: Andrew Mason Date: Thu, 21 Sep 2023 10:03:28 -0400 Subject: [PATCH 03/11] migrate topo2topo to cobra Signed-off-by: Andrew Mason --- .../topo2topo/{ => cli}/plugin_consultopo.go | 2 +- .../topo2topo/{ => cli}/plugin_etcd2topo.go | 2 +- go/cmd/topo2topo/{ => cli}/plugin_zk2topo.go | 2 +- go/cmd/topo2topo/cli/topo2topo.go | 158 ++++++++++++++++++ go/cmd/topo2topo/docgen/main.go | 37 ++++ go/cmd/topo2topo/topo2topo.go | 123 +------------- go/flags/endtoend/topo2topo.txt | 13 +- go/vt/topo/helpers/copy.go | 54 +++--- 8 files changed, 244 insertions(+), 147 deletions(-) rename go/cmd/topo2topo/{ => cli}/plugin_consultopo.go (98%) rename go/cmd/topo2topo/{ => cli}/plugin_etcd2topo.go (98%) rename go/cmd/topo2topo/{ => cli}/plugin_zk2topo.go (98%) create mode 100644 go/cmd/topo2topo/cli/topo2topo.go create mode 100644 go/cmd/topo2topo/docgen/main.go diff --git a/go/cmd/topo2topo/plugin_consultopo.go b/go/cmd/topo2topo/cli/plugin_consultopo.go similarity index 98% rename from go/cmd/topo2topo/plugin_consultopo.go rename to go/cmd/topo2topo/cli/plugin_consultopo.go index 59d6774fdbc..a128f294a42 100644 --- a/go/cmd/topo2topo/plugin_consultopo.go +++ b/go/cmd/topo2topo/cli/plugin_consultopo.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cli // This plugin imports consultopo to register the consul implementation of TopoServer. diff --git a/go/cmd/topo2topo/plugin_etcd2topo.go b/go/cmd/topo2topo/cli/plugin_etcd2topo.go similarity index 98% rename from go/cmd/topo2topo/plugin_etcd2topo.go rename to go/cmd/topo2topo/cli/plugin_etcd2topo.go index d99ef51d4af..5a51923cf00 100644 --- a/go/cmd/topo2topo/plugin_etcd2topo.go +++ b/go/cmd/topo2topo/cli/plugin_etcd2topo.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cli // This plugin imports etcd2topo to register the etcd2 implementation of TopoServer. diff --git a/go/cmd/topo2topo/plugin_zk2topo.go b/go/cmd/topo2topo/cli/plugin_zk2topo.go similarity index 98% rename from go/cmd/topo2topo/plugin_zk2topo.go rename to go/cmd/topo2topo/cli/plugin_zk2topo.go index 62dda455df7..66d14988c75 100644 --- a/go/cmd/topo2topo/plugin_zk2topo.go +++ b/go/cmd/topo2topo/cli/plugin_zk2topo.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cli import ( // Imports and register the zk2 TopologyServer diff --git a/go/cmd/topo2topo/cli/topo2topo.go b/go/cmd/topo2topo/cli/topo2topo.go new file mode 100644 index 00000000000..6e7e173872b --- /dev/null +++ b/go/cmd/topo2topo/cli/topo2topo.go @@ -0,0 +1,158 @@ +/* +Copyright 2023 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cli + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + + "vitess.io/vitess/go/acl" + "vitess.io/vitess/go/vt/grpccommon" + "vitess.io/vitess/go/vt/logutil" + "vitess.io/vitess/go/vt/servenv" + "vitess.io/vitess/go/vt/topo" + "vitess.io/vitess/go/vt/topo/helpers" +) + +var ( + fromImplementation string + fromServerAddress string + fromRoot string + toImplementation string + toServerAddress string + toRoot string + compare bool + doKeyspaces bool + doShards bool + doShardReplications bool + doTablets bool + doRoutingRules bool + + Main = &cobra.Command{ + Use: "topo2topo", + Short: "topo2topo copies Vitess topology data from one topo server to another.", + Long: `topo2topo copies Vitess topology data from one topo server to another. +It can also be used to compare data between two topologies.`, + Args: cobra.NoArgs, + PreRunE: servenv.CobraPreRunE, + RunE: run, + } +) + +func init() { + servenv.MoveFlagsToCobraCommand(Main) + + Main.Flags().StringVar(&fromImplementation, "from_implementation", fromImplementation, "topology implementation to copy data from") + Main.Flags().StringVar(&fromServerAddress, "from_server", fromServerAddress, "topology server address to copy data from") + Main.Flags().StringVar(&fromRoot, "from_root", fromRoot, "topology server root to copy data from") + Main.Flags().StringVar(&toImplementation, "to_implementation", toImplementation, "topology implementation to copy data to") + Main.Flags().StringVar(&toServerAddress, "to_server", toServerAddress, "topology server address to copy data to") + Main.Flags().StringVar(&toRoot, "to_root", toRoot, "topology server root to copy data to") + Main.Flags().BoolVar(&compare, "compare", compare, "compares data between topologies") + Main.Flags().BoolVar(&doKeyspaces, "do-keyspaces", doKeyspaces, "copies the keyspace information") + Main.Flags().BoolVar(&doShards, "do-shards", doShards, "copies the shard information") + Main.Flags().BoolVar(&doShardReplications, "do-shard-replications", doShardReplications, "copies the shard replication information") + Main.Flags().BoolVar(&doTablets, "do-tablets", doTablets, "copies the tablet information") + Main.Flags().BoolVar(&doRoutingRules, "do-routing-rules", doRoutingRules, "copies the routing rules") + + acl.RegisterFlags(Main.Flags()) + grpccommon.RegisterFlags(Main.Flags()) +} + +func run(cmd *cobra.Command, args []string) error { + defer logutil.Flush() + servenv.Init() + + fromTS, err := topo.OpenServer(fromImplementation, fromServerAddress, fromRoot) + if err != nil { + return fmt.Errorf("Cannot open 'from' topo %v: %w", fromImplementation, err) + } + toTS, err := topo.OpenServer(toImplementation, toServerAddress, toRoot) + if err != nil { + return fmt.Errorf("Cannot open 'to' topo %v: %w", toImplementation, err) + } + + ctx := context.Background() + + if compare { + return compareTopos(ctx, fromTS, toTS) + } + + return copyTopos(ctx, fromTS, toTS) +} + +func copyTopos(ctx context.Context, fromTS, toTS *topo.Server) error { + if doKeyspaces { + if err := helpers.CopyKeyspaces(ctx, fromTS, toTS); err != nil { + return err + } + } + if doShards { + if err := helpers.CopyShards(ctx, fromTS, toTS); err != nil { + return err + } + } + if doShardReplications { + if err := helpers.CopyShardReplications(ctx, fromTS, toTS); err != nil { + return err + } + } + if doTablets { + if err := helpers.CopyTablets(ctx, fromTS, toTS); err != nil { + return err + } + } + if doRoutingRules { + if err := helpers.CopyRoutingRules(ctx, fromTS, toTS); err != nil { + return err + } + } + + return nil +} + +func compareTopos(ctx context.Context, fromTS, toTS *topo.Server) (err error) { + if doKeyspaces { + err = helpers.CompareKeyspaces(ctx, fromTS, toTS) + if err != nil { + return fmt.Errorf("Compare keyspaces failed: %w", err) + } + } + if doShards { + err = helpers.CompareShards(ctx, fromTS, toTS) + if err != nil { + return fmt.Errorf("Compare shards failed: %w", err) + } + } + if doShardReplications { + err = helpers.CompareShardReplications(ctx, fromTS, toTS) + if err != nil { + return fmt.Errorf("Compare shard replications failed: %w", err) + } + } + if doTablets { + err = helpers.CompareTablets(ctx, fromTS, toTS) + if err != nil { + return fmt.Errorf("Compare tablets failed: %w", err) + } + } + + fmt.Println("Topologies are in sync") + return nil +} diff --git a/go/cmd/topo2topo/docgen/main.go b/go/cmd/topo2topo/docgen/main.go new file mode 100644 index 00000000000..c1d29fff086 --- /dev/null +++ b/go/cmd/topo2topo/docgen/main.go @@ -0,0 +1,37 @@ +/* +Copyright 2023 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "github.com/spf13/cobra" + + "vitess.io/vitess/go/cmd/internal/docgen" + "vitess.io/vitess/go/cmd/topo2topo/cli" +) + +func main() { + var dir string + cmd := cobra.Command{ + Use: "docgen [-d ]", + RunE: func(cmd *cobra.Command, args []string) error { + return docgen.GenerateMarkdownTree(cli.Main, dir) + }, + } + + cmd.Flags().StringVarP(&dir, "dir", "d", "doc", "output directory to write documentation") + _ = cmd.Execute() +} diff --git a/go/cmd/topo2topo/topo2topo.go b/go/cmd/topo2topo/topo2topo.go index 157960548b8..c1276ebf504 100644 --- a/go/cmd/topo2topo/topo2topo.go +++ b/go/cmd/topo2topo/topo2topo.go @@ -17,132 +17,15 @@ limitations under the License. package main import ( - "context" - "fmt" - "os" - - "github.com/spf13/pflag" - - "vitess.io/vitess/go/acl" + "vitess.io/vitess/go/cmd/topo2topo/cli" "vitess.io/vitess/go/exit" - "vitess.io/vitess/go/vt/grpccommon" "vitess.io/vitess/go/vt/log" - "vitess.io/vitess/go/vt/logutil" - "vitess.io/vitess/go/vt/servenv" - "vitess.io/vitess/go/vt/topo" - "vitess.io/vitess/go/vt/topo/helpers" -) - -var ( - fromImplementation string - fromServerAddress string - fromRoot string - toImplementation string - toServerAddress string - toRoot string - compare bool - doKeyspaces bool - doShards bool - doShardReplications bool - doTablets bool - doRoutingRules bool ) -func init() { - servenv.OnParse(func(fs *pflag.FlagSet) { - fs.StringVar(&fromImplementation, "from_implementation", fromImplementation, "topology implementation to copy data from") - fs.StringVar(&fromServerAddress, "from_server", fromServerAddress, "topology server address to copy data from") - fs.StringVar(&fromRoot, "from_root", fromRoot, "topology server root to copy data from") - fs.StringVar(&toImplementation, "to_implementation", toImplementation, "topology implementation to copy data to") - fs.StringVar(&toServerAddress, "to_server", toServerAddress, "topology server address to copy data to") - fs.StringVar(&toRoot, "to_root", toRoot, "topology server root to copy data to") - fs.BoolVar(&compare, "compare", compare, "compares data between topologies") - fs.BoolVar(&doKeyspaces, "do-keyspaces", doKeyspaces, "copies the keyspace information") - fs.BoolVar(&doShards, "do-shards", doShards, "copies the shard information") - fs.BoolVar(&doShardReplications, "do-shard-replications", doShardReplications, "copies the shard replication information") - fs.BoolVar(&doTablets, "do-tablets", doTablets, "copies the tablet information") - fs.BoolVar(&doRoutingRules, "do-routing-rules", doRoutingRules, "copies the routing rules") - - acl.RegisterFlags(fs) - }) -} - func main() { defer exit.RecoverAll() - defer logutil.Flush() - fs := pflag.NewFlagSet("topo2topo", pflag.ExitOnError) - grpccommon.RegisterFlags(fs) - log.RegisterFlags(fs) - logutil.RegisterFlags(fs) - - servenv.ParseFlags("topo2topo") - servenv.Init() - - fromTS, err := topo.OpenServer(fromImplementation, fromServerAddress, fromRoot) - if err != nil { - log.Exitf("Cannot open 'from' topo %v: %v", fromImplementation, err) - } - toTS, err := topo.OpenServer(toImplementation, toServerAddress, toRoot) - if err != nil { - log.Exitf("Cannot open 'to' topo %v: %v", toImplementation, err) - } - - ctx := context.Background() - - if compare { - compareTopos(ctx, fromTS, toTS) - return - } - copyTopos(ctx, fromTS, toTS) -} - -func copyTopos(ctx context.Context, fromTS, toTS *topo.Server) { - if doKeyspaces { - helpers.CopyKeyspaces(ctx, fromTS, toTS) - } - if doShards { - helpers.CopyShards(ctx, fromTS, toTS) - } - if doShardReplications { - helpers.CopyShardReplications(ctx, fromTS, toTS) - } - if doTablets { - helpers.CopyTablets(ctx, fromTS, toTS) - } - if doRoutingRules { - helpers.CopyRoutingRules(ctx, fromTS, toTS) - } -} - -func compareTopos(ctx context.Context, fromTS, toTS *topo.Server) { - var err error - if doKeyspaces { - err = helpers.CompareKeyspaces(ctx, fromTS, toTS) - if err != nil { - log.Exitf("Compare keyspaces failed: %v", err) - } - } - if doShards { - err = helpers.CompareShards(ctx, fromTS, toTS) - if err != nil { - log.Exitf("Compare shards failed: %v", err) - } - } - if doShardReplications { - err = helpers.CompareShardReplications(ctx, fromTS, toTS) - if err != nil { - log.Exitf("Compare shard replications failed: %v", err) - } - } - if doTablets { - err = helpers.CompareTablets(ctx, fromTS, toTS) - if err != nil { - log.Exitf("Compare tablets failed: %v", err) - } - } - if err == nil { - fmt.Println("Topologies are in sync") - os.Exit(0) + if err := cli.Main.Execute(); err != nil { + log.Exitf("%s", err) } } diff --git a/go/flags/endtoend/topo2topo.txt b/go/flags/endtoend/topo2topo.txt index 45641e30a04..4391a32a1a8 100644 --- a/go/flags/endtoend/topo2topo.txt +++ b/go/flags/endtoend/topo2topo.txt @@ -1,4 +1,10 @@ -Usage of topo2topo: +topo2topo copies Vitess topology data from one topo server to another. +It can also be used to compare data between two topologies. + +Usage: + topo2topo [flags] + +Flags: --alsologtostderr log to standard error as well as files --compare compares data between topologies --config-file string Full path of the config file (with extension) to use. If set, --config-path, --config-type, and --config-name are ignored. @@ -15,7 +21,10 @@ Usage of topo2topo: --from_implementation string topology implementation to copy data from --from_root string topology server root to copy data from --from_server string topology server address to copy data from - -h, --help display usage and exit + --grpc_enable_tracing Enable gRPC tracing. + --grpc_max_message_size int Maximum allowed RPC message size. Larger messages will be rejected by gRPC with the error 'exceeding the max size'. (default 16777216) + --grpc_prometheus Enable gRPC monitoring with Prometheus. + -h, --help help for topo2topo --keep_logs duration keep logs for this long (using ctime) (zero to keep forever) --keep_logs_by_mtime duration keep logs for this long (using mtime) (zero to keep forever) --log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0) diff --git a/go/vt/topo/helpers/copy.go b/go/vt/topo/helpers/copy.go index 27d39179688..0df706eba31 100644 --- a/go/vt/topo/helpers/copy.go +++ b/go/vt/topo/helpers/copy.go @@ -20,6 +20,7 @@ package helpers import ( "context" + "fmt" "google.golang.org/protobuf/proto" @@ -32,17 +33,17 @@ import ( ) // CopyKeyspaces will create the keyspaces in the destination topo. -func CopyKeyspaces(ctx context.Context, fromTS, toTS *topo.Server) { +func CopyKeyspaces(ctx context.Context, fromTS, toTS *topo.Server) error { keyspaces, err := fromTS.GetKeyspaces(ctx) if err != nil { - log.Fatalf("GetKeyspaces: %v", err) + return fmt.Errorf("GetKeyspaces: %w", err) } for _, keyspace := range keyspaces { ki, err := fromTS.GetKeyspace(ctx, keyspace) if err != nil { - log.Fatalf("GetKeyspace(%v): %v", keyspace, err) + return fmt.Errorf("GetKeyspace(%v): %w", keyspace, err) } if err := toTS.CreateKeyspace(ctx, keyspace, ki.Keyspace); err != nil { @@ -70,64 +71,67 @@ func CopyKeyspaces(ctx context.Context, fromTS, toTS *topo.Server) { log.Errorf("GetVSchema(%v): %v", keyspace, err) } } + + return nil } // CopyShards will create the shards in the destination topo. -func CopyShards(ctx context.Context, fromTS, toTS *topo.Server) { +func CopyShards(ctx context.Context, fromTS, toTS *topo.Server) error { keyspaces, err := fromTS.GetKeyspaces(ctx) if err != nil { - log.Fatalf("fromTS.GetKeyspaces: %v", err) + return fmt.Errorf("fromTS.GetKeyspaces: %w", err) } for _, keyspace := range keyspaces { shards, err := fromTS.GetShardNames(ctx, keyspace) if err != nil { - log.Fatalf("GetShardNames(%v): %v", keyspace, err) - return + return fmt.Errorf("GetShardNames(%v): %w", keyspace, err) } for _, shard := range shards { si, err := fromTS.GetShard(ctx, keyspace, shard) if err != nil { - log.Fatalf("GetShard(%v, %v): %v", keyspace, shard, err) + return fmt.Errorf("GetShard(%v, %v): %w", keyspace, shard, err) } if err := toTS.CreateShard(ctx, keyspace, shard); err != nil { if topo.IsErrType(err, topo.NodeExists) { log.Warningf("shard %v/%v already exists", keyspace, shard) } else { - log.Fatalf("CreateShard(%v, %v): %v", keyspace, shard, err) + return fmt.Errorf("CreateShard(%v, %v): %w", keyspace, shard, err) } } if _, err := toTS.UpdateShardFields(ctx, keyspace, shard, func(toSI *topo.ShardInfo) error { toSI.Shard = si.Shard.CloneVT() return nil }); err != nil { - log.Fatalf("UpdateShardFields(%v, %v): %v", keyspace, shard, err) + return fmt.Errorf("UpdateShardFields(%v, %v): %w", keyspace, shard, err) } } } + + return nil } // CopyTablets will create the tablets in the destination topo. -func CopyTablets(ctx context.Context, fromTS, toTS *topo.Server) { +func CopyTablets(ctx context.Context, fromTS, toTS *topo.Server) error { cells, err := fromTS.GetKnownCells(ctx) if err != nil { - log.Fatalf("fromTS.GetKnownCells: %v", err) + return fmt.Errorf("fromTS.GetKnownCells: %w", err) } for _, cell := range cells { tabletAliases, err := fromTS.GetTabletAliasesByCell(ctx, cell) if err != nil { - log.Fatalf("GetTabletsByCell(%v): %v", cell, err) + return fmt.Errorf("GetTabletsByCell(%v): %w", cell, err) } else { for _, tabletAlias := range tabletAliases { // read the source tablet ti, err := fromTS.GetTablet(ctx, tabletAlias) if err != nil { - log.Fatalf("GetTablet(%v): %v", tabletAlias, err) + return fmt.Errorf("GetTablet(%v): %w", tabletAlias, err) } // try to create the destination @@ -141,37 +145,39 @@ func CopyTablets(ctx context.Context, fromTS, toTS *topo.Server) { }) } if err != nil { - log.Fatalf("CreateTablet(%v): %v", tabletAlias, err) + return fmt.Errorf("CreateTablet(%v): %w", tabletAlias, err) } } } } + + return nil } // CopyShardReplications will create the ShardReplication objects in // the destination topo. -func CopyShardReplications(ctx context.Context, fromTS, toTS *topo.Server) { +func CopyShardReplications(ctx context.Context, fromTS, toTS *topo.Server) error { keyspaces, err := fromTS.GetKeyspaces(ctx) if err != nil { - log.Fatalf("fromTS.GetKeyspaces: %v", err) + return fmt.Errorf("fromTS.GetKeyspaces: %w", err) } cells, err := fromTS.GetCellInfoNames(ctx) if err != nil { - log.Fatalf("GetCellInfoNames(): %v", err) + return fmt.Errorf("GetCellInfoNames(): %w", err) } for _, keyspace := range keyspaces { shards, err := fromTS.GetShardNames(ctx, keyspace) if err != nil { - log.Fatalf("GetShardNames(%v): %v", keyspace, err) + return fmt.Errorf("GetShardNames(%v): %w", keyspace, err) } for _, shard := range shards { for _, cell := range cells { sri, err := fromTS.GetShardReplication(ctx, cell, keyspace, shard) if err != nil { - log.Fatalf("GetShardReplication(%v, %v, %v): %v", cell, keyspace, shard, err) + return fmt.Errorf("GetShardReplication(%v, %v, %v): %w", cell, keyspace, shard, err) } sriNodes := map[string]struct{}{} @@ -202,15 +208,19 @@ func CopyShardReplications(ctx context.Context, fromTS, toTS *topo.Server) { } } } + + return nil } // CopyRoutingRules will create the routing rules in the destination topo. -func CopyRoutingRules(ctx context.Context, fromTS, toTS *topo.Server) { +func CopyRoutingRules(ctx context.Context, fromTS, toTS *topo.Server) error { rr, err := fromTS.GetRoutingRules(ctx) if err != nil { - log.Fatalf("GetRoutingRules: %v", err) + return fmt.Errorf("GetRoutingRules: %w", err) } if err := toTS.SaveRoutingRules(ctx, rr); err != nil { log.Errorf("SaveRoutingRules(%v): %v", rr, err) } + + return nil } From 3e22c5c9e25c809330714f09c9c21ae7d5ac7a40 Mon Sep 17 00:00:00 2001 From: Andrew Mason Date: Thu, 21 Sep 2023 10:20:47 -0400 Subject: [PATCH 04/11] add vtgateclienttest flags Signed-off-by: Andrew Mason --- go/flags/endtoend/flags_test.go | 40 +++++++++-------- go/flags/endtoend/vtgateclienttest.txt | 60 ++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 18 deletions(-) create mode 100644 go/flags/endtoend/vtgateclienttest.txt diff --git a/go/flags/endtoend/flags_test.go b/go/flags/endtoend/flags_test.go index eceab3de6fd..25cca54caf9 100644 --- a/go/flags/endtoend/flags_test.go +++ b/go/flags/endtoend/flags_test.go @@ -74,6 +74,9 @@ var ( //go:embed vtctldclient.txt vtctldclientTxt string + //go:embed vtgateclienttest.txt + vtgateclienttestTxt string + //go:embed vttestserver.txt vttestserverTxt string @@ -90,24 +93,25 @@ var ( zkTxt string helpOutput = map[string]string{ - "mysqlctl": mysqlctlTxt, - "mysqlctld": mysqlctldTxt, - "topo2topo": topo2topoTxt, - "vtaclcheck": vtaclcheckTxt, - "vtbackup": vtbackupTxt, - "vtcombo": vtcomboTxt, - "vtctlclient": vtctlclientTxt, - "vtctld": vtctldTxt, - "vtctldclient": vtctldclientTxt, - "vtexplain": vtexplainTxt, - "vtgate": vtgateTxt, - "vtorc": vtorcTxt, - "vttablet": vttabletTxt, - "vttestserver": vttestserverTxt, - "vttlstest": vttlstestTxt, - "zk": zkTxt, - "zkctl": zkctlTxt, - "zkctld": zkctldTxt, + "mysqlctl": mysqlctlTxt, + "mysqlctld": mysqlctldTxt, + "topo2topo": topo2topoTxt, + "vtaclcheck": vtaclcheckTxt, + "vtbackup": vtbackupTxt, + "vtcombo": vtcomboTxt, + "vtctlclient": vtctlclientTxt, + "vtctld": vtctldTxt, + "vtctldclient": vtctldclientTxt, + "vtexplain": vtexplainTxt, + "vtgate": vtgateTxt, + "vtgateclienttest": vtgateclienttestTxt, + "vtorc": vtorcTxt, + "vttablet": vttabletTxt, + "vttestserver": vttestserverTxt, + "vttlstest": vttlstestTxt, + "zk": zkTxt, + "zkctl": zkctlTxt, + "zkctld": zkctldTxt, } ) diff --git a/go/flags/endtoend/vtgateclienttest.txt b/go/flags/endtoend/vtgateclienttest.txt new file mode 100644 index 00000000000..18fced92070 --- /dev/null +++ b/go/flags/endtoend/vtgateclienttest.txt @@ -0,0 +1,60 @@ +Usage of vtgateclienttest: + --alsologtostderr log to standard error as well as files + --catch-sigpipe catch and ignore SIGPIPE on stdout and stderr if specified + --config-file string Full path of the config file (with extension) to use. If set, --config-path, --config-type, and --config-name are ignored. + --config-file-not-found-handling ConfigFileNotFoundHandling Behavior when a config file is not found. (Options: error, exit, ignore, warn) (default warn) + --config-name string Name of the config file (without extension) to search for. (default "vtconfig") + --config-path strings Paths to search for config files in. (default [{{ .Workdir }}]) + --config-persistence-min-interval duration minimum interval between persisting dynamic config changes back to disk (if no change has occurred, nothing is done). (default 1s) + --config-type string Config file type (omit to infer config type from file extension). + --default_tablet_type topodatapb.TabletType The default tablet type to set for queries, when one is not explicitly selected. (default PRIMARY) + --grpc_auth_mode string Which auth plugin implementation to use (eg: static) + --grpc_auth_mtls_allowed_substrings string List of substrings of at least one of the client certificate names (separated by colon). + --grpc_auth_static_client_creds string When using grpc_static_auth in the server, this file provides the credentials to use to authenticate with server. + --grpc_auth_static_password_file string JSON File to read the users/passwords from. + --grpc_ca string server CA to use for gRPC connections, requires TLS, and enforces client certificate check + --grpc_cert string server certificate to use for gRPC connections, requires grpc_key, enables TLS + --grpc_compression string Which protocol to use for compressing gRPC. Default: nothing. Supported: snappy + --grpc_crl string path to a certificate revocation list in PEM format, client certificates will be further verified against this file during TLS handshake + --grpc_enable_optional_tls enable optional TLS mode when a server accepts both TLS and plain-text connections on the same port + --grpc_enable_tracing Enable gRPC tracing. + --grpc_initial_conn_window_size int gRPC initial connection window size + --grpc_initial_window_size int gRPC initial window size + --grpc_keepalive_time duration After a duration of this time, if the client doesn't see any activity, it pings the server to see if the transport is still alive. (default 10s) + --grpc_keepalive_timeout duration After having pinged for keepalive check, the client waits for a duration of Timeout and if no activity is seen even after that the connection is closed. (default 10s) + --grpc_key string server private key to use for gRPC connections, requires grpc_cert, enables TLS + --grpc_max_connection_age duration Maximum age of a client connection before GoAway is sent. (default 2562047h47m16.854775807s) + --grpc_max_connection_age_grace duration Additional grace period after grpc_max_connection_age, after which connections are forcibly closed. (default 2562047h47m16.854775807s) + --grpc_max_message_size int Maximum allowed RPC message size. Larger messages will be rejected by gRPC with the error 'exceeding the max size'. (default 16777216) + --grpc_port int Port to listen on for gRPC calls. If zero, do not listen. + --grpc_prometheus Enable gRPC monitoring with Prometheus. + --grpc_server_ca string path to server CA in PEM format, which will be combine with server cert, return full certificate chain to clients + --grpc_server_initial_conn_window_size int gRPC server initial connection window size + --grpc_server_initial_window_size int gRPC server initial window size + --grpc_server_keepalive_enforcement_policy_min_time duration gRPC server minimum keepalive time (default 10s) + --grpc_server_keepalive_enforcement_policy_permit_without_stream gRPC server permit client keepalive pings even when there are no active streams (RPCs) + -h, --help display usage and exit + --keep_logs duration keep logs for this long (using ctime) (zero to keep forever) + --keep_logs_by_mtime duration keep logs for this long (using mtime) (zero to keep forever) + --lameduck-period duration keep running at least this long after SIGTERM before stopping (default 50ms) + --log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0) + --log_dir string If non-empty, write log files in this directory + --log_err_stacks log stack traces for errors + --log_rotate_max_size uint size in bytes at which logs are rotated (glog.MaxSize) (default 1887436800) + --logtostderr log to standard error instead of files + --max-stack-size int configure the maximum stack size in bytes (default 67108864) + --mysql_server_version string MySQL server version to advertise. (default "8.0.30-Vitess") + --onclose_timeout duration wait no more than this for OnClose handlers before stopping (default 10s) + --onterm_timeout duration wait no more than this for OnTermSync handlers before stopping (default 10s) + --pid_file string If set, the process will write its pid to the named file, and delete it on graceful shutdown. + --port int port for the server + --pprof strings enable profiling + --purge_logs_interval duration how often try to remove old logs (default 1h0m0s) + --security_policy string the name of a registered security policy to use for controlling access to URLs - empty means allow all for anyone (built-in policies: deny-all, read-only) + --service_map strings comma separated list of services to enable (or disable if prefixed with '-') Example: grpc-queryservice + --stderrthreshold severity logs at or above this threshold go to stderr (default 1) + --table-refresh-interval int interval in milliseconds to refresh tables in status page with refreshRequired class + --v Level log level for V logs + -v, --version print binary version + --vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging + --vschema_ddl_authorized_users string List of users authorized to execute vschema ddl operations, or '%' to allow all users. From e603fc17fd89096b50368dd22165818f5e2be07d Mon Sep 17 00:00:00 2001 From: Andrew Mason Date: Thu, 21 Sep 2023 10:24:44 -0400 Subject: [PATCH 05/11] migrate vtgateclienttest to cobra Signed-off-by: Andrew Mason --- go/cmd/vtgateclienttest/cli/main.go | 64 +++++++++++++++++++ .../{ => cli}/plugin_grpcvtgateservice.go | 2 +- go/cmd/vtgateclienttest/docgen/main.go | 37 +++++++++++ go/cmd/vtgateclienttest/main.go | 38 ++--------- go/flags/endtoend/vtgateclienttest.txt | 9 ++- 5 files changed, 114 insertions(+), 36 deletions(-) create mode 100644 go/cmd/vtgateclienttest/cli/main.go rename go/cmd/vtgateclienttest/{ => cli}/plugin_grpcvtgateservice.go (98%) create mode 100644 go/cmd/vtgateclienttest/docgen/main.go diff --git a/go/cmd/vtgateclienttest/cli/main.go b/go/cmd/vtgateclienttest/cli/main.go new file mode 100644 index 00000000000..a30cebe418d --- /dev/null +++ b/go/cmd/vtgateclienttest/cli/main.go @@ -0,0 +1,64 @@ +/* +Copyright 2023 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package cli is the implementation of vtgateclienttest. +// This program has a chain of vtgateservice.VTGateService implementations, +// each one being responsible for one test scenario. +package cli + +import ( + "github.com/spf13/cobra" + + "vitess.io/vitess/go/acl" + "vitess.io/vitess/go/cmd/vtgateclienttest/services" + "vitess.io/vitess/go/vt/servenv" + "vitess.io/vitess/go/vt/vtgate" +) + +var Main = &cobra.Command{ + Use: "vtgateclienttest", + Short: "vtgateclienttest is a chain of vtgateservice.VTGateService implementations, each one being responsible for one test scenario.", + Args: cobra.NoArgs, + PreRunE: servenv.CobraPreRunE, + RunE: run, +} + +func init() { + servenv.RegisterDefaultFlags() + servenv.RegisterFlags() + servenv.RegisterGRPCServerFlags() + servenv.RegisterGRPCServerAuthFlags() + servenv.RegisterServiceMapFlag() + + servenv.MoveFlagsToCobraCommand(Main) + + acl.RegisterFlags(Main.Flags()) +} + +func run(cmd *cobra.Command, args []string) error { + servenv.Init() + + // The implementation chain. + servenv.OnRun(func() { + s := services.CreateServices() + for _, f := range vtgate.RegisterVTGates { + f(s) + } + }) + + servenv.RunDefault() + return nil +} diff --git a/go/cmd/vtgateclienttest/plugin_grpcvtgateservice.go b/go/cmd/vtgateclienttest/cli/plugin_grpcvtgateservice.go similarity index 98% rename from go/cmd/vtgateclienttest/plugin_grpcvtgateservice.go rename to go/cmd/vtgateclienttest/cli/plugin_grpcvtgateservice.go index 4ee159710ca..bbbc6e3039e 100644 --- a/go/cmd/vtgateclienttest/plugin_grpcvtgateservice.go +++ b/go/cmd/vtgateclienttest/cli/plugin_grpcvtgateservice.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cli // Imports and register the gRPC vtgateservice server diff --git a/go/cmd/vtgateclienttest/docgen/main.go b/go/cmd/vtgateclienttest/docgen/main.go new file mode 100644 index 00000000000..3a18cd6feeb --- /dev/null +++ b/go/cmd/vtgateclienttest/docgen/main.go @@ -0,0 +1,37 @@ +/* +Copyright 2023 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "github.com/spf13/cobra" + + "vitess.io/vitess/go/cmd/internal/docgen" + "vitess.io/vitess/go/cmd/vtgateclienttest/cli" +) + +func main() { + var dir string + cmd := cobra.Command{ + Use: "docgen [-d ]", + RunE: func(cmd *cobra.Command, args []string) error { + return docgen.GenerateMarkdownTree(cli.Main, dir) + }, + } + + cmd.Flags().StringVarP(&dir, "dir", "d", "doc", "output directory to write documentation") + _ = cmd.Execute() +} diff --git a/go/cmd/vtgateclienttest/main.go b/go/cmd/vtgateclienttest/main.go index 2623ab84893..313b27de04a 100644 --- a/go/cmd/vtgateclienttest/main.go +++ b/go/cmd/vtgateclienttest/main.go @@ -14,46 +14,18 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package main is the implementation of vtgateclienttest. -// This program has a chain of vtgateservice.VTGateService implementations, -// each one being responsible for one test scenario. package main import ( - "github.com/spf13/pflag" - - "vitess.io/vitess/go/acl" - "vitess.io/vitess/go/cmd/vtgateclienttest/services" + "vitess.io/vitess/go/cmd/vtgateclienttest/cli" "vitess.io/vitess/go/exit" - "vitess.io/vitess/go/vt/servenv" - "vitess.io/vitess/go/vt/vtgate" + "vitess.io/vitess/go/vt/log" ) -func init() { - servenv.RegisterDefaultFlags() - servenv.RegisterFlags() - servenv.RegisterGRPCServerFlags() - servenv.RegisterGRPCServerAuthFlags() - servenv.RegisterServiceMapFlag() - - servenv.OnParse(func(fs *pflag.FlagSet) { - acl.RegisterFlags(fs) - }) -} - func main() { defer exit.Recover() - servenv.ParseFlags("vtgateclienttest") - servenv.Init() - - // The implementation chain. - servenv.OnRun(func() { - s := services.CreateServices() - for _, f := range vtgate.RegisterVTGates { - f(s) - } - }) - - servenv.RunDefault() + if err := cli.Main.Execute(); err != nil { + log.Exitf("%s", err) + } } diff --git a/go/flags/endtoend/vtgateclienttest.txt b/go/flags/endtoend/vtgateclienttest.txt index 18fced92070..778dbfc8243 100644 --- a/go/flags/endtoend/vtgateclienttest.txt +++ b/go/flags/endtoend/vtgateclienttest.txt @@ -1,4 +1,9 @@ -Usage of vtgateclienttest: +vtgateclienttest is a chain of vtgateservice.VTGateService implementations, each one being responsible for one test scenario. + +Usage: + vtgateclienttest [flags] + +Flags: --alsologtostderr log to standard error as well as files --catch-sigpipe catch and ignore SIGPIPE on stdout and stderr if specified --config-file string Full path of the config file (with extension) to use. If set, --config-path, --config-type, and --config-name are ignored. @@ -33,7 +38,7 @@ Usage of vtgateclienttest: --grpc_server_initial_window_size int gRPC server initial window size --grpc_server_keepalive_enforcement_policy_min_time duration gRPC server minimum keepalive time (default 10s) --grpc_server_keepalive_enforcement_policy_permit_without_stream gRPC server permit client keepalive pings even when there are no active streams (RPCs) - -h, --help display usage and exit + -h, --help help for vtgateclienttest --keep_logs duration keep logs for this long (using ctime) (zero to keep forever) --keep_logs_by_mtime duration keep logs for this long (using mtime) (zero to keep forever) --lameduck-period duration keep running at least this long after SIGTERM before stopping (default 50ms) From 28b288d4135a072f7d83842c55589fdac749f086 Mon Sep 17 00:00:00 2001 From: Andrew Mason Date: Thu, 21 Sep 2023 14:39:26 -0400 Subject: [PATCH 06/11] migrate vttestserver to cobra Signed-off-by: Andrew Mason --- go/cmd/vttestserver/cli/main.go | 308 ++++++++++++++++++ .../main_test.go} | 2 +- go/cmd/vttestserver/docgen/main.go | 37 +++ go/cmd/vttestserver/main.go | 282 +--------------- go/flags/endtoend/vttestserver.txt | 9 +- 5 files changed, 355 insertions(+), 283 deletions(-) create mode 100644 go/cmd/vttestserver/cli/main.go rename go/cmd/vttestserver/{vttestserver_test.go => cli/main_test.go} (99%) create mode 100644 go/cmd/vttestserver/docgen/main.go diff --git a/go/cmd/vttestserver/cli/main.go b/go/cmd/vttestserver/cli/main.go new file mode 100644 index 00000000000..f9a2f16cd87 --- /dev/null +++ b/go/cmd/vttestserver/cli/main.go @@ -0,0 +1,308 @@ +/* +Copyright 2023 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// vttestserver allows users to spawn a self-contained Vitess server for local testing/CI. +package cli + +import ( + "encoding/json" + "fmt" + "os" + "os/signal" + "strconv" + "strings" + "syscall" + "time" + + "github.com/spf13/cobra" + "google.golang.org/protobuf/encoding/prototext" + + "vitess.io/vitess/go/acl" + "vitess.io/vitess/go/vt/log" + "vitess.io/vitess/go/vt/servenv" + "vitess.io/vitess/go/vt/vttest" + + vttestpb "vitess.io/vitess/go/vt/proto/vttest" +) + +type topoFlags struct { + cells []string + keyspaces []string + shards []string + replicas int + rdonly int +} + +var ( + basePort int + config vttest.Config + doSeed bool + mycnf string + protoTopo string + seed vttest.SeedConfig + topo topoFlags +) + +func (t *topoFlags) buildTopology() (*vttestpb.VTTestTopology, error) { + topo := &vttestpb.VTTestTopology{} + topo.Cells = t.cells + + keyspaces := t.keyspaces + shardCounts := t.shards + if len(keyspaces) != len(shardCounts) { + return nil, fmt.Errorf("--keyspaces must be same length as --shards") + } + + for i := range keyspaces { + name := keyspaces[i] + numshards, err := strconv.ParseInt(shardCounts[i], 10, 32) + if err != nil { + return nil, err + } + + ks := &vttestpb.Keyspace{ + Name: name, + ReplicaCount: int32(t.replicas), + RdonlyCount: int32(t.rdonly), + } + + for _, shardname := range vttest.GetShardNames(int(numshards)) { + ks.Shards = append(ks.Shards, &vttestpb.Shard{ + Name: shardname, + }) + } + + topo.Keyspaces = append(topo.Keyspaces, ks) + } + + return topo, nil +} + +func init() { + servenv.RegisterFlags() + servenv.RegisterGRPCServerFlags() + servenv.RegisterGRPCServerAuthFlags() + servenv.RegisterServiceMapFlag() +} + +func New() (cmd *cobra.Command) { + cmd = &cobra.Command{ + Use: "vttestserver", + Short: "vttestserver allows users to spawn a self-contained Vitess server for local testing/CI.", + Args: cobra.NoArgs, + PreRunE: servenv.CobraPreRunE, + RunE: run, + } + + servenv.MoveFlagsToCobraCommand(cmd) + + cmd.Flags().IntVar(&basePort, "port", 0, + "Port to use for vtcombo. If this is 0, a random port will be chosen.") + + cmd.Flags().StringVar(&protoTopo, "proto_topo", "", + "Define the fake cluster topology as a compact text format encoded"+ + " vttest proto. See vttest.proto for more information.") + + cmd.Flags().StringVar(&config.SchemaDir, "schema_dir", "", + "Directory for initial schema files. Within this dir,"+ + " there should be a subdir for each keyspace. Within"+ + " each keyspace dir, each file is executed as SQL"+ + " after the database is created on each shard."+ + " If the directory contains a vschema.json file, it"+ + " will be used as the vschema for the V3 API.") + + cmd.Flags().StringVar(&config.DefaultSchemaDir, "default_schema_dir", "", + "Default directory for initial schema files. If no schema is found"+ + " in schema_dir, default to this location.") + + cmd.Flags().StringVar(&config.DataDir, "data_dir", "", + "Directory where the data files will be placed, defaults to a random "+ + "directory under /vt/vtdataroot") + + cmd.Flags().BoolVar(&config.OnlyMySQL, "mysql_only", false, + "If this flag is set only mysql is initialized."+ + " The rest of the vitess components are not started."+ + " Also, the output specifies the mysql unix socket"+ + " instead of the vtgate port.") + + cmd.Flags().BoolVar(&config.PersistentMode, "persistent_mode", false, + "If this flag is set, the MySQL data directory is not cleaned up"+ + " when LocalCluster.TearDown() is called. This is useful for running"+ + " vttestserver as a database container in local developer environments. Note"+ + " that db migration files (--schema_dir option) and seeding of"+ + " random data (--initialize_with_random_data option) will only run during"+ + " cluster startup if the data directory does not already exist. "+ + " Changes to VSchema are persisted across cluster restarts using a simple"+ + " watcher if the --data_dir argument is specified.") + + cmd.Flags().BoolVar(&doSeed, "initialize_with_random_data", false, + "If this flag is each table-shard will be initialized"+ + " with random data. See also the 'rng_seed' and 'min_shard_size'"+ + " and 'max_shard_size' flags.") + + cmd.Flags().IntVar(&seed.RngSeed, "rng_seed", 123, + "The random number generator seed to use when initializing"+ + " with random data (see also --initialize_with_random_data)."+ + " Multiple runs with the same seed will result with the same"+ + " initial data.") + + cmd.Flags().IntVar(&seed.MinSize, "min_table_shard_size", 1000, + "The minimum number of initial rows in a table shard. Ignored if"+ + "--initialize_with_random_data is false. The actual number is chosen"+ + " randomly.") + + cmd.Flags().IntVar(&seed.MaxSize, "max_table_shard_size", 10000, + "The maximum number of initial rows in a table shard. Ignored if"+ + "--initialize_with_random_data is false. The actual number is chosen"+ + " randomly") + + cmd.Flags().Float64Var(&seed.NullProbability, "null_probability", 0.1, + "The probability to initialize a field with 'NULL' "+ + " if --initialize_with_random_data is true. Only applies to fields"+ + " that can contain NULL values.") + + cmd.Flags().StringVar(&config.MySQLBindHost, "mysql_bind_host", "localhost", + "which host to bind vtgate mysql listener to") + + cmd.Flags().StringVar(&mycnf, "extra_my_cnf", "", + "extra files to add to the config, separated by ':'") + + cmd.Flags().StringSliceVar(&topo.cells, "cells", []string{"test"}, "Comma separated list of cells") + cmd.Flags().StringSliceVar(&topo.keyspaces, "keyspaces", []string{"test_keyspace"}, + "Comma separated list of keyspaces") + cmd.Flags().StringSliceVar(&topo.shards, "num_shards", []string{"2"}, + "Comma separated shard count (one per keyspace)") + cmd.Flags().IntVar(&topo.replicas, "replica_count", 2, + "Replica tablets per shard (includes primary)") + cmd.Flags().IntVar(&topo.rdonly, "rdonly_count", 1, + "Rdonly tablets per shard") + + cmd.Flags().StringVar(&config.Charset, "charset", "utf8mb4", "MySQL charset") + + cmd.Flags().StringVar(&config.PlannerVersion, "planner-version", "", "Sets the default planner to use when the session has not changed it. Valid values are: Gen4, Gen4Greedy, Gen4Left2Right") + + cmd.Flags().StringVar(&config.SnapshotFile, "snapshot_file", "", + "A MySQL DB snapshot file") + + cmd.Flags().BoolVar(&config.EnableSystemSettings, "enable_system_settings", true, "This will enable the system settings to be changed per session at the database connection level") + + cmd.Flags().StringVar(&config.TransactionMode, "transaction_mode", "MULTI", "Transaction mode MULTI (default), SINGLE or TWOPC ") + cmd.Flags().Float64Var(&config.TransactionTimeout, "queryserver-config-transaction-timeout", 0, "query server transaction timeout (in seconds), a transaction will be killed if it takes longer than this value") + + cmd.Flags().StringVar(&config.TabletHostName, "tablet_hostname", "localhost", "The hostname to use for the tablet otherwise it will be derived from OS' hostname") + + cmd.Flags().StringVar(&config.VSchemaDDLAuthorizedUsers, "vschema_ddl_authorized_users", "", "Comma separated list of users authorized to execute vschema ddl operations via vtgate") + + cmd.Flags().StringVar(&config.ForeignKeyMode, "foreign_key_mode", "allow", "This is to provide how to handle foreign key constraint in create/alter table. Valid values are: allow, disallow") + cmd.Flags().BoolVar(&config.EnableOnlineDDL, "enable_online_ddl", true, "Allow users to submit, review and control Online DDL") + cmd.Flags().BoolVar(&config.EnableDirectDDL, "enable_direct_ddl", true, "Allow users to submit direct DDL statements") + + // flags for using an actual topo implementation for vtcombo instead of in-memory topo. useful for test setup where an external topo server is shared across multiple vtcombo processes or other components + cmd.Flags().StringVar(&config.ExternalTopoImplementation, "external_topo_implementation", "", "the topology implementation to use for vtcombo process") + cmd.Flags().StringVar(&config.ExternalTopoGlobalServerAddress, "external_topo_global_server_address", "", "the address of the global topology server for vtcombo process") + cmd.Flags().StringVar(&config.ExternalTopoGlobalRoot, "external_topo_global_root", "", "the path of the global topology data in the global topology server for vtcombo process") + + cmd.Flags().DurationVar(&config.VtgateTabletRefreshInterval, "tablet_refresh_interval", 10*time.Second, "Interval at which vtgate refreshes tablet information from topology server.") + acl.RegisterFlags(cmd.Flags()) + + return cmd +} + +func newEnv() (env vttest.Environment, err error) { + if basePort != 0 { + if config.DataDir == "" { + env, err = vttest.NewLocalTestEnv("", basePort) + if err != nil { + return + } + } else { + env, err = vttest.NewLocalTestEnvWithDirectory("", basePort, config.DataDir) + if err != nil { + return + } + } + } + + if protoTopo == "" { + config.Topology, err = topo.buildTopology() + if err != nil { + return + } + } else { + var topology vttestpb.VTTestTopology + err = prototext.Unmarshal([]byte(protoTopo), &topology) + if err != nil { + return + } + if len(topology.Cells) == 0 { + topology.Cells = append(topology.Cells, "test") + } + config.Topology = &topology + } + + if doSeed { + config.Seed = &seed + } + + if mycnf != "" { + config.ExtraMyCnf = strings.Split(mycnf, ":") + } + + return +} + +func run(cmd *cobra.Command, args []string) error { + cluster, err := runCluster() + if err != nil { + return err + } + defer cluster.TearDown() + + servenv.Init() + + kvconf := cluster.JSONConfig() + if err := json.NewEncoder(os.Stdout).Encode(kvconf); err != nil { + return err + } + + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + <-c + + return nil +} + +func runCluster() (cluster vttest.LocalCluster, err error) { + env, err := newEnv() + if err != nil { + return + } + + log.Infof("Starting local cluster...") + log.Infof("config: %#v", config) + cluster = vttest.LocalCluster{ + Config: config, + Env: env, + } + err = cluster.Setup() + if err != nil { + return cluster, err + } + + log.Info("Local cluster started.") + + return cluster, nil +} diff --git a/go/cmd/vttestserver/vttestserver_test.go b/go/cmd/vttestserver/cli/main_test.go similarity index 99% rename from go/cmd/vttestserver/vttestserver_test.go rename to go/cmd/vttestserver/cli/main_test.go index 226d66305be..b68d82b0dd6 100644 --- a/go/cmd/vttestserver/vttestserver_test.go +++ b/go/cmd/vttestserver/cli/main_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cli import ( "context" diff --git a/go/cmd/vttestserver/docgen/main.go b/go/cmd/vttestserver/docgen/main.go new file mode 100644 index 00000000000..61f982e2e56 --- /dev/null +++ b/go/cmd/vttestserver/docgen/main.go @@ -0,0 +1,37 @@ +/* +Copyright 2023 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "github.com/spf13/cobra" + + "vitess.io/vitess/go/cmd/internal/docgen" + "vitess.io/vitess/go/cmd/vttestserver/cli" +) + +func main() { + var dir string + cmd := cobra.Command{ + Use: "docgen [-d ]", + RunE: func(cmd *cobra.Command, args []string) error { + return docgen.GenerateMarkdownTree(cli.New(), dir) + }, + } + + cmd.Flags().StringVarP(&dir, "dir", "d", "doc", "output directory to write documentation") + _ = cmd.Execute() +} diff --git a/go/cmd/vttestserver/main.go b/go/cmd/vttestserver/main.go index f5f9c6bf41c..95e63fa8019 100644 --- a/go/cmd/vttestserver/main.go +++ b/go/cmd/vttestserver/main.go @@ -14,293 +14,15 @@ See the License for the specific language governing permissions and limitations under the License. */ -// vttestserver allows users to spawn a self-contained Vitess server for local testing/CI. package main import ( - "encoding/json" - "fmt" - "os" - "os/signal" - "strconv" - "strings" - "sync" - "syscall" - "time" - - "github.com/spf13/pflag" - "google.golang.org/protobuf/encoding/prototext" - - "vitess.io/vitess/go/acl" + "vitess.io/vitess/go/cmd/vttestserver/cli" "vitess.io/vitess/go/vt/log" - "vitess.io/vitess/go/vt/servenv" - "vitess.io/vitess/go/vt/vttest" - - vttestpb "vitess.io/vitess/go/vt/proto/vttest" ) -type topoFlags struct { - cells []string - keyspaces []string - shards []string - replicas int - rdonly int -} - -var ( - basePort int - config vttest.Config - doSeed bool - mycnf string - protoTopo string - seed vttest.SeedConfig - topo topoFlags -) - -func registerFlags(fs *pflag.FlagSet) { - fs.IntVar(&basePort, "port", 0, - "Port to use for vtcombo. If this is 0, a random port will be chosen.") - - fs.StringVar(&protoTopo, "proto_topo", "", - "Define the fake cluster topology as a compact text format encoded"+ - " vttest proto. See vttest.proto for more information.") - - fs.StringVar(&config.SchemaDir, "schema_dir", "", - "Directory for initial schema files. Within this dir,"+ - " there should be a subdir for each keyspace. Within"+ - " each keyspace dir, each file is executed as SQL"+ - " after the database is created on each shard."+ - " If the directory contains a vschema.json file, it"+ - " will be used as the vschema for the V3 API.") - - fs.StringVar(&config.DefaultSchemaDir, "default_schema_dir", "", - "Default directory for initial schema files. If no schema is found"+ - " in schema_dir, default to this location.") - - fs.StringVar(&config.DataDir, "data_dir", "", - "Directory where the data files will be placed, defaults to a random "+ - "directory under /vt/vtdataroot") - - fs.BoolVar(&config.OnlyMySQL, "mysql_only", false, - "If this flag is set only mysql is initialized."+ - " The rest of the vitess components are not started."+ - " Also, the output specifies the mysql unix socket"+ - " instead of the vtgate port.") - - fs.BoolVar(&config.PersistentMode, "persistent_mode", false, - "If this flag is set, the MySQL data directory is not cleaned up"+ - " when LocalCluster.TearDown() is called. This is useful for running"+ - " vttestserver as a database container in local developer environments. Note"+ - " that db migration files (--schema_dir option) and seeding of"+ - " random data (--initialize_with_random_data option) will only run during"+ - " cluster startup if the data directory does not already exist. "+ - " Changes to VSchema are persisted across cluster restarts using a simple"+ - " watcher if the --data_dir argument is specified.") - - fs.BoolVar(&doSeed, "initialize_with_random_data", false, - "If this flag is each table-shard will be initialized"+ - " with random data. See also the 'rng_seed' and 'min_shard_size'"+ - " and 'max_shard_size' flags.") - - fs.IntVar(&seed.RngSeed, "rng_seed", 123, - "The random number generator seed to use when initializing"+ - " with random data (see also --initialize_with_random_data)."+ - " Multiple runs with the same seed will result with the same"+ - " initial data.") - - fs.IntVar(&seed.MinSize, "min_table_shard_size", 1000, - "The minimum number of initial rows in a table shard. Ignored if"+ - "--initialize_with_random_data is false. The actual number is chosen"+ - " randomly.") - - fs.IntVar(&seed.MaxSize, "max_table_shard_size", 10000, - "The maximum number of initial rows in a table shard. Ignored if"+ - "--initialize_with_random_data is false. The actual number is chosen"+ - " randomly") - - fs.Float64Var(&seed.NullProbability, "null_probability", 0.1, - "The probability to initialize a field with 'NULL' "+ - " if --initialize_with_random_data is true. Only applies to fields"+ - " that can contain NULL values.") - - fs.StringVar(&config.MySQLBindHost, "mysql_bind_host", "localhost", - "which host to bind vtgate mysql listener to") - - fs.StringVar(&mycnf, "extra_my_cnf", "", - "extra files to add to the config, separated by ':'") - - fs.StringSliceVar(&topo.cells, "cells", []string{"test"}, "Comma separated list of cells") - fs.StringSliceVar(&topo.keyspaces, "keyspaces", []string{"test_keyspace"}, - "Comma separated list of keyspaces") - fs.StringSliceVar(&topo.shards, "num_shards", []string{"2"}, - "Comma separated shard count (one per keyspace)") - fs.IntVar(&topo.replicas, "replica_count", 2, - "Replica tablets per shard (includes primary)") - fs.IntVar(&topo.rdonly, "rdonly_count", 1, - "Rdonly tablets per shard") - - fs.StringVar(&config.Charset, "charset", "utf8mb4", "MySQL charset") - - fs.StringVar(&config.PlannerVersion, "planner-version", "", "Sets the default planner to use when the session has not changed it. Valid values are: Gen4, Gen4Greedy, Gen4Left2Right") - - fs.StringVar(&config.SnapshotFile, "snapshot_file", "", - "A MySQL DB snapshot file") - - fs.BoolVar(&config.EnableSystemSettings, "enable_system_settings", true, "This will enable the system settings to be changed per session at the database connection level") - - fs.StringVar(&config.TransactionMode, "transaction_mode", "MULTI", "Transaction mode MULTI (default), SINGLE or TWOPC ") - fs.Float64Var(&config.TransactionTimeout, "queryserver-config-transaction-timeout", 0, "query server transaction timeout (in seconds), a transaction will be killed if it takes longer than this value") - - fs.StringVar(&config.TabletHostName, "tablet_hostname", "localhost", "The hostname to use for the tablet otherwise it will be derived from OS' hostname") - - fs.StringVar(&config.VSchemaDDLAuthorizedUsers, "vschema_ddl_authorized_users", "", "Comma separated list of users authorized to execute vschema ddl operations via vtgate") - - fs.StringVar(&config.ForeignKeyMode, "foreign_key_mode", "allow", "This is to provide how to handle foreign key constraint in create/alter table. Valid values are: allow, disallow") - fs.BoolVar(&config.EnableOnlineDDL, "enable_online_ddl", true, "Allow users to submit, review and control Online DDL") - fs.BoolVar(&config.EnableDirectDDL, "enable_direct_ddl", true, "Allow users to submit direct DDL statements") - - // flags for using an actual topo implementation for vtcombo instead of in-memory topo. useful for test setup where an external topo server is shared across multiple vtcombo processes or other components - fs.StringVar(&config.ExternalTopoImplementation, "external_topo_implementation", "", "the topology implementation to use for vtcombo process") - fs.StringVar(&config.ExternalTopoGlobalServerAddress, "external_topo_global_server_address", "", "the address of the global topology server for vtcombo process") - fs.StringVar(&config.ExternalTopoGlobalRoot, "external_topo_global_root", "", "the path of the global topology data in the global topology server for vtcombo process") - - fs.DurationVar(&config.VtgateTabletRefreshInterval, "tablet_refresh_interval", 10*time.Second, "Interval at which vtgate refreshes tablet information from topology server.") - acl.RegisterFlags(fs) -} - -func init() { - servenv.OnParseFor("vttestserver", registerFlags) -} - -func (t *topoFlags) buildTopology() (*vttestpb.VTTestTopology, error) { - topo := &vttestpb.VTTestTopology{} - topo.Cells = t.cells - - keyspaces := t.keyspaces - shardCounts := t.shards - if len(keyspaces) != len(shardCounts) { - return nil, fmt.Errorf("--keyspaces must be same length as --shards") - } - - for i := range keyspaces { - name := keyspaces[i] - numshards, err := strconv.ParseInt(shardCounts[i], 10, 32) - if err != nil { - return nil, err - } - - ks := &vttestpb.Keyspace{ - Name: name, - ReplicaCount: int32(t.replicas), - RdonlyCount: int32(t.rdonly), - } - - for _, shardname := range vttest.GetShardNames(int(numshards)) { - ks.Shards = append(ks.Shards, &vttestpb.Shard{ - Name: shardname, - }) - } - - topo.Keyspaces = append(topo.Keyspaces, ks) - } - - return topo, nil -} - -// Annoying, but in unit tests, parseFlags gets called multiple times per process -// (anytime startCluster is called), so we need to guard against the second test -// to run failing with, for example: -// -// flag redefined: log_rotate_max_size -var flagsOnce sync.Once - -func parseFlags() (env vttest.Environment, err error) { - flagsOnce.Do(func() { - servenv.RegisterFlags() - servenv.RegisterGRPCServerFlags() - servenv.RegisterGRPCServerAuthFlags() - servenv.RegisterServiceMapFlag() - }) - - servenv.ParseFlags("vttestserver") - - if basePort != 0 { - if config.DataDir == "" { - env, err = vttest.NewLocalTestEnv("", basePort) - if err != nil { - return - } - } else { - env, err = vttest.NewLocalTestEnvWithDirectory("", basePort, config.DataDir) - if err != nil { - return - } - } - } - - if protoTopo == "" { - config.Topology, err = topo.buildTopology() - if err != nil { - return - } - } else { - var topology vttestpb.VTTestTopology - err = prototext.Unmarshal([]byte(protoTopo), &topology) - if err != nil { - return - } - if len(topology.Cells) == 0 { - topology.Cells = append(topology.Cells, "test") - } - config.Topology = &topology - } - - if doSeed { - config.Seed = &seed - } - - if mycnf != "" { - config.ExtraMyCnf = strings.Split(mycnf, ":") - } - - return -} - func main() { - cluster, err := runCluster() - servenv.Init() - if err != nil { + if err := cli.New().Execute(); err != nil { log.Fatal(err) } - defer cluster.TearDown() - - kvconf := cluster.JSONConfig() - if err := json.NewEncoder(os.Stdout).Encode(kvconf); err != nil { - log.Fatal(err) - } - - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt, syscall.SIGTERM) - <-c -} - -func runCluster() (vttest.LocalCluster, error) { - env, err := parseFlags() - if err != nil { - log.Fatal(err) - } - log.Infof("Starting local cluster...") - log.Infof("config: %#v", config) - cluster := vttest.LocalCluster{ - Config: config, - Env: env, - } - err = cluster.Setup() - if err != nil { - return cluster, err - } - - log.Info("Local cluster started.") - - return cluster, nil } diff --git a/go/flags/endtoend/vttestserver.txt b/go/flags/endtoend/vttestserver.txt index 5849f0c1e81..d9fbe1618e6 100644 --- a/go/flags/endtoend/vttestserver.txt +++ b/go/flags/endtoend/vttestserver.txt @@ -1,4 +1,9 @@ -Usage of vttestserver: +vttestserver allows users to spawn a self-contained Vitess server for local testing/CI. + +Usage: + vttestserver [flags] + +Flags: --alsologtostderr log to standard error as well as files --app_idle_timeout duration Idle timeout for app connections (default 1m0s) --app_pool_size int Size of the connection pool for app connections (default 40) @@ -63,7 +68,7 @@ Usage of vttestserver: --grpc_server_initial_window_size int gRPC server initial window size --grpc_server_keepalive_enforcement_policy_min_time duration gRPC server minimum keepalive time (default 10s) --grpc_server_keepalive_enforcement_policy_permit_without_stream gRPC server permit client keepalive pings even when there are no active streams (RPCs) - -h, --help display usage and exit + -h, --help help for vttestserver --initialize_with_random_data If this flag is each table-shard will be initialized with random data. See also the 'rng_seed' and 'min_shard_size' and 'max_shard_size' flags. --keep_logs duration keep logs for this long (using ctime) (zero to keep forever) --keep_logs_by_mtime duration keep logs for this long (using mtime) (zero to keep forever) From 13175a56450f91b0cdf882edb44d5219bd3cdeb4 Mon Sep 17 00:00:00 2001 From: Andrew Mason Date: Thu, 21 Sep 2023 15:33:08 -0400 Subject: [PATCH 07/11] fix tests Signed-off-by: Andrew Mason --- go/cmd/vttestserver/cli/main_test.go | 38 +++++++++++++--------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/go/cmd/vttestserver/cli/main_test.go b/go/cmd/vttestserver/cli/main_test.go index b68d82b0dd6..39dc8e4ea78 100644 --- a/go/cmd/vttestserver/cli/main_test.go +++ b/go/cmd/vttestserver/cli/main_test.go @@ -21,7 +21,6 @@ import ( "fmt" "io" "math/rand" - "os" "os/exec" "path" "strings" @@ -54,9 +53,8 @@ type columnVindex struct { } func TestRunsVschemaMigrations(t *testing.T) { - args := os.Args conf := config - defer resetFlags(args, conf) + defer resetConfig(conf) cluster, err := startCluster() defer cluster.TearDown() @@ -72,9 +70,8 @@ func TestRunsVschemaMigrations(t *testing.T) { } func TestPersistentMode(t *testing.T) { - args := os.Args conf := config - defer resetFlags(args, conf) + defer resetConfig(conf) dir := t.TempDir() @@ -135,9 +132,8 @@ func TestPersistentMode(t *testing.T) { } func TestForeignKeysAndDDLModes(t *testing.T) { - args := os.Args conf := config - defer resetFlags(args, conf) + defer resetConfig(conf) cluster, err := startCluster("--foreign_key_mode=allow", "--enable_online_ddl=true", "--enable_direct_ddl=true") assert.NoError(t, err) @@ -190,9 +186,8 @@ func TestForeignKeysAndDDLModes(t *testing.T) { } func TestCanGetKeyspaces(t *testing.T) { - args := os.Args conf := config - defer resetFlags(args, conf) + defer resetConfig(conf) cluster, err := startCluster() assert.NoError(t, err) @@ -202,9 +197,8 @@ func TestCanGetKeyspaces(t *testing.T) { } func TestExternalTopoServerConsul(t *testing.T) { - args := os.Args conf := config - defer resetFlags(args, conf) + defer resetConfig(conf) // Start a single consul in the background. cmd, serverAddr := startConsul(t) @@ -228,9 +222,8 @@ func TestExternalTopoServerConsul(t *testing.T) { } func TestMtlsAuth(t *testing.T) { - args := os.Args conf := config - defer resetFlags(args, conf) + defer resetConfig(conf) // Our test root. root := t.TempDir() @@ -270,9 +263,8 @@ func TestMtlsAuth(t *testing.T) { } func TestMtlsAuthUnauthorizedFails(t *testing.T) { - args := os.Args conf := config - defer resetFlags(args, conf) + defer resetConfig(conf) // Our test root. root := t.TempDir() @@ -322,16 +314,21 @@ var clusterKeyspaces = []string{ "app_customer", } -func startCluster(flags ...string) (vttest.LocalCluster, error) { - os.Args = []string{"vttestserver"} +func startCluster(flags ...string) (cluster vttest.LocalCluster, err error) { + args := []string{"vttestserver"} schemaDirArg := "--schema_dir=data/schema" tabletHostname := "--tablet_hostname=localhost" keyspaceArg := "--keyspaces=" + strings.Join(clusterKeyspaces, ",") numShardsArg := "--num_shards=2,2" vschemaDDLAuthorizedUsers := "--vschema_ddl_authorized_users=%" alsoLogToStderr := "--alsologtostderr" // better debugging - os.Args = append(os.Args, []string{schemaDirArg, keyspaceArg, numShardsArg, tabletHostname, vschemaDDLAuthorizedUsers, alsoLogToStderr}...) - os.Args = append(os.Args, flags...) + args = append(args, []string{schemaDirArg, keyspaceArg, numShardsArg, tabletHostname, vschemaDDLAuthorizedUsers, alsoLogToStderr}...) + args = append(args, flags...) + + if err = New().ParseFlags(args); err != nil { + return + } + return runCluster() } @@ -384,8 +381,7 @@ func assertEqual(t *testing.T, actual string, expected string, message string) { } } -func resetFlags(args []string, conf vttest.Config) { - os.Args = args +func resetConfig(conf vttest.Config) { config = conf } From 8054456927872e15ceca0caf211eb0519998327e Mon Sep 17 00:00:00 2001 From: Andrew Mason Date: Thu, 21 Sep 2023 15:40:27 -0400 Subject: [PATCH 08/11] migrate vttlstest to cobra Signed-off-by: Andrew Mason --- go/cmd/vttlstest/cli/vttlstest.go | 135 ++++++++++++++++++++++++++++++ go/cmd/vttlstest/docgen/main.go | 37 ++++++++ go/cmd/vttlstest/vttlstest.go | 116 +------------------------ 3 files changed, 174 insertions(+), 114 deletions(-) create mode 100644 go/cmd/vttlstest/cli/vttlstest.go create mode 100644 go/cmd/vttlstest/docgen/main.go diff --git a/go/cmd/vttlstest/cli/vttlstest.go b/go/cmd/vttlstest/cli/vttlstest.go new file mode 100644 index 00000000000..4e0f9c2b95e --- /dev/null +++ b/go/cmd/vttlstest/cli/vttlstest.go @@ -0,0 +1,135 @@ +/* +Copyright 2023 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreedto in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cli + +import ( + "github.com/spf13/cobra" + + "vitess.io/vitess/go/vt/tlstest" +) + +var ( + root = "." + parent = "ca" + serial = "01" + commonName string + + Root = &cobra.Command{ + Use: "vttlstest", + Short: "vttlstest is a tool for generating test certificates, keys, and related artifacts for TLS tests.", + Long: "vttlstest is a tool for generating test certificates, keys, and related artifacts for TLS tests.", + } + + createCACmd = &cobra.Command{ + Use: "CreateCA [--root ]", + DisableFlagsInUseLine: true, + Example: "CreateCA --root /tmp", + Short: "Create certificate authority", + Long: "Create certificate authority", + Args: cobra.NoArgs, + Run: runCreateCA, + } + + createIntermediateCACmd = &cobra.Command{ + Use: "CreateIntermediateCA [--root ] [--parent ] [--serial ] [--common-name ] ", + DisableFlagsInUseLine: true, + Example: "CreateIntermediateCA --root /tmp --parent ca mail.mycoolsite.com", + Short: "Create intermediate certificate authority", + Long: "Create intermediate certificate authority", + Args: cobra.ExactArgs(1), + Run: runCreateIntermediateCA, + } + + createCRLCmd = &cobra.Command{ + Use: "CreateCRL [--root ] ", + DisableFlagsInUseLine: true, + Example: "CreateCRL --root /tmp mail.mycoolsite.com", + Short: "Create certificate revocation list", + Long: "Create certificate revocation list", + Args: cobra.ExactArgs(1), + Run: runCreateCRL, + } + + createSignedCertCmd = &cobra.Command{ + Use: "CreateSignedCert [--root ] [--parent ] [--serial ] [--common-name ] ", + DisableFlagsInUseLine: true, + Example: "CreateSignedCert --root /tmp --common-name mail.mysite.com --parent mail.mycoolsite.com postman1", + Short: "Create signed certificate", + Long: "Create signed certificate", + Args: cobra.ExactArgs(1), + Run: runCreateSignedCert, + } + + revokeCertCmd = &cobra.Command{ + Use: "RevokeCert [--root ] [--parent ] ", + DisableFlagsInUseLine: true, + Example: "RevokeCert --root /tmp --parent mail.mycoolsite.com postman1", + Short: "Revoke a certificate", + Long: "Revoke a certificate", + Args: cobra.ExactArgs(1), + Run: runRevokeCert, + } +) + +func init() { + Root.PersistentFlags().StringVar(&root, "root", root, "root directory for all artifacts") + + Root.AddCommand(createCACmd) + Root.AddCommand(createIntermediateCACmd) + Root.AddCommand(createCRLCmd) + Root.AddCommand(createSignedCertCmd) + Root.AddCommand(revokeCertCmd) + + for _, cmd := range []*cobra.Command{createIntermediateCACmd, createSignedCertCmd} { + cmd.Flags().StringVar(&parent, "parent", parent, "Parent cert name to use. Use 'ca' for the toplevel CA.") + cmd.Flags().StringVar(&serial, "serial", serial, "Serial number for the certificate to create. Should be different for two certificates with the same parent.") + cmd.Flags().StringVar(&commonName, "common-name", commonName, "Common name for the certificate. If empty, uses the name.") + } + revokeCertCmd.Flags().StringVar(&parent, "parent", parent, "Parent cert name to use. Use 'ca' for the toplevel CA.") +} + +func runCreateCA(cmd *cobra.Command, args []string) { + tlstest.CreateCA(root) +} + +func runCreateIntermediateCA(cmd *cobra.Command, args []string) { + name := args[0] + if commonName == "" { + commonName = name + } + + tlstest.CreateIntermediateCA(root, parent, serial, name, commonName) +} + +func runCreateCRL(cmd *cobra.Command, args []string) { + ca := args[0] + tlstest.CreateCRL(root, ca) +} + +func runCreateSignedCert(cmd *cobra.Command, args []string) { + name := args[0] + if commonName == "" { + commonName = name + } + + tlstest.CreateSignedCert(root, parent, serial, name, commonName) +} + +func runRevokeCert(cmd *cobra.Command, args []string) { + name := args[0] + tlstest.RevokeCertAndRegenerateCRL(root, parent, name) +} diff --git a/go/cmd/vttlstest/docgen/main.go b/go/cmd/vttlstest/docgen/main.go new file mode 100644 index 00000000000..2354dceb493 --- /dev/null +++ b/go/cmd/vttlstest/docgen/main.go @@ -0,0 +1,37 @@ +/* +Copyright 2023 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "github.com/spf13/cobra" + + "vitess.io/vitess/go/cmd/internal/docgen" + "vitess.io/vitess/go/cmd/vttlstest/cli" +) + +func main() { + var dir string + cmd := cobra.Command{ + Use: "docgen [-d ]", + RunE: func(cmd *cobra.Command, args []string) error { + return docgen.GenerateMarkdownTree(cli.Root, dir) + }, + } + + cmd.Flags().StringVarP(&dir, "dir", "d", "doc", "output directory to write documentation") + _ = cmd.Execute() +} diff --git a/go/cmd/vttlstest/vttlstest.go b/go/cmd/vttlstest/vttlstest.go index 78bffb813a3..08e994c096d 100644 --- a/go/cmd/vttlstest/vttlstest.go +++ b/go/cmd/vttlstest/vttlstest.go @@ -19,126 +19,14 @@ package main import ( "github.com/spf13/cobra" + "vitess.io/vitess/go/cmd/vttlstest/cli" "vitess.io/vitess/go/exit" "vitess.io/vitess/go/vt/logutil" - "vitess.io/vitess/go/vt/tlstest" ) -var ( - root = "." - parent = "ca" - serial = "01" - commonName string - - rootCmd = &cobra.Command{ - Use: "vttlstest", - Short: "vttlstest is a tool for generating test certificates, keys, and related artifacts for TLS tests.", - Long: "vttlstest is a tool for generating test certificates, keys, and related artifacts for TLS tests.", - } - - createCACmd = &cobra.Command{ - Use: "CreateCA [--root ]", - DisableFlagsInUseLine: true, - Example: "CreateCA --root /tmp", - Short: "Create certificate authority", - Long: "Create certificate authority", - Args: cobra.NoArgs, - Run: runCreateCA, - } - - createIntermediateCACmd = &cobra.Command{ - Use: "CreateIntermediateCA [--root ] [--parent ] [--serial ] [--common-name ] ", - DisableFlagsInUseLine: true, - Example: "CreateIntermediateCA --root /tmp --parent ca mail.mycoolsite.com", - Short: "Create intermediate certificate authority", - Long: "Create intermediate certificate authority", - Args: cobra.ExactArgs(1), - Run: runCreateIntermediateCA, - } - - createCRLCmd = &cobra.Command{ - Use: "CreateCRL [--root ] ", - DisableFlagsInUseLine: true, - Example: "CreateCRL --root /tmp mail.mycoolsite.com", - Short: "Create certificate revocation list", - Long: "Create certificate revocation list", - Args: cobra.ExactArgs(1), - Run: runCreateCRL, - } - - createSignedCertCmd = &cobra.Command{ - Use: "CreateSignedCert [--root ] [--parent ] [--serial ] [--common-name ] ", - DisableFlagsInUseLine: true, - Example: "CreateSignedCert --root /tmp --common-name mail.mysite.com --parent mail.mycoolsite.com postman1", - Short: "Create signed certificate", - Long: "Create signed certificate", - Args: cobra.ExactArgs(1), - Run: runCreateSignedCert, - } - - revokeCertCmd = &cobra.Command{ - Use: "RevokeCert [--root ] [--parent ] ", - DisableFlagsInUseLine: true, - Example: "RevokeCert --root /tmp --parent mail.mycoolsite.com postman1", - Short: "Revoke a certificate", - Long: "Revoke a certificate", - Args: cobra.ExactArgs(1), - Run: runRevokeCert, - } -) - -func init() { - rootCmd.PersistentFlags().StringVar(&root, "root", root, "root directory for all artifacts") - - rootCmd.AddCommand(createCACmd) - rootCmd.AddCommand(createIntermediateCACmd) - rootCmd.AddCommand(createCRLCmd) - rootCmd.AddCommand(createSignedCertCmd) - rootCmd.AddCommand(revokeCertCmd) - - for _, cmd := range []*cobra.Command{createIntermediateCACmd, createSignedCertCmd} { - cmd.Flags().StringVar(&parent, "parent", parent, "Parent cert name to use. Use 'ca' for the toplevel CA.") - cmd.Flags().StringVar(&serial, "serial", serial, "Serial number for the certificate to create. Should be different for two certificates with the same parent.") - cmd.Flags().StringVar(&commonName, "common-name", commonName, "Common name for the certificate. If empty, uses the name.") - } - revokeCertCmd.Flags().StringVar(&parent, "parent", parent, "Parent cert name to use. Use 'ca' for the toplevel CA.") -} - -func runCreateCA(cmd *cobra.Command, args []string) { - tlstest.CreateCA(root) -} - -func runCreateIntermediateCA(cmd *cobra.Command, args []string) { - name := args[0] - if commonName == "" { - commonName = name - } - - tlstest.CreateIntermediateCA(root, parent, serial, name, commonName) -} - -func runCreateCRL(cmd *cobra.Command, args []string) { - ca := args[0] - tlstest.CreateCRL(root, ca) -} - -func runCreateSignedCert(cmd *cobra.Command, args []string) { - name := args[0] - if commonName == "" { - commonName = name - } - - tlstest.CreateSignedCert(root, parent, serial, name, commonName) -} - -func runRevokeCert(cmd *cobra.Command, args []string) { - name := args[0] - tlstest.RevokeCertAndRegenerateCRL(root, parent, name) -} - func main() { defer exit.Recover() defer logutil.Flush() - cobra.CheckErr(rootCmd.Execute()) + cobra.CheckErr(cli.Root.Execute()) } From 434dfef3931c75b91ebea35de0d5f094efc150ef Mon Sep 17 00:00:00 2001 From: Andrew Mason Date: Tue, 26 Sep 2023 13:22:03 -0400 Subject: [PATCH 09/11] move datadir so tests work Signed-off-by: Andrew Mason --- .../data/schema/app_customer/v001__create_customer_table.sql | 0 .../data/schema/app_customer/v002__add_customer_vschema.sql | 0 .../vttestserver/{ => cli}/data/schema/app_customer/vschema.json | 0 .../data/schema/test_keyspace/v001__create_test_table.sql | 0 .../data/schema/test_keyspace/v002__create_hash_vindex.sql | 0 .../data/schema/test_keyspace/v003__add_table_vschema.sql | 0 .../data/schema/test_keyspace/v004__create_test_table1.sql | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename go/cmd/vttestserver/{ => cli}/data/schema/app_customer/v001__create_customer_table.sql (100%) rename go/cmd/vttestserver/{ => cli}/data/schema/app_customer/v002__add_customer_vschema.sql (100%) rename go/cmd/vttestserver/{ => cli}/data/schema/app_customer/vschema.json (100%) rename go/cmd/vttestserver/{ => cli}/data/schema/test_keyspace/v001__create_test_table.sql (100%) rename go/cmd/vttestserver/{ => cli}/data/schema/test_keyspace/v002__create_hash_vindex.sql (100%) rename go/cmd/vttestserver/{ => cli}/data/schema/test_keyspace/v003__add_table_vschema.sql (100%) rename go/cmd/vttestserver/{ => cli}/data/schema/test_keyspace/v004__create_test_table1.sql (100%) diff --git a/go/cmd/vttestserver/data/schema/app_customer/v001__create_customer_table.sql b/go/cmd/vttestserver/cli/data/schema/app_customer/v001__create_customer_table.sql similarity index 100% rename from go/cmd/vttestserver/data/schema/app_customer/v001__create_customer_table.sql rename to go/cmd/vttestserver/cli/data/schema/app_customer/v001__create_customer_table.sql diff --git a/go/cmd/vttestserver/data/schema/app_customer/v002__add_customer_vschema.sql b/go/cmd/vttestserver/cli/data/schema/app_customer/v002__add_customer_vschema.sql similarity index 100% rename from go/cmd/vttestserver/data/schema/app_customer/v002__add_customer_vschema.sql rename to go/cmd/vttestserver/cli/data/schema/app_customer/v002__add_customer_vschema.sql diff --git a/go/cmd/vttestserver/data/schema/app_customer/vschema.json b/go/cmd/vttestserver/cli/data/schema/app_customer/vschema.json similarity index 100% rename from go/cmd/vttestserver/data/schema/app_customer/vschema.json rename to go/cmd/vttestserver/cli/data/schema/app_customer/vschema.json diff --git a/go/cmd/vttestserver/data/schema/test_keyspace/v001__create_test_table.sql b/go/cmd/vttestserver/cli/data/schema/test_keyspace/v001__create_test_table.sql similarity index 100% rename from go/cmd/vttestserver/data/schema/test_keyspace/v001__create_test_table.sql rename to go/cmd/vttestserver/cli/data/schema/test_keyspace/v001__create_test_table.sql diff --git a/go/cmd/vttestserver/data/schema/test_keyspace/v002__create_hash_vindex.sql b/go/cmd/vttestserver/cli/data/schema/test_keyspace/v002__create_hash_vindex.sql similarity index 100% rename from go/cmd/vttestserver/data/schema/test_keyspace/v002__create_hash_vindex.sql rename to go/cmd/vttestserver/cli/data/schema/test_keyspace/v002__create_hash_vindex.sql diff --git a/go/cmd/vttestserver/data/schema/test_keyspace/v003__add_table_vschema.sql b/go/cmd/vttestserver/cli/data/schema/test_keyspace/v003__add_table_vschema.sql similarity index 100% rename from go/cmd/vttestserver/data/schema/test_keyspace/v003__add_table_vschema.sql rename to go/cmd/vttestserver/cli/data/schema/test_keyspace/v003__add_table_vschema.sql diff --git a/go/cmd/vttestserver/data/schema/test_keyspace/v004__create_test_table1.sql b/go/cmd/vttestserver/cli/data/schema/test_keyspace/v004__create_test_table1.sql similarity index 100% rename from go/cmd/vttestserver/data/schema/test_keyspace/v004__create_test_table1.sql rename to go/cmd/vttestserver/cli/data/schema/test_keyspace/v004__create_test_table1.sql From 961af53aef20829ff6734c44f6e4b513d6f9ce89 Mon Sep 17 00:00:00 2001 From: Andrew Mason Date: Tue, 26 Sep 2023 13:56:02 -0400 Subject: [PATCH 10/11] migrate vtexplain to cobra Signed-off-by: Andrew Mason --- go/cmd/vtexplain/cli/vtexplain.go | 167 ++++++++++++++++++++++++++++++ go/cmd/vtexplain/docgen/main.go | 37 +++++++ go/cmd/vtexplain/vtexplain.go | 140 +------------------------ go/flags/endtoend/vtexplain.txt | 7 +- 4 files changed, 211 insertions(+), 140 deletions(-) create mode 100644 go/cmd/vtexplain/cli/vtexplain.go create mode 100644 go/cmd/vtexplain/docgen/main.go diff --git a/go/cmd/vtexplain/cli/vtexplain.go b/go/cmd/vtexplain/cli/vtexplain.go new file mode 100644 index 00000000000..f4e97cfd857 --- /dev/null +++ b/go/cmd/vtexplain/cli/vtexplain.go @@ -0,0 +1,167 @@ +/* +Copyright 2023 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cli + +import ( + "context" + "fmt" + "os" + + "vitess.io/vitess/go/acl" + "vitess.io/vitess/go/vt/logutil" + "vitess.io/vitess/go/vt/servenv" + "vitess.io/vitess/go/vt/vtexplain" + "vitess.io/vitess/go/vt/vtgate/planbuilder/plancontext" + + "github.com/spf13/cobra" + + querypb "vitess.io/vitess/go/vt/proto/query" +) + +var ( + sqlFlag string + sqlFileFlag string + schemaFlag string + schemaFileFlag string + vschemaFlag string + vschemaFileFlag string + ksShardMapFlag string + ksShardMapFileFlag string + normalize bool + dbName string + plannerVersionStr string + + numShards = 2 + replicationMode = "ROW" + executionMode = "multi" + outputMode = "text" + + Main = &cobra.Command{ + Use: "vtexplain", + Short: "", + Args: cobra.NoArgs, + PreRunE: servenv.CobraPreRunE, + RunE: run, + } +) + +func init() { + servenv.MoveFlagsToCobraCommand(Main) + Main.Flags().StringVar(&sqlFlag, "sql", sqlFlag, "A list of semicolon-delimited SQL commands to analyze") + Main.Flags().StringVar(&sqlFileFlag, "sql-file", sqlFileFlag, "Identifies the file that contains the SQL commands to analyze") + Main.Flags().StringVar(&schemaFlag, "schema", schemaFlag, "The SQL table schema") + Main.Flags().StringVar(&schemaFileFlag, "schema-file", schemaFileFlag, "Identifies the file that contains the SQL table schema") + Main.Flags().StringVar(&vschemaFlag, "vschema", vschemaFlag, "Identifies the VTGate routing schema") + Main.Flags().StringVar(&vschemaFileFlag, "vschema-file", vschemaFileFlag, "Identifies the VTGate routing schema file") + Main.Flags().StringVar(&ksShardMapFlag, "ks-shard-map", ksShardMapFlag, "JSON map of keyspace name -> shard name -> ShardReference object. The inner map is the same as the output of FindAllShardsInKeyspace") + Main.Flags().StringVar(&ksShardMapFileFlag, "ks-shard-map-file", ksShardMapFileFlag, "File containing json blob of keyspace name -> shard name -> ShardReference object") + Main.Flags().StringVar(&replicationMode, "replication-mode", replicationMode, "The replication mode to simulate -- must be set to either ROW or STATEMENT") + Main.Flags().BoolVar(&normalize, "normalize", normalize, "Whether to enable vtgate normalization") + Main.Flags().StringVar(&dbName, "dbname", dbName, "Optional database target to override normal routing") + Main.Flags().StringVar(&plannerVersionStr, "planner-version", plannerVersionStr, "Sets the default planner to use. Valid values are: Gen4, Gen4Greedy, Gen4Left2Right") + Main.Flags().IntVar(&numShards, "shards", numShards, "Number of shards per keyspace. Passing --ks-shard-map/--ks-shard-map-file causes this flag to be ignored.") + Main.Flags().StringVar(&executionMode, "execution-mode", executionMode, "The execution mode to simulate -- must be set to multi, legacy-autocommit, or twopc") + Main.Flags().StringVar(&outputMode, "output-mode", outputMode, "Output in human-friendly text or json") + + acl.RegisterFlags(Main.Flags()) +} + +// getFileParam returns a string containing either flag is not "", +// or the content of the file named flagFile +func getFileParam(flag, flagFile, name string, required bool) (string, error) { + if flag != "" { + if flagFile != "" { + return "", fmt.Errorf("action requires only one of %v or %v-file", name, name) + } + return flag, nil + } + + if flagFile == "" { + if required { + return "", fmt.Errorf("action requires one of %v or %v-file", name, name) + } + + return "", nil + } + data, err := os.ReadFile(flagFile) + if err != nil { + return "", fmt.Errorf("cannot read file %v: %v", flagFile, err) + } + return string(data), nil +} + +func run(cmd *cobra.Command, args []string) error { + defer logutil.Flush() + + servenv.Init() + return parseAndRun() +} + +func parseAndRun() error { + plannerVersion, _ := plancontext.PlannerNameToVersion(plannerVersionStr) + if plannerVersionStr != "" && plannerVersion != querypb.ExecuteOptions_Gen4 { + return fmt.Errorf("invalid value specified for planner-version of '%s' -- valid value is Gen4 or an empty value to use the default planner", plannerVersionStr) + } + + sql, err := getFileParam(sqlFlag, sqlFileFlag, "sql", true) + if err != nil { + return err + } + + schema, err := getFileParam(schemaFlag, schemaFileFlag, "schema", true) + if err != nil { + return err + } + + vschema, err := getFileParam(vschemaFlag, vschemaFileFlag, "vschema", true) + if err != nil { + return err + } + + ksShardMap, err := getFileParam(ksShardMapFlag, ksShardMapFileFlag, "ks-shard-map", false) + if err != nil { + return err + } + + opts := &vtexplain.Options{ + ExecutionMode: executionMode, + PlannerVersion: plannerVersion, + ReplicationMode: replicationMode, + NumShards: numShards, + Normalize: normalize, + Target: dbName, + } + + vte, err := vtexplain.Init(context.Background(), vschema, schema, ksShardMap, opts) + if err != nil { + return err + } + defer vte.Stop() + + plans, err := vte.Run(sql) + if err != nil { + return err + } + + if outputMode == "text" { + fmt.Print(vte.ExplainsAsText(plans)) + } else { + fmt.Print(vtexplain.ExplainsAsJSON(plans)) + } + + return nil +} diff --git a/go/cmd/vtexplain/docgen/main.go b/go/cmd/vtexplain/docgen/main.go new file mode 100644 index 00000000000..15ea92b53bb --- /dev/null +++ b/go/cmd/vtexplain/docgen/main.go @@ -0,0 +1,37 @@ +/* +Copyright 2023 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "github.com/spf13/cobra" + + "vitess.io/vitess/go/cmd/internal/docgen" + "vitess.io/vitess/go/cmd/vtexplain/cli" +) + +func main() { + var dir string + cmd := cobra.Command{ + Use: "docgen [-d ]", + RunE: func(cmd *cobra.Command, args []string) error { + return docgen.GenerateMarkdownTree(cli.Main, dir) + }, + } + + cmd.Flags().StringVarP(&dir, "dir", "d", "doc", "output directory to write documentation") + _ = cmd.Execute() +} diff --git a/go/cmd/vtexplain/vtexplain.go b/go/cmd/vtexplain/vtexplain.go index 68ceed51316..37774076382 100644 --- a/go/cmd/vtexplain/vtexplain.go +++ b/go/cmd/vtexplain/vtexplain.go @@ -17,153 +17,17 @@ limitations under the License. package main import ( - "context" "fmt" - "os" - "vitess.io/vitess/go/acl" + "vitess.io/vitess/go/cmd/vtexplain/cli" "vitess.io/vitess/go/exit" - "vitess.io/vitess/go/vt/logutil" - "vitess.io/vitess/go/vt/servenv" - "vitess.io/vitess/go/vt/vtexplain" - "vitess.io/vitess/go/vt/vtgate/planbuilder/plancontext" - - "github.com/spf13/pflag" - - querypb "vitess.io/vitess/go/vt/proto/query" -) - -var ( - sqlFlag string - sqlFileFlag string - schemaFlag string - schemaFileFlag string - vschemaFlag string - vschemaFileFlag string - ksShardMapFlag string - ksShardMapFileFlag string - normalize bool - dbName string - plannerVersionStr string - - numShards = 2 - replicationMode = "ROW" - executionMode = "multi" - outputMode = "text" ) -func registerFlags(fs *pflag.FlagSet) { - fs.StringVar(&sqlFlag, "sql", sqlFlag, "A list of semicolon-delimited SQL commands to analyze") - fs.StringVar(&sqlFileFlag, "sql-file", sqlFileFlag, "Identifies the file that contains the SQL commands to analyze") - fs.StringVar(&schemaFlag, "schema", schemaFlag, "The SQL table schema") - fs.StringVar(&schemaFileFlag, "schema-file", schemaFileFlag, "Identifies the file that contains the SQL table schema") - fs.StringVar(&vschemaFlag, "vschema", vschemaFlag, "Identifies the VTGate routing schema") - fs.StringVar(&vschemaFileFlag, "vschema-file", vschemaFileFlag, "Identifies the VTGate routing schema file") - fs.StringVar(&ksShardMapFlag, "ks-shard-map", ksShardMapFlag, "JSON map of keyspace name -> shard name -> ShardReference object. The inner map is the same as the output of FindAllShardsInKeyspace") - fs.StringVar(&ksShardMapFileFlag, "ks-shard-map-file", ksShardMapFileFlag, "File containing json blob of keyspace name -> shard name -> ShardReference object") - fs.StringVar(&replicationMode, "replication-mode", replicationMode, "The replication mode to simulate -- must be set to either ROW or STATEMENT") - fs.BoolVar(&normalize, "normalize", normalize, "Whether to enable vtgate normalization") - fs.StringVar(&dbName, "dbname", dbName, "Optional database target to override normal routing") - fs.StringVar(&plannerVersionStr, "planner-version", plannerVersionStr, "Sets the default planner to use. Valid values are: Gen4, Gen4Greedy, Gen4Left2Right") - fs.IntVar(&numShards, "shards", numShards, "Number of shards per keyspace. Passing --ks-shard-map/--ks-shard-map-file causes this flag to be ignored.") - fs.StringVar(&executionMode, "execution-mode", executionMode, "The execution mode to simulate -- must be set to multi, legacy-autocommit, or twopc") - fs.StringVar(&outputMode, "output-mode", outputMode, "Output in human-friendly text or json") - - acl.RegisterFlags(fs) -} - -func init() { - servenv.OnParse(registerFlags) -} - -// getFileParam returns a string containing either flag is not "", -// or the content of the file named flagFile -func getFileParam(flag, flagFile, name string, required bool) (string, error) { - if flag != "" { - if flagFile != "" { - return "", fmt.Errorf("action requires only one of %v or %v-file", name, name) - } - return flag, nil - } - - if flagFile == "" { - if required { - return "", fmt.Errorf("action requires one of %v or %v-file", name, name) - } - - return "", nil - } - data, err := os.ReadFile(flagFile) - if err != nil { - return "", fmt.Errorf("cannot read file %v: %v", flagFile, err) - } - return string(data), nil -} - func main() { defer exit.RecoverAll() - defer logutil.Flush() - servenv.ParseFlags("vtexplain") - servenv.Init() - err := parseAndRun() - if err != nil { + if err := cli.Main.Execute(); err != nil { fmt.Printf("ERROR: %s\n", err) exit.Return(1) } } - -func parseAndRun() error { - plannerVersion, _ := plancontext.PlannerNameToVersion(plannerVersionStr) - if plannerVersionStr != "" && plannerVersion != querypb.ExecuteOptions_Gen4 { - return fmt.Errorf("invalid value specified for planner-version of '%s' -- valid value is Gen4 or an empty value to use the default planner", plannerVersionStr) - } - - sql, err := getFileParam(sqlFlag, sqlFileFlag, "sql", true) - if err != nil { - return err - } - - schema, err := getFileParam(schemaFlag, schemaFileFlag, "schema", true) - if err != nil { - return err - } - - vschema, err := getFileParam(vschemaFlag, vschemaFileFlag, "vschema", true) - if err != nil { - return err - } - - ksShardMap, err := getFileParam(ksShardMapFlag, ksShardMapFileFlag, "ks-shard-map", false) - if err != nil { - return err - } - - opts := &vtexplain.Options{ - ExecutionMode: executionMode, - PlannerVersion: plannerVersion, - ReplicationMode: replicationMode, - NumShards: numShards, - Normalize: normalize, - Target: dbName, - } - - vte, err := vtexplain.Init(context.Background(), vschema, schema, ksShardMap, opts) - if err != nil { - return err - } - defer vte.Stop() - - plans, err := vte.Run(sql) - if err != nil { - return err - } - - if outputMode == "text" { - fmt.Print(vte.ExplainsAsText(plans)) - } else { - fmt.Print(vtexplain.ExplainsAsJSON(plans)) - } - - return nil -} diff --git a/go/flags/endtoend/vtexplain.txt b/go/flags/endtoend/vtexplain.txt index 39adec5467c..94c8d87fbf3 100644 --- a/go/flags/endtoend/vtexplain.txt +++ b/go/flags/endtoend/vtexplain.txt @@ -1,4 +1,7 @@ -Usage of vtexplain: +Usage: + vtexplain [flags] + +Flags: --alsologtostderr log to standard error as well as files --batch-interval duration Interval between logical time slots. (default 10ms) --config-file string Full path of the config file (with extension) to use. If set, --config-path, --config-type, and --config-name are ignored. @@ -10,7 +13,7 @@ Usage of vtexplain: --dbname string Optional database target to override normal routing --default_tablet_type topodatapb.TabletType The default tablet type to set for queries, when one is not explicitly selected. (default PRIMARY) --execution-mode string The execution mode to simulate -- must be set to multi, legacy-autocommit, or twopc (default "multi") - -h, --help display usage and exit + -h, --help help for vtexplain --keep_logs duration keep logs for this long (using ctime) (zero to keep forever) --keep_logs_by_mtime duration keep logs for this long (using mtime) (zero to keep forever) --ks-shard-map string JSON map of keyspace name -> shard name -> ShardReference object. The inner map is the same as the output of FindAllShardsInKeyspace From 408a2cc02162f308e589c4b3f7e0e1c746c36254 Mon Sep 17 00:00:00 2001 From: Andrew Mason Date: Tue, 26 Sep 2023 14:19:25 -0400 Subject: [PATCH 11/11] add help and example Signed-off-by: Andrew Mason --- go/cmd/vtexplain/cli/vtexplain.go | 33 ++++++++++++++++++++++++++-- go/flags/endtoend/vtexplain.txt | 36 +++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/go/cmd/vtexplain/cli/vtexplain.go b/go/cmd/vtexplain/cli/vtexplain.go index f4e97cfd857..8b0622cf8a3 100644 --- a/go/cmd/vtexplain/cli/vtexplain.go +++ b/go/cmd/vtexplain/cli/vtexplain.go @@ -51,8 +51,37 @@ var ( outputMode = "text" Main = &cobra.Command{ - Use: "vtexplain", - Short: "", + Use: "vtexplain", + Short: "vtexplain is a command line tool which provides information on how Vitess plans to execute a particular query.", + Long: `vtexplain is a command line tool which provides information on how Vitess plans to execute a particular query. + +It can be used to validate queries for compatibility with Vitess. + +For a user guide that describes how to use the vtexplain tool to explain how Vitess executes a particular SQL statement, see Analyzing a SQL statement. + +## Limitations + +### The VSchema must use a keyspace name. + +VTExplain requires a keyspace name for each keyspace in an input VSchema: +` + + "```\n" + + `"keyspace_name": { + "_comment": "Keyspace definition goes here." +} +` + "```" + ` + +If no keyspace name is present, VTExplain will return the following error: +` + + "```\n" + + `ERROR: initVtgateExecutor: json: cannot unmarshal bool into Go value of type map[string]json.RawMessage +` + "```\n", + Example: "Explain how Vitess will execute the query `SELECT * FROM users` using the VSchema contained in `vschemas.json` and the database schema `schema.sql`:\n\n" + + "```\nvtexplain --vschema-file vschema.json --schema-file schema.sql --sql \"SELECT * FROM users\"\n```\n\n" + + + "Explain how the example will execute on 128 shards using Row-based replication:\n\n" + + + "```\nvtexplain -- -shards 128 --vschema-file vschema.json --schema-file schema.sql --replication-mode \"ROW\" --output-mode text --sql \"INSERT INTO users (user_id, name) VALUES(1, 'john')\"\n```\n", Args: cobra.NoArgs, PreRunE: servenv.CobraPreRunE, RunE: run, diff --git a/go/flags/endtoend/vtexplain.txt b/go/flags/endtoend/vtexplain.txt index 94c8d87fbf3..f75559474c0 100644 --- a/go/flags/endtoend/vtexplain.txt +++ b/go/flags/endtoend/vtexplain.txt @@ -1,6 +1,42 @@ +vtexplain is a command line tool which provides information on how Vitess plans to execute a particular query. + +It can be used to validate queries for compatibility with Vitess. + +For a user guide that describes how to use the vtexplain tool to explain how Vitess executes a particular SQL statement, see Analyzing a SQL statement. + +## Limitations + +### The VSchema must use a keyspace name. + +VTExplain requires a keyspace name for each keyspace in an input VSchema: +``` +"keyspace_name": { + "_comment": "Keyspace definition goes here." +} +``` + +If no keyspace name is present, VTExplain will return the following error: +``` +ERROR: initVtgateExecutor: json: cannot unmarshal bool into Go value of type map[string]json.RawMessage +``` + Usage: vtexplain [flags] +Examples: +Explain how Vitess will execute the query `SELECT * FROM users` using the VSchema contained in `vschemas.json` and the database schema `schema.sql`: + +``` +vtexplain --vschema-file vschema.json --schema-file schema.sql --sql "SELECT * FROM users" +``` + +Explain how the example will execute on 128 shards using Row-based replication: + +``` +vtexplain -- -shards 128 --vschema-file vschema.json --schema-file schema.sql --replication-mode "ROW" --output-mode text --sql "INSERT INTO users (user_id, name) VALUES(1, 'john')" +``` + + Flags: --alsologtostderr log to standard error as well as files --batch-interval duration Interval between logical time slots. (default 10ms)