diff --git a/go/cmd/mysqlctl/command/init.go b/go/cmd/mysqlctl/command/init.go new file mode 100644 index 00000000000..71a9661aa80 --- /dev/null +++ b/go/cmd/mysqlctl/command/init.go @@ -0,0 +1,71 @@ +/* +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 command + +import ( + "context" + "fmt" + "time" + + "github.com/spf13/cobra" + + "vitess.io/vitess/go/vt/mysqlctl" +) + +var Init = &cobra.Command{ + Use: "init", + Short: "Initializes the directory structure and starts mysqld.", + Long: "Bootstraps a new `mysqld` instance, initializes its data directory, and starts the instance.\n" + + "The MySQL version and flavor will be auto-detected, with a minimal configuration file applied.", + Example: `mysqlctl \ + --alsologtostderr \ + --tablet_uid 101 \ + --mysql_port 12345 \ + init`, + Args: cobra.NoArgs, + RunE: commandInit, +} + +var initArgs = struct { + WaitTime time.Duration + InitDbSQLFile string +}{ + WaitTime: 5 * time.Minute, +} + +func commandInit(cmd *cobra.Command, args []string) error { + // Generate my.cnf from scratch and use it to find mysqld. + mysqld, cnf, err := mysqlctl.CreateMysqldAndMycnf(tabletUID, mysqlSocket, mysqlPort) + if err != nil { + return fmt.Errorf("failed to initialize mysql config: %v", err) + } + defer mysqld.Close() + + ctx, cancel := context.WithTimeout(context.Background(), initArgs.WaitTime) + defer cancel() + if err := mysqld.Init(ctx, cnf, initArgs.InitDbSQLFile); err != nil { + return fmt.Errorf("failed init mysql: %v", err) + } + return nil +} + +func init() { + Init.Flags().DurationVar(&initArgs.WaitTime, "wait_time", initArgs.WaitTime, "How long to wait for mysqld startup.") + Init.Flags().StringVar(&initArgs.InitDbSQLFile, "init_db_sql_file", initArgs.InitDbSQLFile, "Path to .sql file to run after mysqld initiliaztion.") + + Root.AddCommand(Init) +} diff --git a/go/cmd/mysqlctl/command/init_config.go b/go/cmd/mysqlctl/command/init_config.go new file mode 100644 index 00000000000..70e751e02cb --- /dev/null +++ b/go/cmd/mysqlctl/command/init_config.go @@ -0,0 +1,57 @@ +/* +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 command + +import ( + "fmt" + + "github.com/spf13/cobra" + + "vitess.io/vitess/go/vt/mysqlctl" +) + +var InitConfig = &cobra.Command{ + Use: "init_config", + Short: "Initializes the directory structure, creates my.cnf file, but does not start mysqld.", + Long: "Bootstraps the configuration for a new `mysqld` instance and initializes its data directory.\n" + + "This command is the same as `init` except the `mysqld` server will not be started.", + Example: `mysqlctl \ + --alsologtostderr \ + --tablet_uid 101 \ + --mysql_port 12345 \ + init_config`, + Args: cobra.NoArgs, + RunE: commandInitConfig, +} + +func commandInitConfig(cmd *cobra.Command, args []string) error { + // Generate my.cnf from scratch and use it to find mysqld. + mysqld, cnf, err := mysqlctl.CreateMysqldAndMycnf(tabletUID, mysqlSocket, mysqlPort) + if err != nil { + return fmt.Errorf("failed to initialize mysql config: %v", err) + } + defer mysqld.Close() + if err := mysqld.InitConfig(cnf); err != nil { + return fmt.Errorf("failed to init mysql config: %v", err) + } + + return nil +} + +func init() { + Root.AddCommand(InitConfig) +} diff --git a/go/cmd/mysqlctl/plugin_prometheusbackend.go b/go/cmd/mysqlctl/command/plugin_prometheusbackend.go similarity index 98% rename from go/cmd/mysqlctl/plugin_prometheusbackend.go rename to go/cmd/mysqlctl/command/plugin_prometheusbackend.go index 62853982f11..7376af743a4 100644 --- a/go/cmd/mysqlctl/plugin_prometheusbackend.go +++ b/go/cmd/mysqlctl/command/plugin_prometheusbackend.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package command // This plugin imports Prometheus to allow for instrumentation // with the Prometheus client library diff --git a/go/cmd/mysqlctl/command/position.go b/go/cmd/mysqlctl/command/position.go new file mode 100644 index 00000000000..46f848e1bbb --- /dev/null +++ b/go/cmd/mysqlctl/command/position.go @@ -0,0 +1,74 @@ +/* +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 command + +import ( + "fmt" + + "github.com/spf13/cobra" + + "vitess.io/vitess/go/mysql/replication" +) + +var Position = &cobra.Command{ + Use: "position ", + Short: "Compute operations on replication positions", + Args: cobra.MatchAll(cobra.ExactArgs(3), func(cmd *cobra.Command, args []string) error { + switch args[0] { + case "equal", "at_least", "append": + default: + return fmt.Errorf("invalid operation %s (choices are 'equal', 'at_least', 'append')", args[0]) + } + + return nil + }), + RunE: commandPosition, +} + +func commandPosition(cmd *cobra.Command, args []string) error { + pos1, err := replication.DecodePosition(args[1]) + if err != nil { + return err + } + + switch args[0] { + case "equal": + pos2, err := replication.DecodePosition(args[2]) + if err != nil { + return err + } + fmt.Println(pos1.Equal(pos2)) + case "at_least": + pos2, err := replication.DecodePosition(args[2]) + if err != nil { + return err + } + fmt.Println(pos1.AtLeast(pos2)) + case "append": + gtid, err := replication.DecodeGTID(args[2]) + if err != nil { + return err + } + fmt.Println(replication.AppendGTID(pos1, gtid)) + } + + return nil +} + +func init() { + Root.AddCommand(Position) +} diff --git a/go/cmd/mysqlctl/command/reinit_config.go b/go/cmd/mysqlctl/command/reinit_config.go new file mode 100644 index 00000000000..b06642c8203 --- /dev/null +++ b/go/cmd/mysqlctl/command/reinit_config.go @@ -0,0 +1,58 @@ +/* +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 command + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + + "vitess.io/vitess/go/vt/mysqlctl" +) + +var ReinitConfig = &cobra.Command{ + Use: "reinit_config", + Short: "Reinitializes my.cnf file with new server_id.", + Long: "Regenerate new configuration files for an existing `mysqld` instance (generating new server_id and server_uuid values).\n" + + "This could be helpful to revert configuration changes, or to pick up changes made to the bundled config in newer Vitess versions.", + Example: `mysqlctl \ + --alsologtostderr \ + --tablet_uid 101 \ + --mysql_port 12345 \ + reinit_config`, + Args: cobra.NoArgs, + RunE: commandReinitConfig, +} + +func commandReinitConfig(cmd *cobra.Command, args []string) error { + // There ought to be an existing my.cnf, so use it to find mysqld. + mysqld, cnf, err := mysqlctl.OpenMysqldAndMycnf(tabletUID) + if err != nil { + return fmt.Errorf("failed to find mysql config: %v", err) + } + defer mysqld.Close() + + if err := mysqld.ReinitConfig(context.TODO(), cnf); err != nil { + return fmt.Errorf("failed to reinit mysql config: %v", err) + } + return nil +} + +func init() { + Root.AddCommand(ReinitConfig) +} diff --git a/go/cmd/mysqlctl/command/root.go b/go/cmd/mysqlctl/command/root.go new file mode 100644 index 00000000000..4f5626ef7e6 --- /dev/null +++ b/go/cmd/mysqlctl/command/root.go @@ -0,0 +1,77 @@ +/* +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 command + +import ( + "fmt" + + "github.com/spf13/cobra" + + "vitess.io/vitess/go/acl" + vtcmd "vitess.io/vitess/go/cmd" + "vitess.io/vitess/go/vt/dbconfigs" + "vitess.io/vitess/go/vt/logutil" + "vitess.io/vitess/go/vt/servenv" +) + +var ( + mysqlPort = 3306 + tabletUID = uint32(41983) + mysqlSocket string + + Root = &cobra.Command{ + Use: "mysqlctl", + Short: "mysqlctl initializes and controls mysqld with Vitess-specific configuration.", + Long: "`mysqlctl` is a command-line client used for managing `mysqld` instances.\n\n" + + + "It is responsible for bootstrapping tasks such as generating a configuration file for `mysqld` and initializing the instance and its data directory.\n" + + "The `mysqld_safe` watchdog is utilized when present.\n" + + "This helps ensure that `mysqld` is automatically restarted after failures.", + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + if err := servenv.CobraPreRunE(cmd, args); err != nil { + return nil + } + + if vtcmd.IsRunningAsRoot() { + return fmt.Errorf("mysqlctl cannot be run as root. Please run as a different user") + } + + return nil + }, + PersistentPostRun: func(cmd *cobra.Command, args []string) { + logutil.Flush() + }, + Version: servenv.AppVersion.String(), + } +) + +func init() { + servenv.RegisterDefaultSocketFileFlags() + servenv.RegisterFlags() + servenv.RegisterServiceMapFlag() + + // mysqlctl only starts and stops mysql, only needs dba. + dbconfigs.RegisterFlags(dbconfigs.Dba) + + servenv.MovePersistentFlagsToCobraCommand(Root) + + Root.PersistentFlags().IntVar(&mysqlPort, "mysql_port", mysqlPort, "MySQL port.") + Root.PersistentFlags().Uint32Var(&tabletUID, "tablet_uid", tabletUID, "Tablet UID.") + Root.PersistentFlags().StringVar(&mysqlSocket, "mysql_socket", mysqlSocket, "Path to the mysqld socket file.") + + acl.RegisterFlags(Root.PersistentFlags()) +} diff --git a/go/cmd/mysqlctl/command/shutdown.go b/go/cmd/mysqlctl/command/shutdown.go new file mode 100644 index 00000000000..41c804856eb --- /dev/null +++ b/go/cmd/mysqlctl/command/shutdown.go @@ -0,0 +1,66 @@ +/* +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 command + +import ( + "context" + "fmt" + "time" + + "github.com/spf13/cobra" + + "vitess.io/vitess/go/vt/mysqlctl" +) + +var Shutdown = &cobra.Command{ + Use: "shutdown", + Short: "Shuts down mysqld, without removing any files.", + Long: "Stop a `mysqld` instance that was previously started with `init` or `start`.\n\n" + + + "For large `mysqld` instances, you may need to extend the `wait_time` to shutdown cleanly.", + Example: `mysqlctl --tablet_uid 101 --alsologtostderr shutdown`, + Args: cobra.NoArgs, + RunE: commandShutdown, +} + +var shutdownArgs = struct { + WaitTime time.Duration +}{ + WaitTime: 5 * time.Minute, +} + +func commandShutdown(cmd *cobra.Command, args []string) error { + // There ought to be an existing my.cnf, so use it to find mysqld. + mysqld, cnf, err := mysqlctl.OpenMysqldAndMycnf(tabletUID) + if err != nil { + return fmt.Errorf("failed to find mysql config: %v", err) + } + defer mysqld.Close() + + ctx, cancel := context.WithTimeout(context.Background(), shutdownArgs.WaitTime) + defer cancel() + if err := mysqld.Shutdown(ctx, cnf, true); err != nil { + return fmt.Errorf("failed shutdown mysql: %v", err) + } + return nil +} + +func init() { + Shutdown.Flags().DurationVar(&shutdownArgs.WaitTime, "wait_time", shutdownArgs.WaitTime, "How long to wait for mysqld shutdown.") + + Root.AddCommand(Shutdown) +} diff --git a/go/cmd/mysqlctl/command/start.go b/go/cmd/mysqlctl/command/start.go new file mode 100644 index 00000000000..397909e0966 --- /dev/null +++ b/go/cmd/mysqlctl/command/start.go @@ -0,0 +1,67 @@ +/* +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 command + +import ( + "context" + "fmt" + "time" + + "github.com/spf13/cobra" + + "vitess.io/vitess/go/flagutil" + "vitess.io/vitess/go/vt/mysqlctl" +) + +var Start = &cobra.Command{ + Use: "start", + Short: "Starts mysqld on an already 'init'-ed directory.", + Long: "Resume an existing `mysqld` instance that was previously bootstrapped with `init` or `init_config`", + Example: `mysqlctl --tablet_uid 101 --alsologtostderr start`, + Args: cobra.NoArgs, + RunE: commandStart, +} + +var startArgs = struct { + WaitTime time.Duration + MySQLdArgs flagutil.StringListValue +}{ + WaitTime: 5 * time.Minute, +} + +func commandStart(cmd *cobra.Command, args []string) error { + // There ought to be an existing my.cnf, so use it to find mysqld. + mysqld, cnf, err := mysqlctl.OpenMysqldAndMycnf(tabletUID) + if err != nil { + return fmt.Errorf("failed to find mysql config: %v", err) + } + defer mysqld.Close() + + ctx, cancel := context.WithTimeout(context.Background(), startArgs.WaitTime) + defer cancel() + if err := mysqld.Start(ctx, cnf, startArgs.MySQLdArgs...); err != nil { + return fmt.Errorf("failed start mysql: %v", err) + } + return nil +} + +func init() { + Start.Flags().DurationVar(&startArgs.WaitTime, "wait_time", startArgs.WaitTime, "How long to wait for mysqld startup.") + Start.Flags().Var(&startArgs.MySQLdArgs, "mysqld_args", "List of comma-separated flags to pass additionally to mysqld.") + + Root.AddCommand(Start) +} diff --git a/go/cmd/mysqlctl/command/teardown.go b/go/cmd/mysqlctl/command/teardown.go new file mode 100644 index 00000000000..0d37a15cfdc --- /dev/null +++ b/go/cmd/mysqlctl/command/teardown.go @@ -0,0 +1,70 @@ +/* +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 command + +import ( + "context" + "fmt" + "time" + + "github.com/spf13/cobra" + + "vitess.io/vitess/go/vt/mysqlctl" +) + +var Teardown = &cobra.Command{ + Use: "teardown", + Short: "Shuts mysqld down and removes the directory.", + Long: "{{< warning >}}\n" + + "This is a destructive operation.\n" + + "{{}}\n\n" + + + "Shuts down a `mysqld` instance and removes its data directory.", + Example: `mysqlctl --tablet_uid 101 --alsologtostderr teardown`, + Args: cobra.NoArgs, + RunE: commandTeardown, +} + +var teardownArgs = struct { + WaitTime time.Duration + Force bool +}{ + WaitTime: 5 * time.Minute, +} + +func commandTeardown(cmd *cobra.Command, args []string) error { + // There ought to be an existing my.cnf, so use it to find mysqld. + mysqld, cnf, err := mysqlctl.OpenMysqldAndMycnf(tabletUID) + if err != nil { + return fmt.Errorf("failed to find mysql config: %v", err) + } + defer mysqld.Close() + + ctx, cancel := context.WithTimeout(context.Background(), teardownArgs.WaitTime) + defer cancel() + if err := mysqld.Teardown(ctx, cnf, teardownArgs.Force); err != nil { + return fmt.Errorf("failed teardown mysql (forced? %v): %v", teardownArgs.Force, err) + } + return nil +} + +func init() { + Teardown.Flags().DurationVar(&teardownArgs.WaitTime, "wait_time", teardownArgs.WaitTime, "How long to wait for mysqld shutdown.") + Teardown.Flags().BoolVarP(&teardownArgs.Force, "force", "f", teardownArgs.Force, "Remove the root directory even if mysqld shutdown fails.") + + Root.AddCommand(Teardown) +} diff --git a/go/cmd/mysqlctl/docgen/main.go b/go/cmd/mysqlctl/docgen/main.go new file mode 100644 index 00000000000..2162b5e8551 --- /dev/null +++ b/go/cmd/mysqlctl/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/mysqlctl/command" +) + +func main() { + var dir string + cmd := cobra.Command{ + Use: "docgen [-d ]", + RunE: func(cmd *cobra.Command, args []string) error { + return docgen.GenerateMarkdownTree(command.Root, dir) + }, + } + + cmd.Flags().StringVarP(&dir, "dir", "d", "doc", "output directory to write documentation") + _ = cmd.Execute() +} diff --git a/go/cmd/mysqlctl/mysqlctl.go b/go/cmd/mysqlctl/mysqlctl.go index ba59309e981..72198c2c8c0 100644 --- a/go/cmd/mysqlctl/mysqlctl.go +++ b/go/cmd/mysqlctl/mysqlctl.go @@ -18,269 +18,12 @@ limitations under the License. package main import ( - "context" - "fmt" - "os" - "time" - - "github.com/spf13/pflag" - - "vitess.io/vitess/go/mysql/replication" - - "vitess.io/vitess/go/acl" - "vitess.io/vitess/go/cmd" - "vitess.io/vitess/go/exit" - "vitess.io/vitess/go/flagutil" - "vitess.io/vitess/go/vt/dbconfigs" + "vitess.io/vitess/go/cmd/mysqlctl/command" "vitess.io/vitess/go/vt/log" - "vitess.io/vitess/go/vt/logutil" - "vitess.io/vitess/go/vt/mysqlctl" - "vitess.io/vitess/go/vt/servenv" -) - -var ( - mysqlPort = 3306 - tabletUID = uint32(41983) - mysqlSocket string ) -func init() { - servenv.RegisterDefaultSocketFileFlags() - servenv.RegisterFlags() - servenv.RegisterServiceMapFlag() - // mysqlctl only starts and stops mysql, only needs dba. - dbconfigs.RegisterFlags(dbconfigs.Dba) - servenv.OnParse(func(fs *pflag.FlagSet) { - fs.IntVar(&mysqlPort, "mysql_port", mysqlPort, "MySQL port") - fs.Uint32Var(&tabletUID, "tablet_uid", tabletUID, "Tablet UID") - fs.StringVar(&mysqlSocket, "mysql_socket", mysqlSocket, "Path to the mysqld socket file") - - acl.RegisterFlags(fs) - }) -} - -func initConfigCmd(subFlags *pflag.FlagSet, args []string) error { - _ = subFlags.Parse(args) - - // Generate my.cnf from scratch and use it to find mysqld. - mysqld, cnf, err := mysqlctl.CreateMysqldAndMycnf(tabletUID, mysqlSocket, mysqlPort) - if err != nil { - return fmt.Errorf("failed to initialize mysql config: %v", err) - } - defer mysqld.Close() - if err := mysqld.InitConfig(cnf); err != nil { - return fmt.Errorf("failed to init mysql config: %v", err) - } - return nil -} - -func initCmd(subFlags *pflag.FlagSet, args []string) error { - waitTime := subFlags.Duration("wait_time", 5*time.Minute, "How long to wait for mysqld startup") - initDBSQLFile := subFlags.String("init_db_sql_file", "", "Path to .sql file to run after mysqld initiliaztion") - _ = subFlags.Parse(args) - - // Generate my.cnf from scratch and use it to find mysqld. - mysqld, cnf, err := mysqlctl.CreateMysqldAndMycnf(tabletUID, mysqlSocket, mysqlPort) - if err != nil { - return fmt.Errorf("failed to initialize mysql config: %v", err) - } - defer mysqld.Close() - - ctx, cancel := context.WithTimeout(context.Background(), *waitTime) - defer cancel() - if err := mysqld.Init(ctx, cnf, *initDBSQLFile); err != nil { - return fmt.Errorf("failed init mysql: %v", err) - } - return nil -} - -func reinitConfigCmd(subFlags *pflag.FlagSet, args []string) error { - _ = subFlags.Parse(args) - - // There ought to be an existing my.cnf, so use it to find mysqld. - mysqld, cnf, err := mysqlctl.OpenMysqldAndMycnf(tabletUID) - if err != nil { - return fmt.Errorf("failed to find mysql config: %v", err) - } - defer mysqld.Close() - - if err := mysqld.ReinitConfig(context.TODO(), cnf); err != nil { - return fmt.Errorf("failed to reinit mysql config: %v", err) - } - return nil -} - -func shutdownCmd(subFlags *pflag.FlagSet, args []string) error { - waitTime := subFlags.Duration("wait_time", 5*time.Minute, "How long to wait for mysqld shutdown") - _ = subFlags.Parse(args) - - // There ought to be an existing my.cnf, so use it to find mysqld. - mysqld, cnf, err := mysqlctl.OpenMysqldAndMycnf(tabletUID) - if err != nil { - return fmt.Errorf("failed to find mysql config: %v", err) - } - defer mysqld.Close() - - ctx, cancel := context.WithTimeout(context.Background(), *waitTime) - defer cancel() - if err := mysqld.Shutdown(ctx, cnf, true); err != nil { - return fmt.Errorf("failed shutdown mysql: %v", err) - } - return nil -} - -func startCmd(subFlags *pflag.FlagSet, args []string) error { - waitTime := subFlags.Duration("wait_time", 5*time.Minute, "How long to wait for mysqld startup") - var mysqldArgs flagutil.StringListValue - subFlags.Var(&mysqldArgs, "mysqld_args", "List of comma-separated flags to pass additionally to mysqld") - _ = subFlags.Parse(args) - - // There ought to be an existing my.cnf, so use it to find mysqld. - mysqld, cnf, err := mysqlctl.OpenMysqldAndMycnf(tabletUID) - if err != nil { - return fmt.Errorf("failed to find mysql config: %v", err) - } - defer mysqld.Close() - - ctx, cancel := context.WithTimeout(context.Background(), *waitTime) - defer cancel() - if err := mysqld.Start(ctx, cnf, mysqldArgs...); err != nil { - return fmt.Errorf("failed start mysql: %v", err) - } - return nil -} - -func teardownCmd(subFlags *pflag.FlagSet, args []string) error { - waitTime := subFlags.Duration("wait_time", 5*time.Minute, "How long to wait for mysqld shutdown") - force := subFlags.Bool("force", false, "Remove the root directory even if mysqld shutdown fails") - _ = subFlags.Parse(args) - - // There ought to be an existing my.cnf, so use it to find mysqld. - mysqld, cnf, err := mysqlctl.OpenMysqldAndMycnf(tabletUID) - if err != nil { - return fmt.Errorf("failed to find mysql config: %v", err) - } - defer mysqld.Close() - - ctx, cancel := context.WithTimeout(context.Background(), *waitTime) - defer cancel() - if err := mysqld.Teardown(ctx, cnf, *force); err != nil { - return fmt.Errorf("failed teardown mysql (forced? %v): %v", *force, err) - } - return nil -} - -func positionCmd(subFlags *pflag.FlagSet, args []string) error { - _ = subFlags.Parse(args) - if len(args) < 3 { - return fmt.Errorf("not enough arguments for position operation") - } - - pos1, err := replication.DecodePosition(args[1]) - if err != nil { - return err - } - - switch args[0] { - case "equal": - pos2, err := replication.DecodePosition(args[2]) - if err != nil { - return err - } - fmt.Println(pos1.Equal(pos2)) - case "at_least": - pos2, err := replication.DecodePosition(args[2]) - if err != nil { - return err - } - fmt.Println(pos1.AtLeast(pos2)) - case "append": - gtid, err := replication.DecodeGTID(args[2]) - if err != nil { - return err - } - fmt.Println(replication.AppendGTID(pos1, gtid)) - } - - return nil -} - -type command struct { - name string - method func(*pflag.FlagSet, []string) error - params string - help string -} - -var commands = []command{ - {"init", initCmd, "[--wait_time=5m] [--init_db_sql_file=]", - "Initializes the directory structure and starts mysqld"}, - {"init_config", initConfigCmd, "", - "Initializes the directory structure, creates my.cnf file, but does not start mysqld"}, - {"reinit_config", reinitConfigCmd, "", - "Reinitializes my.cnf file with new server_id"}, - {"teardown", teardownCmd, "[--wait_time=5m] [--force]", - "Shuts mysqld down, and removes the directory"}, - {"start", startCmd, "[--wait_time=5m]", - "Starts mysqld on an already 'init'-ed directory"}, - {"shutdown", shutdownCmd, "[--wait_time=5m]", - "Shuts down mysqld, does not remove any file"}, - - {"position", positionCmd, - " ", - "Compute operations on replication positions"}, -} - func main() { - defer exit.Recover() - defer logutil.Flush() - - fs := pflag.NewFlagSet("mysqlctl", pflag.ExitOnError) - log.RegisterFlags(fs) - logutil.RegisterFlags(fs) - pflag.Usage = func() { - w := os.Stderr - fmt.Fprintf(w, "Usage: %s [global-flags] -- [command-flags]\n", os.Args[0]) - fmt.Fprintf(w, "\nThe commands are listed below. Use '%s -- {-h, --help}' for command help.\n\n", os.Args[0]) - for _, cmd := range commands { - fmt.Fprintf(w, " %s", cmd.name) - if cmd.params != "" { - fmt.Fprintf(w, " %s", cmd.params) - } - fmt.Fprintf(w, "\n") - } - fmt.Fprintf(w, "\nGlobal flags:\n") - pflag.PrintDefaults() - } - args := servenv.ParseFlagsWithArgs("mysqlctl") - - if cmd.IsRunningAsRoot() { - fmt.Fprintln(os.Stderr, "mysqlctl cannot be ran as root. Please run as a different user") - exit.Return(1) - } - - action := args[0] - for _, cmd := range commands { - if cmd.name == action { - subFlags := pflag.NewFlagSet(action, pflag.ExitOnError) - subFlags.Usage = func() { - w := os.Stderr - fmt.Fprintf(w, "Usage: %s %s %s\n\n", os.Args[0], cmd.name, cmd.params) - fmt.Fprintf(w, cmd.help) - fmt.Fprintf(w, "\n\n") - subFlags.PrintDefaults() - } - // This is logged and we want sentence capitalization and punctuation. - pflag.ErrHelp = fmt.Errorf("\nSee %s --help for more information.", os.Args[0]) // nolint:revive - if err := cmd.method(subFlags, args[1:]); err != nil { - log.Errorf("%v\n", err) - subFlags.Usage() - exit.Return(1) - } - return - } + if err := command.Root.Execute(); err != nil { + log.Exit(err) } - log.Errorf("invalid action: %v\n\n", action) - pflag.Usage() - exit.Return(1) } diff --git a/go/cmd/mysqlctld/cli/mysqlctld.go b/go/cmd/mysqlctld/cli/mysqlctld.go new file mode 100644 index 00000000000..4afbf510951 --- /dev/null +++ b/go/cmd/mysqlctld/cli/mysqlctld.go @@ -0,0 +1,180 @@ +/* +Copyright 2019 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. +*/ + +// mysqlctld is a daemon that starts or initializes mysqld and provides an RPC +// interface for vttablet to stop and start mysqld from a different container +// without having to restart the container running mysqlctld. +package cli + +import ( + "context" + "fmt" + "os" + "time" + + "github.com/spf13/cobra" + + "vitess.io/vitess/go/acl" + "vitess.io/vitess/go/vt/dbconfigs" + "vitess.io/vitess/go/vt/log" + "vitess.io/vitess/go/vt/logutil" + "vitess.io/vitess/go/vt/mysqlctl" + "vitess.io/vitess/go/vt/servenv" +) + +var ( + // mysqld is used by the rpc implementation plugin. + mysqld *mysqlctl.Mysqld + cnf *mysqlctl.Mycnf + + mysqlPort = 3306 + tabletUID = uint32(41983) + mysqlSocket string + + // mysqlctl init flags + waitTime = 5 * time.Minute + initDBSQLFile string + + Main = &cobra.Command{ + Use: "mysqlctld", + Short: "mysqlctld is a daemon that starts or initializes mysqld.", + Long: "`mysqlctld` is a gRPC server that can be used instead of the `mysqlctl` client tool.\n" + + "If the target directories are empty when it is invoked, it automatically performs initialization operations to bootstrap the `mysqld` instance before starting it.\n" + + "The `mysqlctld` process can subsequently receive gRPC commands from a `vttablet` to perform housekeeping operations like shutting down and restarting the `mysqld` instance as needed.\n\n" + + + "{{< warning >}}\n" + + "`mysqld_safe` is not used so the `mysqld` process will not be automatically restarted in case of a failure.\n" + + "{{}}\n\n" + + "To enable communication with a `vttablet`, the server must be configured to receive gRPC messages on a unix domain socket.", + Example: `mysqlctld \ + --log_dir=${VTDATAROOT}/logs \ + --tablet_uid=100 \ + --mysql_port=17100 \ + --socket_file=/path/to/socket_file`, + Args: cobra.NoArgs, + PreRunE: servenv.CobraPreRunE, + RunE: run, + } +) + +func init() { + servenv.RegisterDefaultFlags() + servenv.RegisterDefaultSocketFileFlags() + servenv.RegisterFlags() + servenv.RegisterGRPCServerFlags() + servenv.RegisterGRPCServerAuthFlags() + servenv.RegisterServiceMapFlag() + // mysqlctld only starts and stops mysql, only needs dba. + dbconfigs.RegisterFlags(dbconfigs.Dba) + + servenv.MoveFlagsToCobraCommand(Main) + + Main.Flags().IntVar(&mysqlPort, "mysql_port", mysqlPort, "MySQL port") + Main.Flags().Uint32Var(&tabletUID, "tablet_uid", tabletUID, "Tablet UID") + Main.Flags().StringVar(&mysqlSocket, "mysql_socket", mysqlSocket, "Path to the mysqld socket file") + Main.Flags().DurationVar(&waitTime, "wait_time", waitTime, "How long to wait for mysqld startup or shutdown") + Main.Flags().StringVar(&initDBSQLFile, "init_db_sql_file", initDBSQLFile, "Path to .sql file to run after mysqld initialization") + + acl.RegisterFlags(Main.Flags()) +} + +func run(cmd *cobra.Command, args []string) error { + defer logutil.Flush() + + // We'll register this OnTerm handler before mysqld starts, so we get notified + // if mysqld dies on its own without us (or our RPC client) telling it to. + mysqldTerminated := make(chan struct{}) + onTermFunc := func() { + close(mysqldTerminated) + } + + // Start or Init mysqld as needed. + ctx, cancel := context.WithTimeout(context.Background(), waitTime) + mycnfFile := mysqlctl.MycnfFile(tabletUID) + if _, statErr := os.Stat(mycnfFile); os.IsNotExist(statErr) { + // Generate my.cnf from scratch and use it to find mysqld. + log.Infof("mycnf file (%s) doesn't exist, initializing", mycnfFile) + + var err error + mysqld, cnf, err = mysqlctl.CreateMysqldAndMycnf(tabletUID, mysqlSocket, mysqlPort) + if err != nil { + cancel() + return fmt.Errorf("failed to initialize mysql config: %w", err) + } + mysqld.OnTerm(onTermFunc) + + if err := mysqld.Init(ctx, cnf, initDBSQLFile); err != nil { + cancel() + return fmt.Errorf("failed to initialize mysql data dir and start mysqld: %w", err) + } + } else { + // There ought to be an existing my.cnf, so use it to find mysqld. + log.Infof("mycnf file (%s) already exists, starting without init", mycnfFile) + + var err error + mysqld, cnf, err = mysqlctl.OpenMysqldAndMycnf(tabletUID) + if err != nil { + cancel() + return fmt.Errorf("failed to find mysql config: %w", err) + } + mysqld.OnTerm(onTermFunc) + + err = mysqld.RefreshConfig(ctx, cnf) + if err != nil { + cancel() + return fmt.Errorf("failed to refresh config: %w", err) + } + + // check if we were interrupted during a previous restore + if !mysqlctl.RestoreWasInterrupted(cnf) { + if err := mysqld.Start(ctx, cnf); err != nil { + cancel() + return fmt.Errorf("failed to start mysqld: %w", err) + } + } else { + log.Infof("found interrupted restore, not starting mysqld") + } + } + cancel() + + servenv.Init() + defer servenv.Close() + + // Take mysqld down with us on SIGTERM before entering lame duck. + servenv.OnTermSync(func() { + log.Infof("mysqlctl received SIGTERM, shutting down mysqld first") + ctx := context.Background() + if err := mysqld.Shutdown(ctx, cnf, true); err != nil { + log.Errorf("failed to shutdown mysqld: %v", err) + } + }) + + // Start RPC server and wait for SIGTERM. + mysqlctldTerminated := make(chan struct{}) + go func() { + servenv.RunDefault() + close(mysqlctldTerminated) + }() + + select { + case <-mysqldTerminated: + log.Infof("mysqld shut down on its own, exiting mysqlctld") + case <-mysqlctldTerminated: + log.Infof("mysqlctld shut down gracefully") + } + + return nil +} diff --git a/go/cmd/mysqlctld/plugin_grpcmysqlctlserver.go b/go/cmd/mysqlctld/cli/plugin_grpcmysqlctlserver.go similarity index 98% rename from go/cmd/mysqlctld/plugin_grpcmysqlctlserver.go rename to go/cmd/mysqlctld/cli/plugin_grpcmysqlctlserver.go index ee81ab77515..1186d5ed788 100644 --- a/go/cmd/mysqlctld/plugin_grpcmysqlctlserver.go +++ b/go/cmd/mysqlctld/cli/plugin_grpcmysqlctlserver.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package cli // Import and register the gRPC mysqlctl server diff --git a/go/cmd/mysqlctld/plugin_prometheusbackend.go b/go/cmd/mysqlctld/cli/plugin_prometheusbackend.go similarity index 98% rename from go/cmd/mysqlctld/plugin_prometheusbackend.go rename to go/cmd/mysqlctld/cli/plugin_prometheusbackend.go index 4ae114ceedd..e01ecf0bead 100644 --- a/go/cmd/mysqlctld/plugin_prometheusbackend.go +++ b/go/cmd/mysqlctld/cli/plugin_prometheusbackend.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 Prometheus to allow for instrumentation // with the Prometheus client library diff --git a/go/cmd/mysqlctld/docgen/main.go b/go/cmd/mysqlctld/docgen/main.go new file mode 100644 index 00000000000..4c920fa46e0 --- /dev/null +++ b/go/cmd/mysqlctld/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/mysqlctld/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/mysqlctld/mysqlctld.go b/go/cmd/mysqlctld/mysqlctld.go index 39b9ac11490..5843c5a15e1 100644 --- a/go/cmd/mysqlctld/mysqlctld.go +++ b/go/cmd/mysqlctld/mysqlctld.go @@ -20,140 +20,12 @@ limitations under the License. package main import ( - "context" - "os" - "time" - - "github.com/spf13/pflag" - - "vitess.io/vitess/go/acl" - "vitess.io/vitess/go/exit" - "vitess.io/vitess/go/vt/dbconfigs" + "vitess.io/vitess/go/cmd/mysqlctld/cli" "vitess.io/vitess/go/vt/log" - "vitess.io/vitess/go/vt/logutil" - "vitess.io/vitess/go/vt/mysqlctl" - "vitess.io/vitess/go/vt/servenv" -) - -var ( - // mysqld is used by the rpc implementation plugin. - mysqld *mysqlctl.Mysqld - cnf *mysqlctl.Mycnf - - mysqlPort = 3306 - tabletUID = uint32(41983) - mysqlSocket string - - // mysqlctl init flags - waitTime = 5 * time.Minute - initDBSQLFile string ) -func init() { - servenv.RegisterDefaultFlags() - servenv.RegisterDefaultSocketFileFlags() - servenv.RegisterFlags() - servenv.RegisterGRPCServerFlags() - servenv.RegisterGRPCServerAuthFlags() - servenv.RegisterServiceMapFlag() - // mysqlctld only starts and stops mysql, only needs dba. - dbconfigs.RegisterFlags(dbconfigs.Dba) - servenv.OnParse(func(fs *pflag.FlagSet) { - fs.IntVar(&mysqlPort, "mysql_port", mysqlPort, "MySQL port") - fs.Uint32Var(&tabletUID, "tablet_uid", tabletUID, "Tablet UID") - fs.StringVar(&mysqlSocket, "mysql_socket", mysqlSocket, "Path to the mysqld socket file") - fs.DurationVar(&waitTime, "wait_time", waitTime, "How long to wait for mysqld startup or shutdown") - fs.StringVar(&initDBSQLFile, "init_db_sql_file", initDBSQLFile, "Path to .sql file to run after mysqld initialization") - - acl.RegisterFlags(fs) - }) -} - func main() { - defer exit.Recover() - defer logutil.Flush() - - servenv.ParseFlags("mysqlctld") - - // We'll register this OnTerm handler before mysqld starts, so we get notified - // if mysqld dies on its own without us (or our RPC client) telling it to. - mysqldTerminated := make(chan struct{}) - onTermFunc := func() { - close(mysqldTerminated) - } - - // Start or Init mysqld as needed. - ctx, cancel := context.WithTimeout(context.Background(), waitTime) - mycnfFile := mysqlctl.MycnfFile(tabletUID) - if _, statErr := os.Stat(mycnfFile); os.IsNotExist(statErr) { - // Generate my.cnf from scratch and use it to find mysqld. - log.Infof("mycnf file (%s) doesn't exist, initializing", mycnfFile) - - var err error - mysqld, cnf, err = mysqlctl.CreateMysqldAndMycnf(tabletUID, mysqlSocket, mysqlPort) - if err != nil { - log.Errorf("failed to initialize mysql config: %v", err) - exit.Return(1) - } - mysqld.OnTerm(onTermFunc) - - if err := mysqld.Init(ctx, cnf, initDBSQLFile); err != nil { - log.Errorf("failed to initialize mysql data dir and start mysqld: %v", err) - exit.Return(1) - } - } else { - // There ought to be an existing my.cnf, so use it to find mysqld. - log.Infof("mycnf file (%s) already exists, starting without init", mycnfFile) - - var err error - mysqld, cnf, err = mysqlctl.OpenMysqldAndMycnf(tabletUID) - if err != nil { - log.Errorf("failed to find mysql config: %v", err) - exit.Return(1) - } - mysqld.OnTerm(onTermFunc) - - err = mysqld.RefreshConfig(ctx, cnf) - if err != nil { - log.Errorf("failed to refresh config: %v", err) - exit.Return(1) - } - - // check if we were interrupted during a previous restore - if !mysqlctl.RestoreWasInterrupted(cnf) { - if err := mysqld.Start(ctx, cnf); err != nil { - log.Errorf("failed to start mysqld: %v", err) - exit.Return(1) - } - } else { - log.Infof("found interrupted restore, not starting mysqld") - } - } - cancel() - - servenv.Init() - defer servenv.Close() - - // Take mysqld down with us on SIGTERM before entering lame duck. - servenv.OnTermSync(func() { - log.Infof("mysqlctl received SIGTERM, shutting down mysqld first") - ctx := context.Background() - if err := mysqld.Shutdown(ctx, cnf, true); err != nil { - log.Errorf("failed to shutdown mysqld: %v", err) - } - }) - - // Start RPC server and wait for SIGTERM. - mysqlctldTerminated := make(chan struct{}) - go func() { - servenv.RunDefault() - close(mysqlctldTerminated) - }() - - select { - case <-mysqldTerminated: - log.Infof("mysqld shut down on its own, exiting mysqlctld") - case <-mysqlctldTerminated: - log.Infof("mysqlctld shut down gracefully") + if err := cli.Main.Execute(); err != nil { + log.Exit(err) } } diff --git a/go/flags/endtoend/mysqlctl.txt b/go/flags/endtoend/mysqlctl.txt index 4af44804749..a8f832d3345 100644 --- a/go/flags/endtoend/mysqlctl.txt +++ b/go/flags/endtoend/mysqlctl.txt @@ -1,16 +1,24 @@ -Usage: mysqlctl [global-flags] -- [command-flags] +`mysqlctl` is a command-line client used for managing `mysqld` instances. -The commands are listed below. Use 'mysqlctl -- {-h, --help}' for command help. +It is responsible for bootstrapping tasks such as generating a configuration file for `mysqld` and initializing the instance and its data directory. +The `mysqld_safe` watchdog is utilized when present. +This helps ensure that `mysqld` is automatically restarted after failures. - init [--wait_time=5m] [--init_db_sql_file=] - init_config - reinit_config - teardown [--wait_time=5m] [--force] - start [--wait_time=5m] - shutdown [--wait_time=5m] - position +Usage: + mysqlctl [command] -Global flags: +Available Commands: + completion Generate the autocompletion script for the specified shell + help Help about any command + init Initializes the directory structure and starts mysqld. + init_config Initializes the directory structure, creates my.cnf file, but does not start mysqld. + position Compute operations on replication positions + reinit_config Reinitializes my.cnf file with new server_id. + shutdown Shuts down mysqld, without removing any files. + start Starts mysqld on an already 'init'-ed directory. + teardown Shuts mysqld down and removes the directory. + +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) @@ -52,7 +60,7 @@ Global flags: --db_tls_min_version string Configures the minimal TLS version negotiated when SSL is enabled. Defaults to TLSv1.2. Options: TLSv1.0, TLSv1.1, TLSv1.2, TLSv1.3. --dba_idle_timeout duration Idle timeout for dba connections (default 1m0s) --dba_pool_size int Size of the connection pool for dba connections (default 20) - -h, --help display usage and exit + -h, --help help for mysqlctl --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) @@ -62,9 +70,9 @@ Global flags: --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_port int MySQL port (default 3306) + --mysql_port int MySQL port. (default 3306) --mysql_server_version string MySQL server version to advertise. (default "8.0.30-Vitess") - --mysql_socket string Path to the mysqld socket file + --mysql_socket string Path to the mysqld socket file. --mysqlctl_client_protocol string the protocol to use to talk to the mysqlctl server (default "grpc") --mysqlctl_mycnf_template string template file to use for generating the my.cnf file during server init --mysqlctl_socket string socket file to use for remote mysqlctl actions (empty for local actions) @@ -81,7 +89,9 @@ Global flags: --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 --tablet_dir string The directory within the vtdataroot to store vttablet/mysql files. Defaults to being generated by the tablet uid. - --tablet_uid uint32 Tablet UID (default 41983) + --tablet_uid uint32 Tablet UID. (default 41983) --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 + +Use "mysqlctl [command] --help" for more information about a command. diff --git a/go/flags/endtoend/mysqlctld.txt b/go/flags/endtoend/mysqlctld.txt index 6fbbd059492..e6374093b57 100644 --- a/go/flags/endtoend/mysqlctld.txt +++ b/go/flags/endtoend/mysqlctld.txt @@ -1,4 +1,24 @@ -Usage of mysqlctld: +`mysqlctld` is a gRPC server that can be used instead of the `mysqlctl` client tool. +If the target directories are empty when it is invoked, it automatically performs initialization operations to bootstrap the `mysqld` instance before starting it. +The `mysqlctld` process can subsequently receive gRPC commands from a `vttablet` to perform housekeeping operations like shutting down and restarting the `mysqld` instance as needed. + +{{ "{{< warning >}}" }} +`mysqld_safe` is not used so the `mysqld` process will not be automatically restarted in case of a failure. +{{ "{{}}" }} + +To enable communication with a `vttablet`, the server must be configured to receive gRPC messages on a unix domain socket. + +Usage: + mysqlctld [flags] + +Examples: +mysqlctld \ + --log_dir=${VTDATAROOT}/logs \ + --tablet_uid=100 \ + --mysql_port=17100 \ + --socket_file=/path/to/socket_file + +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) @@ -62,7 +82,7 @@ Usage of mysqlctld: --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 mysqlctld --init_db_sql_file string Path to .sql file to run after mysqld initialization --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) diff --git a/go/test/endtoend/cluster/mysqlctl_process.go b/go/test/endtoend/cluster/mysqlctl_process.go index b5e7cfb5a32..5a1a49064b3 100644 --- a/go/test/endtoend/cluster/mysqlctl_process.go +++ b/go/test/endtoend/cluster/mysqlctl_process.go @@ -47,6 +47,7 @@ type MysqlctlProcess struct { ExtraArgs []string InitMysql bool SecureTransport bool + MajorVersion int } // InitDb executes mysqlctl command to add cell info @@ -54,8 +55,13 @@ func (mysqlctl *MysqlctlProcess) InitDb() (err error) { args := []string{"--log_dir", mysqlctl.LogDirectory, "--tablet_uid", fmt.Sprintf("%d", mysqlctl.TabletUID), "--mysql_port", fmt.Sprintf("%d", mysqlctl.MySQLPort), - "init", "--", - "--init_db_sql_file", mysqlctl.InitDBFile} + "init", + } + if mysqlctl.MajorVersion < 18 { + args = append(args, "--") + } + + args = append(args, "--init_db_sql_file", mysqlctl.InitDBFile) if *isCoverage { args = append([]string{"--test.coverprofile=" + getCoveragePath("mysql-initdb.out"), "--test.v"}, args...) } @@ -143,11 +149,18 @@ ssl_key={{.ServerKey}} } if init { - tmpProcess.Args = append(tmpProcess.Args, "init", "--", - "--init_db_sql_file", mysqlctl.InitDBFile) + tmpProcess.Args = append(tmpProcess.Args, "init") + if mysqlctl.MajorVersion < 18 { + tmpProcess.Args = append(tmpProcess.Args, "--") + } + + tmpProcess.Args = append(tmpProcess.Args, "--init_db_sql_file", mysqlctl.InitDBFile) + } else { + tmpProcess.Args = append(tmpProcess.Args, "start") } + } else { + tmpProcess.Args = append(tmpProcess.Args, "start") } - tmpProcess.Args = append(tmpProcess.Args, "start") tmpProcess.Env = append(tmpProcess.Env, os.Environ()...) tmpProcess.Env = append(tmpProcess.Env, DefaultVttestEnv) log.Infof("Starting mysqlctl with command: %v", tmpProcess.Args) @@ -240,11 +253,17 @@ func MysqlCtlProcessInstanceOptionalInit(tabletUID int, mySQLPort int, tmpDirect if err != nil { return nil, err } + + version, err := GetMajorVersion("mysqlctl") + if err != nil { + log.Warningf("failed to get major mysqlctl version; backwards-compatibility for CLI changes may not work: %s", err) + } mysqlctl := &MysqlctlProcess{ Name: "mysqlctl", Binary: "mysqlctl", LogDirectory: tmpDirectory, InitDBFile: initFile, + MajorVersion: version, } mysqlctl.MySQLPort = mySQLPort mysqlctl.TabletUID = tabletUID diff --git a/go/vt/servenv/servenv.go b/go/vt/servenv/servenv.go index e115989af12..40d74c06062 100644 --- a/go/vt/servenv/servenv.go +++ b/go/vt/servenv/servenv.go @@ -369,17 +369,33 @@ func ParseFlagsForTests(cmd string) { // the given cobra command, then copies over the glog flags that otherwise // require manual transferring. func MoveFlagsToCobraCommand(cmd *cobra.Command) { - cmd.Flags().AddFlagSet(GetFlagSetFor(cmd.Use)) + moveFlags(cmd.Use, cmd.Flags()) +} + +// MovePersistentFlagsToCobraCommand functions exactly like MoveFlagsToCobraCommand, +// but moves the servenv-registered flags to the persistent flagset of +// the given cobra command, then copies over the glog flags that otherwise +// require manual transferring. +// +// Useful for transferring flags to a parent command whose subcommands should +// inherit the servenv-registered flags. +func MovePersistentFlagsToCobraCommand(cmd *cobra.Command) { + moveFlags(cmd.Use, cmd.PersistentFlags()) +} + +func moveFlags(name string, fs *pflag.FlagSet) { + fs.AddFlagSet(GetFlagSetFor(name)) + // glog flags, no better way to do this - _flag.PreventGlogVFlagFromClobberingVersionFlagShorthand(cmd.Flags()) - cmd.Flags().AddGoFlag(flag.Lookup("logtostderr")) - cmd.Flags().AddGoFlag(flag.Lookup("log_backtrace_at")) - cmd.Flags().AddGoFlag(flag.Lookup("alsologtostderr")) - cmd.Flags().AddGoFlag(flag.Lookup("stderrthreshold")) - cmd.Flags().AddGoFlag(flag.Lookup("log_dir")) - cmd.Flags().AddGoFlag(flag.Lookup("vmodule")) - - pflag.CommandLine = cmd.Flags() + _flag.PreventGlogVFlagFromClobberingVersionFlagShorthand(fs) + fs.AddGoFlag(flag.Lookup("logtostderr")) + fs.AddGoFlag(flag.Lookup("log_backtrace_at")) + fs.AddGoFlag(flag.Lookup("alsologtostderr")) + fs.AddGoFlag(flag.Lookup("stderrthreshold")) + fs.AddGoFlag(flag.Lookup("log_dir")) + fs.AddGoFlag(flag.Lookup("vmodule")) + + pflag.CommandLine = fs } // CobraPreRunE returns the common function that commands will need to load diff --git a/go/vt/vttest/mysqlctl.go b/go/vt/vttest/mysqlctl.go index 8646e344ea5..d8df6c99d48 100644 --- a/go/vt/vttest/mysqlctl.go +++ b/go/vt/vttest/mysqlctl.go @@ -66,7 +66,7 @@ func (ctl *Mysqlctl) Setup() error { "--alsologtostderr", "--tablet_uid", fmt.Sprintf("%d", ctl.UID), "--mysql_port", fmt.Sprintf("%d", ctl.Port), - "init", "--", + "init", "--init_db_sql_file", ctl.InitFile, )