From 753bf42061542f82ba2e6cb00bb6297a6c19c3b8 Mon Sep 17 00:00:00 2001 From: Sugu Sougoumarane Date: Wed, 18 Jul 2018 21:31:56 -0700 Subject: [PATCH 1/3] nomycnf: operate without mycnf more naturally Addresses #4087 With this change, a cnf is not needed for vttablet to operate. If any connection parameter is specified: socket or host & port, then vttablet assumes that the mysql is external, and does not attempt to load the cnf file. Consequently, this disables vttablet from performing backups and restores. In such cases, vttablet crashes if restore_from_backup is set to true. If no connection parameters are specifed, vttablet behaves like before: look for a mycnf file, and fail if one cannot be found. Of course, the cnf file fields can also be explicitly passed in explicitly. In this case, the cnf's socket file is used to connect to MySQL. Implementation: mycnf has been removed from mysqld. It's now passed as input parameter for operations that need it. The purpose of this change is to clearly document which operations of mysqld require a mycnf. The ActionAgent stores Cnf as a separate field, and checks for null value before invoking mysqld functions that require it. At this point, only Backup and Restore require it. There's a quirky corner case where a StopSlave command saves a marker file in the tree. That part has been changed to save that file only if a Cnf is present. The more correct behavior would be to store that in the topo, which is beyond the scope of this change. tabletDir was derived from mycnf and stored as a field in mysqld. The wrapper function TabletDir has now been moved into Mycnf and call sites have been changed accordingly. Signed-off-by: Sugu Sougoumarane --- go/cmd/mysqlctl/mysqlctl.go | 24 +-- go/cmd/mysqlctld/mysqlctld.go | 13 +- go/cmd/mysqlctld/plugin_grpcmysqlctlserver.go | 2 +- go/cmd/vtcombo/main.go | 15 +- go/cmd/vttablet/vttablet.go | 26 ++- go/vt/dbconfigs/dbconfigs.go | 24 +-- go/vt/mysqlctl/backup.go | 41 ++--- go/vt/mysqlctl/cmd.go | 22 +-- .../fakemysqldaemon/fakemysqldaemon.go | 23 +-- go/vt/mysqlctl/grpcmysqlctlserver/server.go | 13 +- go/vt/mysqlctl/mycnf.go | 6 + go/vt/mysqlctl/mycnf_flag.go | 3 - go/vt/mysqlctl/mysql_daemon.go | 15 +- go/vt/mysqlctl/mysqld.go | 153 ++++++++---------- go/vt/vttablet/tabletmanager/action_agent.go | 36 +++-- go/vt/vttablet/tabletmanager/restore.go | 5 +- go/vt/vttablet/tabletmanager/rpc_backup.go | 6 +- go/vt/wrangler/testlib/backup_test.go | 26 +-- 18 files changed, 233 insertions(+), 220 deletions(-) diff --git a/go/cmd/mysqlctl/mysqlctl.go b/go/cmd/mysqlctl/mysqlctl.go index 9e87249a9c7..7d3c81e8f33 100644 --- a/go/cmd/mysqlctl/mysqlctl.go +++ b/go/cmd/mysqlctl/mysqlctl.go @@ -48,12 +48,12 @@ func initConfigCmd(subFlags *flag.FlagSet, args []string) error { subFlags.Parse(args) // Generate my.cnf from scratch and use it to find mysqld. - mysqld, err := mysqlctl.CreateMysqld(uint32(*tabletUID), *mysqlSocket, int32(*mysqlPort)) + mysqld, cnf, err := mysqlctl.CreateMysqld(uint32(*tabletUID), *mysqlSocket, int32(*mysqlPort)) if err != nil { return fmt.Errorf("failed to initialize mysql config: %v", err) } defer mysqld.Close() - if err := mysqld.InitConfig(); err != nil { + if err := mysqld.InitConfig(cnf); err != nil { return fmt.Errorf("failed to init mysql config: %v", err) } return nil @@ -65,7 +65,7 @@ func initCmd(subFlags *flag.FlagSet, args []string) error { subFlags.Parse(args) // Generate my.cnf from scratch and use it to find mysqld. - mysqld, err := mysqlctl.CreateMysqld(uint32(*tabletUID), *mysqlSocket, int32(*mysqlPort)) + mysqld, cnf, err := mysqlctl.CreateMysqld(uint32(*tabletUID), *mysqlSocket, int32(*mysqlPort)) if err != nil { return fmt.Errorf("failed to initialize mysql config: %v", err) } @@ -73,7 +73,7 @@ func initCmd(subFlags *flag.FlagSet, args []string) error { ctx, cancel := context.WithTimeout(context.Background(), *waitTime) defer cancel() - if err := mysqld.Init(ctx, *initDBSQLFile); err != nil { + if err := mysqld.Init(ctx, cnf, *initDBSQLFile); err != nil { return fmt.Errorf("failed init mysql: %v", err) } return nil @@ -81,13 +81,13 @@ func initCmd(subFlags *flag.FlagSet, args []string) error { func reinitConfigCmd(subFlags *flag.FlagSet, args []string) error { // There ought to be an existing my.cnf, so use it to find mysqld. - mysqld, err := mysqlctl.OpenMysqld(uint32(*tabletUID)) + mysqld, cnf, err := mysqlctl.OpenMysqld(uint32(*tabletUID)) if err != nil { return fmt.Errorf("failed to find mysql config: %v", err) } defer mysqld.Close() - if err := mysqld.ReinitConfig(context.TODO()); err != nil { + if err := mysqld.ReinitConfig(context.TODO(), cnf); err != nil { return fmt.Errorf("failed to reinit mysql config: %v", err) } return nil @@ -98,7 +98,7 @@ func shutdownCmd(subFlags *flag.FlagSet, args []string) error { subFlags.Parse(args) // There ought to be an existing my.cnf, so use it to find mysqld. - mysqld, err := mysqlctl.OpenMysqld(uint32(*tabletUID)) + mysqld, cnf, err := mysqlctl.OpenMysqld(uint32(*tabletUID)) if err != nil { return fmt.Errorf("failed to find mysql config: %v", err) } @@ -106,7 +106,7 @@ func shutdownCmd(subFlags *flag.FlagSet, args []string) error { ctx, cancel := context.WithTimeout(context.Background(), *waitTime) defer cancel() - if err := mysqld.Shutdown(ctx, true); err != nil { + if err := mysqld.Shutdown(ctx, cnf, true); err != nil { return fmt.Errorf("failed shutdown mysql: %v", err) } return nil @@ -119,7 +119,7 @@ func startCmd(subFlags *flag.FlagSet, args []string) error { subFlags.Parse(args) // There ought to be an existing my.cnf, so use it to find mysqld. - mysqld, err := mysqlctl.OpenMysqld(uint32(*tabletUID)) + mysqld, cnf, err := mysqlctl.OpenMysqld(uint32(*tabletUID)) if err != nil { return fmt.Errorf("failed to find mysql config: %v", err) } @@ -127,7 +127,7 @@ func startCmd(subFlags *flag.FlagSet, args []string) error { ctx, cancel := context.WithTimeout(context.Background(), *waitTime) defer cancel() - if err := mysqld.Start(ctx, mysqldArgs...); err != nil { + if err := mysqld.Start(ctx, cnf, mysqldArgs...); err != nil { return fmt.Errorf("failed start mysql: %v", err) } return nil @@ -139,7 +139,7 @@ func teardownCmd(subFlags *flag.FlagSet, args []string) error { subFlags.Parse(args) // There ought to be an existing my.cnf, so use it to find mysqld. - mysqld, err := mysqlctl.OpenMysqld(uint32(*tabletUID)) + mysqld, cnf, err := mysqlctl.OpenMysqld(uint32(*tabletUID)) if err != nil { return fmt.Errorf("failed to find mysql config: %v", err) } @@ -147,7 +147,7 @@ func teardownCmd(subFlags *flag.FlagSet, args []string) error { ctx, cancel := context.WithTimeout(context.Background(), *waitTime) defer cancel() - if err := mysqld.Teardown(ctx, *force); err != nil { + if err := mysqld.Teardown(ctx, cnf, *force); err != nil { return fmt.Errorf("failed teardown mysql (forced? %v): %v", *force, err) } return nil diff --git a/go/cmd/mysqlctld/mysqlctld.go b/go/cmd/mysqlctld/mysqlctld.go index b304f9dcc71..f696562bd94 100644 --- a/go/cmd/mysqlctld/mysqlctld.go +++ b/go/cmd/mysqlctld/mysqlctld.go @@ -36,6 +36,7 @@ import ( var ( // mysqld is used by the rpc implementation plugin. mysqld *mysqlctl.Mysqld + cnf *mysqlctl.Mycnf mysqlPort = flag.Int("mysql_port", 3306, "mysql port") tabletUID = flag.Uint("tablet_uid", 41983, "tablet uid") @@ -74,14 +75,14 @@ func main() { log.Infof("mycnf file (%s) doesn't exist, initializing", mycnfFile) var err error - mysqld, err = mysqlctl.CreateMysqld(uint32(*tabletUID), *mysqlSocket, int32(*mysqlPort)) + mysqld, cnf, err = mysqlctl.CreateMysqld(uint32(*tabletUID), *mysqlSocket, int32(*mysqlPort)) if err != nil { log.Errorf("failed to initialize mysql config: %v", err) exit.Return(1) } mysqld.OnTerm(onTermFunc) - if err := mysqld.Init(ctx, *initDBSQLFile); err != nil { + 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) } @@ -90,20 +91,20 @@ func main() { log.Infof("mycnf file (%s) already exists, starting without init", mycnfFile) var err error - mysqld, err = mysqlctl.OpenMysqld(uint32(*tabletUID)) + mysqld, cnf, err = mysqlctl.OpenMysqld(uint32(*tabletUID)) if err != nil { log.Errorf("failed to find mysql config: %v", err) exit.Return(1) } mysqld.OnTerm(onTermFunc) - err = mysqld.RefreshConfig(ctx) + err = mysqld.RefreshConfig(ctx, cnf) if err != nil { log.Errorf("failed to refresh config: %v", err) exit.Return(1) } - if err := mysqld.Start(ctx); err != nil { + if err := mysqld.Start(ctx, cnf); err != nil { log.Errorf("failed to start mysqld: %v", err) exit.Return(1) } @@ -117,7 +118,7 @@ func main() { servenv.OnTermSync(func() { log.Infof("mysqlctl received SIGTERM, shutting down mysqld first") ctx := context.Background() - mysqld.Shutdown(ctx, true) + mysqld.Shutdown(ctx, cnf, true) }) // Start RPC server and wait for SIGTERM. diff --git a/go/cmd/mysqlctld/plugin_grpcmysqlctlserver.go b/go/cmd/mysqlctld/plugin_grpcmysqlctlserver.go index b14de1ef80f..088d1118c10 100644 --- a/go/cmd/mysqlctld/plugin_grpcmysqlctlserver.go +++ b/go/cmd/mysqlctld/plugin_grpcmysqlctlserver.go @@ -27,7 +27,7 @@ func init() { servenv.InitServiceMap("grpc", "mysqlctl") servenv.OnRun(func() { if servenv.GRPCCheckServiceMap("mysqlctl") { - grpcmysqlctlserver.StartServer(servenv.GRPCServer, mysqld) + grpcmysqlctlserver.StartServer(servenv.GRPCServer, cnf, mysqld) } }) } diff --git a/go/cmd/vtcombo/main.go b/go/cmd/vtcombo/main.go index 9171f123638..3265b36c381 100644 --- a/go/cmd/vtcombo/main.go +++ b/go/cmd/vtcombo/main.go @@ -94,21 +94,16 @@ func main() { servenv.Init() tabletenv.Init() - // database configs - mycnf, err := mysqlctl.NewMycnfFromFlags(0) - if err != nil { - log.Errorf("mycnf read failed: %v", err) - exit.Return(1) - } - dbcfgs, err := dbconfigs.Init(mycnf.SocketFile) + dbcfgs, err := dbconfigs.Init("") if err != nil { log.Warning(err) } - mysqld := mysqlctl.NewMysqld(mycnf, dbcfgs) + mysqld := mysqlctl.NewMysqld(dbcfgs) servenv.OnClose(mysqld.Close) - // tablets configuration and init - if err := vtcombo.InitTabletMap(ts, tpb, mysqld, dbcfgs, *schemaDir, mycnf); err != nil { + // tablets configuration and init. + // Send mycnf as nil because vtcombo won't do backups and restores. + if err := vtcombo.InitTabletMap(ts, tpb, mysqld, dbcfgs, *schemaDir, nil); err != nil { log.Errorf("initTabletMapProto failed: %v", err) exit.Return(1) } diff --git a/go/cmd/vttablet/vttablet.go b/go/cmd/vttablet/vttablet.go index e96f7984604..8353209d9a6 100644 --- a/go/cmd/vttablet/vttablet.go +++ b/go/cmd/vttablet/vttablet.go @@ -67,13 +67,29 @@ func main() { if err != nil { log.Exitf("failed to parse -tablet-path: %v", err) } + if tabletAlias.Uid < 0 { + log.Exitf("invalid tablet id: %d", tabletAlias.Uid) + } - mycnf, err := mysqlctl.NewMycnfFromFlags(tabletAlias.Uid) - if err != nil { - log.Exitf("mycnf read failed: %v", err) + var mycnf *mysqlctl.Mycnf + var socketFile string + // If no connection parameters were specified, load the mycnf file + // and use the socket from it. If connection parameters were specified, + // we assume that the mysql is not local, and we skip loading mycnf. + // This also means that backup and restore will not be allowed. + if !dbconfigs.HasConnectionParams() { + var err error + if mycnf, err = mysqlctl.NewMycnfFromFlags(tabletAlias.Uid); err != nil { + log.Exitf("mycnf read failed: %v", err) + } + socketFile = mycnf.SocketFile + } else { + log.Info("connection parameters were specified. Not loading my.cnf.") } - dbcfgs, err := dbconfigs.Init(mycnf.SocketFile) + // If connection parameters were not specified, we'll use + // mycnf's socket file (if present). + dbcfgs, err := dbconfigs.Init(socketFile) if err != nil { log.Warning(err) } @@ -115,7 +131,7 @@ func main() { // Create mysqld and register the health reporter (needs to be done // before initializing the agent, so the initial health check // done by the agent has the right reporter) - mysqld := mysqlctl.NewMysqld(mycnf, dbcfgs) + mysqld := mysqlctl.NewMysqld(dbcfgs) servenv.OnClose(mysqld.Close) // Depends on both query and updateStream. diff --git a/go/vt/dbconfigs/dbconfigs.go b/go/vt/dbconfigs/dbconfigs.go index 33e4d6c49a9..0d5b6eae35a 100644 --- a/go/vt/dbconfigs/dbconfigs.go +++ b/go/vt/dbconfigs/dbconfigs.go @@ -214,6 +214,14 @@ func (dbcfgs *DBConfigs) Copy() *DBConfigs { return result } +// HasConnectionParams returns true if connection parameters were +// specified in the command-line. This will allow the caller to +// search for alternate ways to connect, like looking in the my.cnf +// file. +func HasConnectionParams() bool { + return baseConfig.Host != "" || baseConfig.UnixSocket != "" +} + // Init will initialize all the necessary connection parameters. // Precedence is as follows: if baseConfig command line options are // set, they supersede all other settings. @@ -222,15 +230,6 @@ func (dbcfgs *DBConfigs) Copy() *DBConfigs { // If no per-user parameters are supplied, then the defaultSocketFile // is used to initialize the per-user conn params. func Init(defaultSocketFile string) (*DBConfigs, error) { - // This is to support legacy behavior: use supplied socket value - // if conn parameters are not specified. - // TODO(sougou): deprecate. - for _, uc := range dbConfigs.userConfigs { - if uc.param.UnixSocket == "" && uc.param.Host == "" { - uc.param.UnixSocket = defaultSocketFile - } - } - // The new base configs, if set, supersede legacy settings. if baseConfig.Host != "" || baseConfig.UnixSocket != "" { for _, uc := range dbConfigs.userConfigs { @@ -246,6 +245,13 @@ func Init(defaultSocketFile string) (*DBConfigs, error) { uc.param.SslKey = baseConfig.SslKey } } + } else { + // Use supplied socket value if conn parameters are not specified. + for _, uc := range dbConfigs.userConfigs { + if uc.param.UnixSocket == "" && uc.param.Host == "" { + uc.param.UnixSocket = defaultSocketFile + } + } } // See if the CredentialsServer is working. We do not use the diff --git a/go/vt/mysqlctl/backup.go b/go/vt/mysqlctl/backup.go index 1dfa5121707..87b100535cc 100644 --- a/go/vt/mysqlctl/backup.go +++ b/go/vt/mysqlctl/backup.go @@ -222,7 +222,7 @@ func findFilesToBackup(cnf *Mycnf) ([]FileEntry, error) { // - uses the BackupStorage service to store a new backup // - shuts down Mysqld during the backup // - remember if we were replicating, restore the exact same state -func Backup(ctx context.Context, mysqld MysqlDaemon, logger logutil.Logger, dir, name string, backupConcurrency int, hookExtraEnv map[string]string) error { +func Backup(ctx context.Context, cnf *Mycnf, mysqld MysqlDaemon, logger logutil.Logger, dir, name string, backupConcurrency int, hookExtraEnv map[string]string) error { // Start the backup with the BackupStorage. bs, err := backupstorage.GetBackupStorage() if err != nil { @@ -235,7 +235,7 @@ func Backup(ctx context.Context, mysqld MysqlDaemon, logger logutil.Logger, dir, } // Take the backup, and either AbortBackup or EndBackup. - usable, err := backup(ctx, mysqld, logger, bh, backupConcurrency, hookExtraEnv) + usable, err := backup(ctx, cnf, mysqld, logger, bh, backupConcurrency, hookExtraEnv) var finishErr error if usable { finishErr = bh.EndBackup(ctx) @@ -259,7 +259,7 @@ func Backup(ctx context.Context, mysqld MysqlDaemon, logger logutil.Logger, dir, // backup returns a boolean that indicates if the backup is usable, // and an overall error. -func backup(ctx context.Context, mysqld MysqlDaemon, logger logutil.Logger, bh backupstorage.BackupHandle, backupConcurrency int, hookExtraEnv map[string]string) (bool, error) { +func backup(ctx context.Context, cnf *Mycnf, mysqld MysqlDaemon, logger logutil.Logger, bh backupstorage.BackupHandle, backupConcurrency int, hookExtraEnv map[string]string) (bool, error) { // Save initial state so we can restore. slaveStartRequired := false sourceIsMaster := false @@ -312,22 +312,22 @@ func backup(ctx context.Context, mysqld MysqlDaemon, logger logutil.Logger, bh b logger.Infof("using replication position: %v", replicationPosition) // shutdown mysqld - err = mysqld.Shutdown(ctx, true) + err = mysqld.Shutdown(ctx, cnf, true) if err != nil { return false, fmt.Errorf("can't shutdown mysqld: %v", err) } // Backup everything, capture the error. - backupErr := backupFiles(ctx, mysqld, logger, bh, replicationPosition, backupConcurrency, hookExtraEnv) + backupErr := backupFiles(ctx, cnf, mysqld, logger, bh, replicationPosition, backupConcurrency, hookExtraEnv) usable := backupErr == nil // Try to restart mysqld - err = mysqld.RefreshConfig(ctx) + err = mysqld.RefreshConfig(ctx, cnf) if err != nil { return usable, fmt.Errorf("can't refresh mysqld config: %v", err) } - err = mysqld.Start(ctx) + err = mysqld.Start(ctx, cnf) if err != nil { return usable, fmt.Errorf("can't restart mysqld: %v", err) } @@ -365,9 +365,9 @@ func backup(ctx context.Context, mysqld MysqlDaemon, logger logutil.Logger, bh b } // backupFiles finds the list of files to backup, and creates the backup. -func backupFiles(ctx context.Context, mysqld MysqlDaemon, logger logutil.Logger, bh backupstorage.BackupHandle, replicationPosition mysql.Position, backupConcurrency int, hookExtraEnv map[string]string) (err error) { +func backupFiles(ctx context.Context, cnf *Mycnf, mysqld MysqlDaemon, logger logutil.Logger, bh backupstorage.BackupHandle, replicationPosition mysql.Position, backupConcurrency int, hookExtraEnv map[string]string) (err error) { // Get the files to backup. - fes, err := findFilesToBackup(mysqld.Cnf()) + fes, err := findFilesToBackup(cnf) if err != nil { return fmt.Errorf("can't find files to backup: %v", err) } @@ -392,7 +392,7 @@ func backupFiles(ctx context.Context, mysqld MysqlDaemon, logger logutil.Logger, // Backup the individual file. name := fmt.Sprintf("%v", i) - rec.RecordError(backupFile(ctx, mysqld, logger, bh, &fes[i], name, hookExtraEnv)) + rec.RecordError(backupFile(ctx, cnf, mysqld, logger, bh, &fes[i], name, hookExtraEnv)) }(i) } @@ -431,10 +431,10 @@ func backupFiles(ctx context.Context, mysqld MysqlDaemon, logger logutil.Logger, } // backupFile backs up an individual file. -func backupFile(ctx context.Context, mysqld MysqlDaemon, logger logutil.Logger, bh backupstorage.BackupHandle, fe *FileEntry, name string, hookExtraEnv map[string]string) (err error) { +func backupFile(ctx context.Context, cnf *Mycnf, mysqld MysqlDaemon, logger logutil.Logger, bh backupstorage.BackupHandle, fe *FileEntry, name string, hookExtraEnv map[string]string) (err error) { // Open the source file for reading. var source *os.File - source, err = fe.open(mysqld.Cnf(), true) + source, err = fe.open(cnf, true) if err != nil { return err } @@ -734,6 +734,7 @@ func removeExistingFiles(cnf *Mycnf) error { // and returns ErrNoBackup. Any other error is returned. func Restore( ctx context.Context, + cnf *Mycnf, mysqld MysqlDaemon, dir string, restoreConcurrency int, @@ -744,7 +745,7 @@ func Restore( dbName string) (mysql.Position, error) { // Wait for mysqld to be ready, in case it was launched in parallel with us. - if err := mysqld.Wait(ctx); err != nil { + if err := mysqld.Wait(ctx, cnf); err != nil { return mysql.Position{}, err } @@ -817,24 +818,24 @@ func Restore( // context. Thus we use the background context to get through to the finish. logger.Infof("Restore: shutdown mysqld") - err = mysqld.Shutdown(context.Background(), true) + err = mysqld.Shutdown(context.Background(), cnf, true) if err != nil { return mysql.Position{}, err } logger.Infof("Restore: deleting existing files") - if err := removeExistingFiles(mysqld.Cnf()); err != nil { + if err := removeExistingFiles(cnf); err != nil { return mysql.Position{}, err } logger.Infof("Restore: reinit config file") - err = mysqld.ReinitConfig(context.Background()) + err = mysqld.ReinitConfig(context.Background(), cnf) if err != nil { return mysql.Position{}, err } logger.Infof("Restore: copying all files") - if err := restoreFiles(context.Background(), mysqld.Cnf(), bh, bm.FileEntries, bm.TransformHook, !bm.SkipCompress, restoreConcurrency, hookExtraEnv); err != nil { + if err := restoreFiles(context.Background(), cnf, bh, bm.FileEntries, bm.TransformHook, !bm.SkipCompress, restoreConcurrency, hookExtraEnv); err != nil { return mysql.Position{}, err } @@ -847,7 +848,7 @@ func Restore( // of those who can connect. logger.Infof("Restore: starting mysqld for mysql_upgrade") // Note Start will use dba user for waiting, this is fine, it will be allowed. - err = mysqld.Start(context.Background(), "--skip-grant-tables", "--skip-networking") + err = mysqld.Start(context.Background(), cnf, "--skip-grant-tables", "--skip-networking") if err != nil { return mysql.Position{}, err } @@ -868,11 +869,11 @@ func Restore( // The MySQL manual recommends restarting mysqld after running mysql_upgrade, // so that any changes made to system tables take effect. logger.Infof("Restore: restarting mysqld after mysql_upgrade") - err = mysqld.Shutdown(context.Background(), true) + err = mysqld.Shutdown(context.Background(), cnf, true) if err != nil { return mysql.Position{}, err } - err = mysqld.Start(context.Background()) + err = mysqld.Start(context.Background(), cnf) if err != nil { return mysql.Position{}, err } diff --git a/go/vt/mysqlctl/cmd.go b/go/vt/mysqlctl/cmd.go index ab5684d0b81..f8cb758d42a 100644 --- a/go/vt/mysqlctl/cmd.go +++ b/go/vt/mysqlctl/cmd.go @@ -27,9 +27,9 @@ import ( ) // CreateMysqld returns a Mysqld object to use for working with a MySQL -// installation that hasn't been set up yet. This will generate a new my.cnf -// from scratch and use that to call NewMysqld(). -func CreateMysqld(tabletUID uint32, mysqlSocket string, mysqlPort int32) (*Mysqld, error) { +// installation that hasn't been set up yet. This will additionally generate +// a new my.cnf from scratch and return a corresponding *Mycnf. +func CreateMysqld(tabletUID uint32, mysqlSocket string, mysqlPort int32) (*Mysqld, *Mycnf, error) { mycnf := NewMycnf(tabletUID, mysqlPort) // Choose a random MySQL server-id, since this is a fresh data dir. // We don't want to use the tablet UID as the MySQL server-id, @@ -41,7 +41,7 @@ func CreateMysqld(tabletUID uint32, mysqlSocket string, mysqlPort int32) (*Mysql // lose data by skipping binlog events due to replicate-same-server-id=FALSE, // which is the default setting. if err := mycnf.RandomizeMysqlServerID(); err != nil { - return nil, fmt.Errorf("couldn't generate random MySQL server_id: %v", err) + return nil, nil, fmt.Errorf("couldn't generate random MySQL server_id: %v", err) } if mysqlSocket != "" { mycnf.SocketFile = mysqlSocket @@ -49,26 +49,26 @@ func CreateMysqld(tabletUID uint32, mysqlSocket string, mysqlPort int32) (*Mysql dbcfgs, err := dbconfigs.Init(mycnf.SocketFile) if err != nil { - return nil, fmt.Errorf("couldn't Init dbconfigs: %v", err) + return nil, nil, fmt.Errorf("couldn't Init dbconfigs: %v", err) } - return NewMysqld(mycnf, dbcfgs), nil + return NewMysqld(dbcfgs), mycnf, nil } // OpenMysqld returns a Mysqld object to use for working with a MySQL // installation that already exists. This will look for an existing my.cnf file -// and use that to call NewMysqld(). -func OpenMysqld(tabletUID uint32) (*Mysqld, error) { +// and return a corresponding *Mycnf. +func OpenMysqld(tabletUID uint32) (*Mysqld, *Mycnf, error) { // We pass a port of 0, this will be read and overwritten from the path on disk mycnf, err := ReadMycnf(NewMycnf(tabletUID, 0)) if err != nil { - return nil, fmt.Errorf("couldn't read my.cnf file: %v", err) + return nil, nil, fmt.Errorf("couldn't read my.cnf file: %v", err) } dbcfgs, err := dbconfigs.Init(mycnf.SocketFile) if err != nil { - return nil, fmt.Errorf("couldn't Init dbconfigs: %v", err) + return nil, nil, fmt.Errorf("couldn't Init dbconfigs: %v", err) } - return NewMysqld(mycnf, dbcfgs), nil + return NewMysqld(dbcfgs), mycnf, nil } diff --git a/go/vt/mysqlctl/fakemysqldaemon/fakemysqldaemon.go b/go/vt/mysqlctl/fakemysqldaemon/fakemysqldaemon.go index dcc63a13f75..3869933f8af 100644 --- a/go/vt/mysqlctl/fakemysqldaemon/fakemysqldaemon.go +++ b/go/vt/mysqlctl/fakemysqldaemon/fakemysqldaemon.go @@ -44,9 +44,6 @@ type FakeMysqlDaemon struct { // appPool is set if db is set. appPool *dbconnpool.ConnectionPool - // Mycnf will be returned by Cnf() - Mycnf *mysqlctl.Mycnf - // Running is used by Start / Shutdown Running bool @@ -151,18 +148,8 @@ func NewFakeMysqlDaemon(db *fakesqldb.DB) *FakeMysqlDaemon { return result } -// Cnf is part of the MysqlDaemon interface -func (fmd *FakeMysqlDaemon) Cnf() *mysqlctl.Mycnf { - return fmd.Mycnf -} - -// TabletDir is part of the MysqlDaemon interface. -func (fmd *FakeMysqlDaemon) TabletDir() string { - return "" -} - // Start is part of the MysqlDaemon interface -func (fmd *FakeMysqlDaemon) Start(ctx context.Context, mysqldArgs ...string) error { +func (fmd *FakeMysqlDaemon) Start(ctx context.Context, cnf *mysqlctl.Mycnf, mysqldArgs ...string) error { if fmd.Running { return fmt.Errorf("fake mysql daemon already running") } @@ -171,7 +158,7 @@ func (fmd *FakeMysqlDaemon) Start(ctx context.Context, mysqldArgs ...string) err } // Shutdown is part of the MysqlDaemon interface -func (fmd *FakeMysqlDaemon) Shutdown(ctx context.Context, waitForMysqld bool) error { +func (fmd *FakeMysqlDaemon) Shutdown(ctx context.Context, cnf *mysqlctl.Mycnf, waitForMysqld bool) error { if !fmd.Running { return fmt.Errorf("fake mysql daemon not running") } @@ -185,17 +172,17 @@ func (fmd *FakeMysqlDaemon) RunMysqlUpgrade() error { } // ReinitConfig is part of the MysqlDaemon interface -func (fmd *FakeMysqlDaemon) ReinitConfig(ctx context.Context) error { +func (fmd *FakeMysqlDaemon) ReinitConfig(ctx context.Context, cnf *mysqlctl.Mycnf) error { return nil } // RefreshConfig is part of the MysqlDaemon interface -func (fmd *FakeMysqlDaemon) RefreshConfig(ctx context.Context) error { +func (fmd *FakeMysqlDaemon) RefreshConfig(ctx context.Context, cnf *mysqlctl.Mycnf) error { return nil } // Wait is part of the MysqlDaemon interface. -func (fmd *FakeMysqlDaemon) Wait(ctx context.Context) error { +func (fmd *FakeMysqlDaemon) Wait(ctx context.Context, cnf *mysqlctl.Mycnf) error { return nil } diff --git a/go/vt/mysqlctl/grpcmysqlctlserver/server.go b/go/vt/mysqlctl/grpcmysqlctlserver/server.go index 340e7fc3277..72be321923a 100644 --- a/go/vt/mysqlctl/grpcmysqlctlserver/server.go +++ b/go/vt/mysqlctl/grpcmysqlctlserver/server.go @@ -31,17 +31,18 @@ import ( // server is our gRPC server. type server struct { + cnf *mysqlctl.Mycnf mysqld *mysqlctl.Mysqld } // Start implements the server side of the MysqlctlClient interface. func (s *server) Start(ctx context.Context, request *mysqlctlpb.StartRequest) (*mysqlctlpb.StartResponse, error) { - return &mysqlctlpb.StartResponse{}, s.mysqld.Start(ctx, request.MysqldArgs...) + return &mysqlctlpb.StartResponse{}, s.mysqld.Start(ctx, s.cnf, request.MysqldArgs...) } // Shutdown implements the server side of the MysqlctlClient interface. func (s *server) Shutdown(ctx context.Context, request *mysqlctlpb.ShutdownRequest) (*mysqlctlpb.ShutdownResponse, error) { - return &mysqlctlpb.ShutdownResponse{}, s.mysqld.Shutdown(ctx, request.WaitForMysqld) + return &mysqlctlpb.ShutdownResponse{}, s.mysqld.Shutdown(ctx, s.cnf, request.WaitForMysqld) } // RunMysqlUpgrade implements the server side of the MysqlctlClient interface. @@ -51,15 +52,15 @@ func (s *server) RunMysqlUpgrade(ctx context.Context, request *mysqlctlpb.RunMys // ReinitConfig implements the server side of the MysqlctlClient interface. func (s *server) ReinitConfig(ctx context.Context, request *mysqlctlpb.ReinitConfigRequest) (*mysqlctlpb.ReinitConfigResponse, error) { - return &mysqlctlpb.ReinitConfigResponse{}, s.mysqld.ReinitConfig(ctx) + return &mysqlctlpb.ReinitConfigResponse{}, s.mysqld.ReinitConfig(ctx, s.cnf) } // RefreshConfig implements the server side of the MysqlctlClient interface. func (s *server) RefreshConfig(ctx context.Context, request *mysqlctlpb.RefreshConfigRequest) (*mysqlctlpb.RefreshConfigResponse, error) { - return &mysqlctlpb.RefreshConfigResponse{}, s.mysqld.RefreshConfig(ctx) + return &mysqlctlpb.RefreshConfigResponse{}, s.mysqld.RefreshConfig(ctx, s.cnf) } // StartServer registers the Server for RPCs. -func StartServer(s *grpc.Server, mysqld *mysqlctl.Mysqld) { - mysqlctlpb.RegisterMysqlCtlServer(s, &server{mysqld}) +func StartServer(s *grpc.Server, cnf *mysqlctl.Mycnf, mysqld *mysqlctl.Mysqld) { + mysqlctlpb.RegisterMysqlCtlServer(s, &server{cnf: cnf, mysqld: mysqld}) } diff --git a/go/vt/mysqlctl/mycnf.go b/go/vt/mysqlctl/mycnf.go index 3adba0728e1..f4dae766bbb 100644 --- a/go/vt/mysqlctl/mycnf.go +++ b/go/vt/mysqlctl/mycnf.go @@ -26,6 +26,7 @@ import ( "fmt" "io" "os" + "path" "strconv" ) @@ -107,6 +108,11 @@ type Mycnf struct { path string // the actual path that represents this mycnf } +// TabletDir returns the tablet directory. +func (cnf *Mycnf) TabletDir() string { + return path.Dir(cnf.DataDir) +} + func (cnf *Mycnf) lookup(key string) string { key = normKey([]byte(key)) return cnf.mycnfMap[key] diff --git a/go/vt/mysqlctl/mycnf_flag.go b/go/vt/mysqlctl/mycnf_flag.go index 031d0869708..024be07403d 100644 --- a/go/vt/mysqlctl/mycnf_flag.go +++ b/go/vt/mysqlctl/mycnf_flag.go @@ -119,9 +119,6 @@ func NewMycnfFromFlags(uid uint32) (mycnf *Mycnf, err error) { } if *flagMycnfFile == "" { - if uid == 0 { - log.Exitf("No mycnf_server_id, no mycnf-file, and no backup server id to use") - } *flagMycnfFile = MycnfFile(uid) log.Infof("No mycnf_server_id, no mycnf-file specified, using default config for server id %v: %v", uid, *flagMycnfFile) } else { diff --git a/go/vt/mysqlctl/mysql_daemon.go b/go/vt/mysqlctl/mysql_daemon.go index 8828eb28782..2ee5b3e7bff 100644 --- a/go/vt/mysqlctl/mysql_daemon.go +++ b/go/vt/mysqlctl/mysql_daemon.go @@ -29,18 +29,13 @@ import ( // MysqlDaemon is the interface we use for abstracting Mysqld. type MysqlDaemon interface { - // Cnf returns the underlying mycnf - Cnf() *Mycnf - // TabletDir returns the tablet directory. - TabletDir() string - // methods related to mysql running or not - Start(ctx context.Context, mysqldArgs ...string) error - Shutdown(ctx context.Context, waitForMysqld bool) error + Start(ctx context.Context, cnf *Mycnf, mysqldArgs ...string) error + Shutdown(ctx context.Context, cnf *Mycnf, waitForMysqld bool) error RunMysqlUpgrade() error - ReinitConfig(ctx context.Context) error - RefreshConfig(ctx context.Context) error - Wait(ctx context.Context) error + ReinitConfig(ctx context.Context, cnf *Mycnf) error + RefreshConfig(ctx context.Context, cnf *Mycnf) error + Wait(ctx context.Context, cnf *Mycnf) error // GetMysqlPort returns the current port mysql is listening on. GetMysqlPort() (int32, error) diff --git a/go/vt/mysqlctl/mysqld.go b/go/vt/mysqlctl/mysqld.go index 900f8f9ee2c..46c2acf6fe1 100644 --- a/go/vt/mysqlctl/mysqld.go +++ b/go/vt/mysqlctl/mysqld.go @@ -80,11 +80,9 @@ var ( // Mysqld is the object that represents a mysqld daemon running on this server. type Mysqld struct { - config *Mycnf - dbcfgs *dbconfigs.DBConfigs - dbaPool *dbconnpool.ConnectionPool - appPool *dbconnpool.ConnectionPool - tabletDir string + dbcfgs *dbconfigs.DBConfigs + dbaPool *dbconnpool.ConnectionPool + appPool *dbconnpool.ConnectionPool // mutex protects the fields below. mutex sync.Mutex @@ -94,11 +92,9 @@ type Mysqld struct { // NewMysqld creates a Mysqld object based on the provided configuration // and connection parameters. -func NewMysqld(config *Mycnf, dbcfgs *dbconfigs.DBConfigs) *Mysqld { +func NewMysqld(dbcfgs *dbconfigs.DBConfigs) *Mysqld { result := &Mysqld{ - config: config, - dbcfgs: dbcfgs, - tabletDir: path.Dir(config.DataDir), + dbcfgs: dbcfgs, } // Create and open the connection pool for dba access. @@ -112,17 +108,6 @@ func NewMysqld(config *Mycnf, dbcfgs *dbconfigs.DBConfigs) *Mysqld { return result } -// Cnf returns the mysql config for the daemon -func (mysqld *Mysqld) Cnf() *Mycnf { - return mysqld.config -} - -// TabletDir returns the main tablet directory. -// It's a method so it can be accessed through the MysqlDaemon interface. -func (mysqld *Mysqld) TabletDir() string { - return mysqld.tabletDir -} - // RunMysqlUpgrade will run the mysql_upgrade program on the current // install. Will be called only when mysqld is running with no // network and no grant tables. @@ -160,18 +145,18 @@ func (mysqld *Mysqld) RunMysqlUpgrade() error { if err != nil { return err } - cnf, err := mysqld.defaultsExtraFile(params) + defaultsFile, err := mysqld.defaultsExtraFile(params) if err != nil { return err } - defer os.Remove(cnf) + defer os.Remove(defaultsFile) // Run the program, if it fails, we fail. Note in this // moment, mysqld is running with no grant tables on the local // socket only, so this doesn't need any user or password. args := []string{ // --defaults-file=* must be the first arg. - "--defaults-file=" + cnf, + "--defaults-file=" + defaultsFile, "--force", // Don't complain if it's already been upgraded. } cmd := exec.Command(name, args...) @@ -186,7 +171,7 @@ func (mysqld *Mysqld) RunMysqlUpgrade() error { // If a mysqlctld address is provided in a flag, Start will run // remotely. When waiting for mysqld to start, we will use // the dba user. -func (mysqld *Mysqld) Start(ctx context.Context, mysqldArgs ...string) error { +func (mysqld *Mysqld) Start(ctx context.Context, cnf *Mycnf, mysqldArgs ...string) error { // Execute as remote action on mysqlctld if requested. if *socketFile != "" { log.Infof("executing Mysqld.Start() remotely via mysqlctld server: %v", *socketFile) @@ -198,15 +183,15 @@ func (mysqld *Mysqld) Start(ctx context.Context, mysqldArgs ...string) error { return client.Start(ctx, mysqldArgs...) } - if err := mysqld.startNoWait(ctx, mysqldArgs...); err != nil { + if err := mysqld.startNoWait(ctx, cnf, mysqldArgs...); err != nil { return err } - return mysqld.Wait(ctx) + return mysqld.Wait(ctx, cnf) } // startNoWait is the internal version of Start, and it doesn't wait. -func (mysqld *Mysqld) startNoWait(ctx context.Context, mysqldArgs ...string) error { +func (mysqld *Mysqld) startNoWait(ctx context.Context, cnf *Mycnf, mysqldArgs ...string) error { var name string ts := fmt.Sprintf("Mysqld.Start(%v)", time.Now().Unix()) @@ -234,7 +219,7 @@ func (mysqld *Mysqld) startNoWait(ctx context.Context, mysqldArgs ...string) err } } arg := []string{ - "--defaults-file=" + mysqld.config.path} + "--defaults-file=" + cnf.path} arg = append(arg, mysqldArgs...) env := []string{os.ExpandEnv("LD_LIBRARY_PATH=$VT_MYSQL_ROOT/lib/mysql")} @@ -297,27 +282,27 @@ func (mysqld *Mysqld) startNoWait(ctx context.Context, mysqldArgs ...string) err // Wait returns nil when mysqld is up and accepting connections. It // will use the dba credentials to try to connect. Use wait() with // different credentials if needed. -func (mysqld *Mysqld) Wait(ctx context.Context) error { +func (mysqld *Mysqld) Wait(ctx context.Context, cnf *Mycnf) error { params, err := dbconfigs.WithCredentials(mysqld.dbcfgs.Dba()) if err != nil { return err } - return mysqld.wait(ctx, params) + return mysqld.wait(ctx, cnf, params) } // wait is the internal version of Wait, that takes credentials. -func (mysqld *Mysqld) wait(ctx context.Context, params *mysql.ConnParams) error { - log.Infof("Waiting for mysqld socket file (%v) to be ready...", mysqld.config.SocketFile) +func (mysqld *Mysqld) wait(ctx context.Context, cnf *Mycnf, params *mysql.ConnParams) error { + log.Infof("Waiting for mysqld socket file (%v) to be ready...", cnf.SocketFile) for { select { case <-ctx.Done(): - return errors.New("deadline exceeded waiting for mysqld socket file to appear: " + mysqld.config.SocketFile) + return errors.New("deadline exceeded waiting for mysqld socket file to appear: " + cnf.SocketFile) default: } - _, statErr := os.Stat(mysqld.config.SocketFile) + _, statErr := os.Stat(cnf.SocketFile) if statErr == nil { // Make sure the socket file isn't stale. conn, connErr := mysql.Connect(ctx, params) @@ -340,7 +325,7 @@ func (mysqld *Mysqld) wait(ctx context.Context, params *mysql.ConnParams) error // flushed - on the order of 20-30 minutes. // // If a mysqlctld address is provided in a flag, Shutdown will run remotely. -func (mysqld *Mysqld) Shutdown(ctx context.Context, waitForMysqld bool) error { +func (mysqld *Mysqld) Shutdown(ctx context.Context, cnf *Mycnf, waitForMysqld bool) error { log.Infof("Mysqld.Shutdown") // Execute as remote action on mysqlctld if requested. @@ -364,8 +349,8 @@ func (mysqld *Mysqld) Shutdown(ctx context.Context, waitForMysqld bool) error { mysqld.mutex.Unlock() // possibly mysql is already shutdown, check for a few files first - _, socketPathErr := os.Stat(mysqld.config.SocketFile) - _, pidPathErr := os.Stat(mysqld.config.PidFile) + _, socketPathErr := os.Stat(cnf.SocketFile) + _, pidPathErr := os.Stat(cnf.PidFile) if os.IsNotExist(socketPathErr) && os.IsNotExist(pidPathErr) { log.Warningf("assuming mysqld already shut down - no socket, no pid file found") return nil @@ -421,7 +406,7 @@ func (mysqld *Mysqld) Shutdown(ctx context.Context, waitForMysqld bool) error { // didn't start. if waitForMysqld { log.Infof("Mysqld.Shutdown: waiting for socket file (%v) and pid file (%v) to disappear", - mysqld.config.SocketFile, mysqld.config.PidFile) + cnf.SocketFile, cnf.PidFile) for { select { @@ -430,8 +415,8 @@ func (mysqld *Mysqld) Shutdown(ctx context.Context, waitForMysqld bool) error { default: } - _, socketPathErr = os.Stat(mysqld.config.SocketFile) - _, pidPathErr = os.Stat(mysqld.config.PidFile) + _, socketPathErr = os.Stat(cnf.SocketFile) + _, pidPathErr = os.Stat(cnf.PidFile) if os.IsNotExist(socketPathErr) && os.IsNotExist(pidPathErr) { return nil } @@ -479,9 +464,9 @@ func binaryPath(root, binary string) (string, error) { // InitConfig will create the default directory structure for the mysqld process, // generate / configure a my.cnf file. -func (mysqld *Mysqld) InitConfig() error { +func (mysqld *Mysqld) InitConfig(cnf *Mycnf) error { log.Infof("mysqlctl.InitConfig") - err := mysqld.createDirs() + err := mysqld.createDirs(cnf) if err != nil { log.Errorf("%s", err.Error()) return err @@ -493,8 +478,8 @@ func (mysqld *Mysqld) InitConfig() error { } // Set up config files. - if err = mysqld.initConfig(root, mysqld.config.path); err != nil { - log.Errorf("failed creating %v: %v", mysqld.config.path, err) + if err = mysqld.initConfig(root, cnf, cnf.path); err != nil { + log.Errorf("failed creating %v: %v", cnf.path, err) return err } return nil @@ -503,22 +488,22 @@ func (mysqld *Mysqld) InitConfig() error { // Init will create the default directory structure for the mysqld process, // generate / configure a my.cnf file install a skeleton database, // and apply the provided initial SQL file. -func (mysqld *Mysqld) Init(ctx context.Context, initDBSQLFile string) error { +func (mysqld *Mysqld) Init(ctx context.Context, cnf *Mycnf, initDBSQLFile string) error { log.Infof("mysqlctl.Init") - err := mysqld.InitConfig() + err := mysqld.InitConfig(cnf) if err != nil { log.Errorf("%s", err.Error()) return err } // Install data dir. - if err = mysqld.installDataDir(); err != nil { + if err = mysqld.installDataDir(cnf); err != nil { return err } // Start mysqld. We do not use Start, as we have to wait using // the root user. - if err = mysqld.startNoWait(ctx); err != nil { - log.Errorf("failed starting mysqld (check mysql error log %v for more info): %v", mysqld.config.ErrorLogPath, err) + if err = mysqld.startNoWait(ctx, cnf); err != nil { + log.Errorf("failed starting mysqld (check mysql error log %v for more info): %v", cnf.ErrorLogPath, err) return err } @@ -527,10 +512,10 @@ func (mysqld *Mysqld) Init(ctx context.Context, initDBSQLFile string) error { params := &mysql.ConnParams{ Uname: "root", Charset: "utf8", - UnixSocket: mysqld.config.SocketFile, + UnixSocket: cnf.SocketFile, } - if err = mysqld.wait(ctx, params); err != nil { - log.Errorf("failed starting mysqld in time (check mysyql error log %v for more info): %v", mysqld.config.ErrorLogPath, err) + if err = mysqld.wait(ctx, cnf, params); err != nil { + log.Errorf("failed starting mysqld in time (check mysyql error log %v for more info): %v", cnf.ErrorLogPath, err) return err } @@ -554,7 +539,7 @@ func useMysqldInitialize(version string) bool { strings.Contains(version, "Ver 8.0.") } -func (mysqld *Mysqld) installDataDir() error { +func (mysqld *Mysqld) installDataDir(cnf *Mycnf) error { mysqlRoot, err := vtenv.VtMysqlRoot() if err != nil { return err @@ -579,7 +564,7 @@ func (mysqld *Mysqld) installDataDir() error { log.Infof("Installing data dir with mysqld --initialize-insecure") args := []string{ - "--defaults-file=" + mysqld.config.path, + "--defaults-file=" + cnf.path, "--basedir=" + mysqlBaseDir, "--initialize-insecure", // Use empty 'root'@'localhost' password. } @@ -592,7 +577,7 @@ func (mysqld *Mysqld) installDataDir() error { log.Infof("Installing data dir with mysql_install_db") args := []string{ - "--defaults-file=" + mysqld.config.path, + "--defaults-file=" + cnf.path, "--basedir=" + mysqlBaseDir, } cmdPath, err := binaryPath(mysqlRoot, "mysql_install_db") @@ -606,16 +591,16 @@ func (mysqld *Mysqld) installDataDir() error { return nil } -func (mysqld *Mysqld) initConfig(root, outFile string) error { +func (mysqld *Mysqld) initConfig(root string, cnf *Mycnf, outFile string) error { var err error var configData string switch hr := hook.NewSimpleHook("make_mycnf").Execute(); hr.ExitStatus { case hook.HOOK_DOES_NOT_EXIST: log.Infof("make_mycnf hook doesn't exist, reading template files") - configData, err = mysqld.config.makeMycnf(getMycnfTemplates(root)) + configData, err = cnf.makeMycnf(getMycnfTemplates(root)) case hook.HOOK_SUCCESS: - configData, err = mysqld.config.fillMycnfTemplate(hr.Stdout) + configData, err = cnf.fillMycnfTemplate(hr.Stdout) default: return fmt.Errorf("make_mycnf hook failed(%v): %v", hr.ExitStatus, hr.Stderr) } @@ -648,7 +633,7 @@ func getMycnfTemplates(root string) []string { // RefreshConfig attempts to recreate the my.cnf from templates, and log and // swap in to place if it's updated. It keeps a copy of the last version in case fallback is required. // Should be called from a stable replica, server_id is not regenerated. -func (mysqld *Mysqld) RefreshConfig(ctx context.Context) error { +func (mysqld *Mysqld) RefreshConfig(ctx context.Context, cnf *Mycnf) error { // Execute as remote action on mysqlctld if requested. if *socketFile != "" { log.Infof("executing Mysqld.RefreshConfig() remotely via mysqlctld server: %v", *socketFile) @@ -665,20 +650,20 @@ func (mysqld *Mysqld) RefreshConfig(ctx context.Context) error { if err != nil { return err } - f, err := ioutil.TempFile(path.Dir(mysqld.config.path), "my.cnf") + f, err := ioutil.TempFile(path.Dir(cnf.path), "my.cnf") if err != nil { return fmt.Errorf("Could not create temp file: %v", err) } defer os.Remove(f.Name()) - err = mysqld.initConfig(root, f.Name()) + err = mysqld.initConfig(root, cnf, f.Name()) if err != nil { return fmt.Errorf("Could not initConfig in %v: %v", f.Name(), err) } - existing, err := ioutil.ReadFile(mysqld.config.path) + existing, err := ioutil.ReadFile(cnf.path) if err != nil { - return fmt.Errorf("Could not read existing file %v: %v", mysqld.config.path, err) + return fmt.Errorf("Could not read existing file %v: %v", cnf.path, err) } updated, err := ioutil.ReadFile(f.Name()) if err != nil { @@ -690,14 +675,14 @@ func (mysqld *Mysqld) RefreshConfig(ctx context.Context) error { return nil } - backupPath := mysqld.config.path + ".previous" - err = os.Rename(mysqld.config.path, backupPath) + backupPath := cnf.path + ".previous" + err = os.Rename(cnf.path, backupPath) if err != nil { - return fmt.Errorf("Could not back up existing %v: %v", mysqld.config.path, err) + return fmt.Errorf("Could not back up existing %v: %v", cnf.path, err) } - err = os.Rename(f.Name(), mysqld.config.path) + err = os.Rename(f.Name(), cnf.path) if err != nil { - return fmt.Errorf("Could not move %v to %v: %v", f.Name(), mysqld.config.path, err) + return fmt.Errorf("Could not move %v to %v: %v", f.Name(), cnf.path, err) } log.Infof("Updated my.cnf. Backup of previous version available in %v", backupPath) @@ -708,7 +693,7 @@ func (mysqld *Mysqld) RefreshConfig(ctx context.Context) error { // moment it only randomizes ServerID because it's not safe to restore a replica // from a backup and then give it the same ServerID as before, MySQL can then // skip transactions in the replication stream with the same server_id. -func (mysqld *Mysqld) ReinitConfig(ctx context.Context) error { +func (mysqld *Mysqld) ReinitConfig(ctx context.Context, cnf *Mycnf) error { log.Infof("Mysqld.ReinitConfig") // Execute as remote action on mysqlctld if requested. @@ -722,27 +707,28 @@ func (mysqld *Mysqld) ReinitConfig(ctx context.Context) error { return client.ReinitConfig(ctx) } - if err := mysqld.config.RandomizeMysqlServerID(); err != nil { + if err := cnf.RandomizeMysqlServerID(); err != nil { return err } root, err := vtenv.VtRoot() if err != nil { return err } - return mysqld.initConfig(root, mysqld.config.path) + return mysqld.initConfig(root, cnf, cnf.path) } -func (mysqld *Mysqld) createDirs() error { - log.Infof("creating directory %s", mysqld.tabletDir) - if err := os.MkdirAll(mysqld.tabletDir, os.ModePerm); err != nil { +func (mysqld *Mysqld) createDirs(cnf *Mycnf) error { + tabletDir := cnf.TabletDir() + log.Infof("creating directory %s", tabletDir) + if err := os.MkdirAll(tabletDir, os.ModePerm); err != nil { return err } for _, dir := range TopLevelDirs() { - if err := mysqld.createTopDir(dir); err != nil { + if err := mysqld.createTopDir(cnf, dir); err != nil { return err } } - for _, dir := range mysqld.config.directoryList() { + for _, dir := range cnf.directoryList() { log.Infof("creating directory %s", dir) if err := os.MkdirAll(dir, os.ModePerm); err != nil { return err @@ -759,20 +745,21 @@ func (mysqld *Mysqld) createDirs() error { // that points to the newly created directory. For example, if // /vt/data is present, it will create the following structure: // /vt/data/vt_xxxx /vt/vt_xxxx/data -> /vt/data/vt_xxxx -func (mysqld *Mysqld) createTopDir(dir string) error { - vtname := path.Base(mysqld.tabletDir) +func (mysqld *Mysqld) createTopDir(cnf *Mycnf, dir string) error { + tabletDir := cnf.TabletDir() + vtname := path.Base(tabletDir) target := path.Join(vtenv.VtDataRoot(), dir) _, err := os.Lstat(target) if err != nil { if os.IsNotExist(err) { - topdir := path.Join(mysqld.tabletDir, dir) + topdir := path.Join(tabletDir, dir) log.Infof("creating directory %s", topdir) return os.MkdirAll(topdir, os.ModePerm) } return err } linkto := path.Join(target, vtname) - source := path.Join(mysqld.tabletDir, dir) + source := path.Join(tabletDir, dir) log.Infof("creating directory %s", linkto) err = os.MkdirAll(linkto, os.ModePerm) if err != nil { @@ -783,9 +770,9 @@ func (mysqld *Mysqld) createTopDir(dir string) error { } // Teardown will shutdown the running daemon, and delete the root directory. -func (mysqld *Mysqld) Teardown(ctx context.Context, force bool) error { +func (mysqld *Mysqld) Teardown(ctx context.Context, cnf *Mycnf, force bool) error { log.Infof("mysqlctl.Teardown") - if err := mysqld.Shutdown(ctx, true); err != nil { + if err := mysqld.Shutdown(ctx, cnf, true); err != nil { log.Warningf("failed mysqld shutdown: %v", err.Error()) if !force { return err @@ -793,7 +780,7 @@ func (mysqld *Mysqld) Teardown(ctx context.Context, force bool) error { } var removalErr error for _, dir := range TopLevelDirs() { - qdir := path.Join(mysqld.tabletDir, dir) + qdir := path.Join(cnf.TabletDir(), dir) if err := deleteTopDir(qdir); err != nil { removalErr = err } diff --git a/go/vt/vttablet/tabletmanager/action_agent.go b/go/vt/vttablet/tabletmanager/action_agent.go index 79e6dae5600..4abe92269ef 100644 --- a/go/vt/vttablet/tabletmanager/action_agent.go +++ b/go/vt/vttablet/tabletmanager/action_agent.go @@ -87,6 +87,7 @@ type ActionAgent struct { HealthReporter health.Reporter TopoServer *topo.Server TabletAlias *topodatapb.TabletAlias + Cnf *mysqlctl.Mycnf MysqlDaemon mysqlctl.MysqlDaemon DBConfigs *dbconfigs.DBConfigs BinlogPlayerMap *BinlogPlayerMap @@ -228,12 +229,18 @@ func NewActionAgent( batchCtx: batchCtx, TopoServer: ts, TabletAlias: tabletAlias, + Cnf: mycnf, MysqlDaemon: mysqld, DBConfigs: dbcfgs, History: history.New(historyLength), _healthy: fmt.Errorf("healthcheck not run yet"), orc: orc, } + // Sanity check for inconsistent flags + if agent.Cnf == nil && *restoreFromBackup { + return nil, fmt.Errorf("you cannot enable -restore_from_backup without a my.cnf file") + } + agent.registerQueryRuleSources() // try to initialize the tablet if we have to @@ -260,16 +267,11 @@ func NewActionAgent( mysqlHost = appConfig.Host mysqlPort = int32(appConfig.Port) } else { - // Assume unix socket was specified and try to figure out the mysql port - // by other means. - mysqlPort = mycnf.MysqlPort - if mysqlPort == 0 { - // we don't know the port, try to get it from mysqld - var err error - mysqlPort, err = mysqld.GetMysqlPort() - if err != nil { - log.Warningf("Cannot get current mysql port, will use 0 for now: %v", err) - } + // Assume unix socket was specified and try to get the port from mysqld + var err error + mysqlPort, err = mysqld.GetMysqlPort() + if err != nil { + log.Warningf("Cannot get current mysql port, will try to get it later: %v", err) } } @@ -336,6 +338,7 @@ func NewTestActionAgent(batchCtx context.Context, ts *topo.Server, tabletAlias * batchCtx: batchCtx, TopoServer: ts, TabletAlias: tabletAlias, + Cnf: nil, MysqlDaemon: mysqlDaemon, DBConfigs: &dbconfigs.DBConfigs{}, BinlogPlayerMap: nil, @@ -374,6 +377,7 @@ func NewComboActionAgent(batchCtx context.Context, ts *topo.Server, tabletAlias batchCtx: batchCtx, TopoServer: ts, TabletAlias: tabletAlias, + Cnf: nil, MysqlDaemon: mysqlDaemon, DBConfigs: dbcfgs, BinlogPlayerMap: nil, @@ -477,9 +481,14 @@ func (agent *ActionAgent) slaveStopped() bool { return *agent._slaveStopped } + // If there's no Cnf file, don't read state. + if agent.Cnf == nil { + return false + } + // If the marker file exists, we're stopped. // Treat any read error as if the file doesn't exist. - _, err := os.Stat(path.Join(agent.MysqlDaemon.TabletDir(), slaveStoppedFile)) + _, err := os.Stat(path.Join(agent.Cnf.TabletDir(), slaveStoppedFile)) slaveStopped := err == nil agent._slaveStopped = &slaveStopped return slaveStopped @@ -495,7 +504,10 @@ func (agent *ActionAgent) setSlaveStopped(slaveStopped bool) { // We store a marker in the filesystem so it works regardless of whether // mysqld is running, and so it's tied to this particular instance of the // tablet data dir (the one that's paused at a known replication position). - tabletDir := agent.MysqlDaemon.TabletDir() + if agent.Cnf == nil { + return + } + tabletDir := agent.Cnf.TabletDir() if tabletDir == "" { return } diff --git a/go/vt/vttablet/tabletmanager/restore.go b/go/vt/vttablet/tabletmanager/restore.go index 7c252e54d1c..030b575b4a2 100644 --- a/go/vt/vttablet/tabletmanager/restore.go +++ b/go/vt/vttablet/tabletmanager/restore.go @@ -48,6 +48,9 @@ func (agent *ActionAgent) RestoreData(ctx context.Context, logger logutil.Logger return err } defer agent.unlock() + if agent.Cnf == nil { + return fmt.Errorf("cannot perform restore without my.cnf, please restart vttablet with a my.cnf file specified") + } return agent.restoreDataLocked(ctx, logger, deleteBeforeRestore) } @@ -75,7 +78,7 @@ func (agent *ActionAgent) restoreDataLocked(ctx context.Context, logger logutil. localMetadata := agent.getLocalMetadataValues(originalType) tablet := agent.Tablet() dir := fmt.Sprintf("%v/%v", tablet.Keyspace, tablet.Shard) - pos, err := mysqlctl.Restore(ctx, agent.MysqlDaemon, dir, *restoreConcurrency, agent.hookExtraEnv(), localMetadata, logger, deleteBeforeRestore, topoproto.TabletDbName(tablet)) + pos, err := mysqlctl.Restore(ctx, agent.Cnf, agent.MysqlDaemon, dir, *restoreConcurrency, agent.hookExtraEnv(), localMetadata, logger, deleteBeforeRestore, topoproto.TabletDbName(tablet)) switch err { case nil: // Starting from here we won't be able to recover if we get stopped by a cancelled diff --git a/go/vt/vttablet/tabletmanager/rpc_backup.go b/go/vt/vttablet/tabletmanager/rpc_backup.go index 66d40b1a819..2ad3cc2d988 100644 --- a/go/vt/vttablet/tabletmanager/rpc_backup.go +++ b/go/vt/vttablet/tabletmanager/rpc_backup.go @@ -36,6 +36,10 @@ func (agent *ActionAgent) Backup(ctx context.Context, concurrency int, logger lo } defer agent.unlock() + if agent.Cnf == nil { + return fmt.Errorf("cannot perform backup without my.cnf, please restart vttablet with a my.cnf file specified") + } + // update our type to BACKUP tablet, err := agent.TopoServer.GetTablet(ctx, agent.TabletAlias) if err != nil { @@ -60,7 +64,7 @@ func (agent *ActionAgent) Backup(ctx context.Context, concurrency int, logger lo // now we can run the backup dir := fmt.Sprintf("%v/%v", tablet.Keyspace, tablet.Shard) name := fmt.Sprintf("%v.%v", time.Now().UTC().Format("2006-01-02.150405"), topoproto.TabletAliasString(tablet.Alias)) - returnErr := mysqlctl.Backup(ctx, agent.MysqlDaemon, l, dir, name, concurrency, agent.hookExtraEnv()) + returnErr := mysqlctl.Backup(ctx, agent.Cnf, agent.MysqlDaemon, l, dir, name, concurrency, agent.hookExtraEnv()) // change our type back to the original value _, err = topotools.ChangeType(ctx, agent.TopoServer, tablet.Alias, originalType) diff --git a/go/vt/wrangler/testlib/backup_test.go b/go/vt/wrangler/testlib/backup_test.go index 9c4ee85c552..e359fba6916 100644 --- a/go/vt/wrangler/testlib/backup_test.go +++ b/go/vt/wrangler/testlib/backup_test.go @@ -108,13 +108,14 @@ func TestBackupRestore(t *testing.T) { "STOP SLAVE", "START SLAVE", } - sourceTablet.FakeMysqlDaemon.Mycnf = &mysqlctl.Mycnf{ + sourceTablet.StartActionLoop(t, wr) + defer sourceTablet.StopActionLoop(t) + + sourceTablet.Agent.Cnf = &mysqlctl.Mycnf{ DataDir: sourceDataDir, InnodbDataHomeDir: sourceInnodbDataDir, InnodbLogGroupHomeDir: sourceInnodbLogDir, } - sourceTablet.StartActionLoop(t, wr) - defer sourceTablet.StopActionLoop(t) // run the backup if err := vp.Run([]string{"Backup", topoproto.TabletAliasString(sourceTablet.Tablet.Alias)}); err != nil { @@ -150,15 +151,6 @@ func TestBackupRestore(t *testing.T) { "FAKE SET MASTER", "START SLAVE", } - destTablet.FakeMysqlDaemon.Mycnf = &mysqlctl.Mycnf{ - DataDir: sourceDataDir, - InnodbDataHomeDir: sourceInnodbDataDir, - InnodbLogGroupHomeDir: sourceInnodbLogDir, - BinLogPath: path.Join(root, "bin-logs/filename_prefix"), - RelayLogPath: path.Join(root, "relay-logs/filename_prefix"), - RelayLogIndexPath: path.Join(root, "relay-log.index"), - RelayLogInfoPath: path.Join(root, "relay-log.info"), - } destTablet.FakeMysqlDaemon.FetchSuperQueryMap = map[string]*sqltypes.Result{ "SHOW DATABASES": {}, } @@ -168,6 +160,16 @@ func TestBackupRestore(t *testing.T) { destTablet.StartActionLoop(t, wr) defer destTablet.StopActionLoop(t) + destTablet.Agent.Cnf = &mysqlctl.Mycnf{ + DataDir: sourceDataDir, + InnodbDataHomeDir: sourceInnodbDataDir, + InnodbLogGroupHomeDir: sourceInnodbLogDir, + BinLogPath: path.Join(root, "bin-logs/filename_prefix"), + RelayLogPath: path.Join(root, "relay-logs/filename_prefix"), + RelayLogIndexPath: path.Join(root, "relay-log.index"), + RelayLogInfoPath: path.Join(root, "relay-log.info"), + } + if err := destTablet.Agent.RestoreData(ctx, logutil.NewConsoleLogger(), false /* deleteBeforeRestore */); err != nil { t.Fatalf("RestoreData failed: %v", err) } From d8d26c7543a814bf85d2acf3f7c99f8761f67312 Mon Sep 17 00:00:00 2001 From: Sugu Sougoumarane Date: Fri, 20 Jul 2018 09:01:22 -0700 Subject: [PATCH 2/3] nomycnf: address review comments Signed-off-by: Sugu Sougoumarane --- go/cmd/mysqlctl/mysqlctl.go | 12 ++++++------ go/cmd/mysqlctld/mysqlctld.go | 4 ++-- go/cmd/vttablet/vttablet.go | 5 +++-- go/vt/dbconfigs/dbconfigs.go | 2 +- go/vt/mysqlctl/cmd.go | 15 +++++++-------- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/go/cmd/mysqlctl/mysqlctl.go b/go/cmd/mysqlctl/mysqlctl.go index 7d3c81e8f33..23db8721b78 100644 --- a/go/cmd/mysqlctl/mysqlctl.go +++ b/go/cmd/mysqlctl/mysqlctl.go @@ -48,7 +48,7 @@ func initConfigCmd(subFlags *flag.FlagSet, args []string) error { subFlags.Parse(args) // Generate my.cnf from scratch and use it to find mysqld. - mysqld, cnf, err := mysqlctl.CreateMysqld(uint32(*tabletUID), *mysqlSocket, int32(*mysqlPort)) + mysqld, cnf, err := mysqlctl.CreateMysqldAndMycnf(uint32(*tabletUID), *mysqlSocket, int32(*mysqlPort)) if err != nil { return fmt.Errorf("failed to initialize mysql config: %v", err) } @@ -65,7 +65,7 @@ func initCmd(subFlags *flag.FlagSet, args []string) error { subFlags.Parse(args) // Generate my.cnf from scratch and use it to find mysqld. - mysqld, cnf, err := mysqlctl.CreateMysqld(uint32(*tabletUID), *mysqlSocket, int32(*mysqlPort)) + mysqld, cnf, err := mysqlctl.CreateMysqldAndMycnf(uint32(*tabletUID), *mysqlSocket, int32(*mysqlPort)) if err != nil { return fmt.Errorf("failed to initialize mysql config: %v", err) } @@ -81,7 +81,7 @@ func initCmd(subFlags *flag.FlagSet, args []string) error { func reinitConfigCmd(subFlags *flag.FlagSet, args []string) error { // There ought to be an existing my.cnf, so use it to find mysqld. - mysqld, cnf, err := mysqlctl.OpenMysqld(uint32(*tabletUID)) + mysqld, cnf, err := mysqlctl.OpenMysqldAndMycnf(uint32(*tabletUID)) if err != nil { return fmt.Errorf("failed to find mysql config: %v", err) } @@ -98,7 +98,7 @@ func shutdownCmd(subFlags *flag.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.OpenMysqld(uint32(*tabletUID)) + mysqld, cnf, err := mysqlctl.OpenMysqldAndMycnf(uint32(*tabletUID)) if err != nil { return fmt.Errorf("failed to find mysql config: %v", err) } @@ -119,7 +119,7 @@ func startCmd(subFlags *flag.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.OpenMysqld(uint32(*tabletUID)) + mysqld, cnf, err := mysqlctl.OpenMysqldAndMycnf(uint32(*tabletUID)) if err != nil { return fmt.Errorf("failed to find mysql config: %v", err) } @@ -139,7 +139,7 @@ func teardownCmd(subFlags *flag.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.OpenMysqld(uint32(*tabletUID)) + mysqld, cnf, err := mysqlctl.OpenMysqldAndMycnf(uint32(*tabletUID)) if err != nil { return fmt.Errorf("failed to find mysql config: %v", err) } diff --git a/go/cmd/mysqlctld/mysqlctld.go b/go/cmd/mysqlctld/mysqlctld.go index f696562bd94..6f9cebf296c 100644 --- a/go/cmd/mysqlctld/mysqlctld.go +++ b/go/cmd/mysqlctld/mysqlctld.go @@ -75,7 +75,7 @@ func main() { log.Infof("mycnf file (%s) doesn't exist, initializing", mycnfFile) var err error - mysqld, cnf, err = mysqlctl.CreateMysqld(uint32(*tabletUID), *mysqlSocket, int32(*mysqlPort)) + mysqld, cnf, err = mysqlctl.CreateMysqldAndMycnf(uint32(*tabletUID), *mysqlSocket, int32(*mysqlPort)) if err != nil { log.Errorf("failed to initialize mysql config: %v", err) exit.Return(1) @@ -91,7 +91,7 @@ func main() { log.Infof("mycnf file (%s) already exists, starting without init", mycnfFile) var err error - mysqld, cnf, err = mysqlctl.OpenMysqld(uint32(*tabletUID)) + mysqld, cnf, err = mysqlctl.OpenMysqldAndMycnf(uint32(*tabletUID)) if err != nil { log.Errorf("failed to find mysql config: %v", err) exit.Return(1) diff --git a/go/cmd/vttablet/vttablet.go b/go/cmd/vttablet/vttablet.go index 8353209d9a6..f4ebedd9f5b 100644 --- a/go/cmd/vttablet/vttablet.go +++ b/go/cmd/vttablet/vttablet.go @@ -87,8 +87,9 @@ func main() { log.Info("connection parameters were specified. Not loading my.cnf.") } - // If connection parameters were not specified, we'll use - // mycnf's socket file (if present). + // If connection parameters were specified, socketFile will be empty. + // Otherwise, the socketFile (read from mycnf) will be used to initialize + // dbconfigs. dbcfgs, err := dbconfigs.Init(socketFile) if err != nil { log.Warning(err) diff --git a/go/vt/dbconfigs/dbconfigs.go b/go/vt/dbconfigs/dbconfigs.go index 0d5b6eae35a..802a5376c7f 100644 --- a/go/vt/dbconfigs/dbconfigs.go +++ b/go/vt/dbconfigs/dbconfigs.go @@ -231,7 +231,7 @@ func HasConnectionParams() bool { // is used to initialize the per-user conn params. func Init(defaultSocketFile string) (*DBConfigs, error) { // The new base configs, if set, supersede legacy settings. - if baseConfig.Host != "" || baseConfig.UnixSocket != "" { + if HasConnectionParams() { for _, uc := range dbConfigs.userConfigs { uc.param.Host = baseConfig.Host uc.param.Port = baseConfig.Port diff --git a/go/vt/mysqlctl/cmd.go b/go/vt/mysqlctl/cmd.go index f8cb758d42a..7289d999570 100644 --- a/go/vt/mysqlctl/cmd.go +++ b/go/vt/mysqlctl/cmd.go @@ -26,10 +26,9 @@ import ( "vitess.io/vitess/go/vt/dbconfigs" ) -// CreateMysqld returns a Mysqld object to use for working with a MySQL -// installation that hasn't been set up yet. This will additionally generate -// a new my.cnf from scratch and return a corresponding *Mycnf. -func CreateMysqld(tabletUID uint32, mysqlSocket string, mysqlPort int32) (*Mysqld, *Mycnf, error) { +// CreateMysqldAndMycnf returns a Mysqld and a Mycnf object to use for working with a MySQL +// installation that hasn't been set up yet. +func CreateMysqldAndMycnf(tabletUID uint32, mysqlSocket string, mysqlPort int32) (*Mysqld, *Mycnf, error) { mycnf := NewMycnf(tabletUID, mysqlPort) // Choose a random MySQL server-id, since this is a fresh data dir. // We don't want to use the tablet UID as the MySQL server-id, @@ -55,10 +54,10 @@ func CreateMysqld(tabletUID uint32, mysqlSocket string, mysqlPort int32) (*Mysql return NewMysqld(dbcfgs), mycnf, nil } -// OpenMysqld returns a Mysqld object to use for working with a MySQL -// installation that already exists. This will look for an existing my.cnf file -// and return a corresponding *Mycnf. -func OpenMysqld(tabletUID uint32) (*Mysqld, *Mycnf, error) { +// OpenMysqldAndMycnf returns a Mysqld and a Mycnf object to use for working with a MySQL +// installation that already exists. The Mycnf will be built based on the my.cnf file +// of the MySQL instance. +func OpenMysqldAndMycnf(tabletUID uint32) (*Mysqld, *Mycnf, error) { // We pass a port of 0, this will be read and overwritten from the path on disk mycnf, err := ReadMycnf(NewMycnf(tabletUID, 0)) if err != nil { From ac116287db4002bf9d2288344a2e22036a198424 Mon Sep 17 00:00:00 2001 From: Sugu Sougoumarane Date: Fri, 20 Jul 2018 16:30:58 -0700 Subject: [PATCH 3/3] nomycnf: add atest for Backup failure Signed-off-by: Sugu Sougoumarane --- test/tabletmanager.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/tabletmanager.py b/test/tabletmanager.py index 1ce2c27b571..2cfc236aabc 100755 --- a/test/tabletmanager.py +++ b/test/tabletmanager.py @@ -191,6 +191,15 @@ def test_command_line(self): qr = tablet_62044.execute('select id, msg from vt_select_test') self.assertEqual(len(qr['rows']), 4, 'expected 4 rows in vt_select_test: %s' % str(qr)) + + # Verify backup fails + try: + utils.run_vtctl(['Backup', tablet_62044.tablet_alias]) + except Exception as e: + self.assertIn('cannot perform backup without my.cnf', str(e)) + else: + self.assertFail('did not get an exception') + tablet_62044.kill_vttablet() def test_actions_and_timeouts(self):