func Start(mt *Mysqld, mysqlWaitTime time.Duration) error { var name string // try the mysqld start hook, if any h := hook.NewSimpleHook("mysqld_start") hr := h.Execute() switch hr.ExitStatus { case hook.HOOK_SUCCESS: // hook exists and worked, we can keep going name = "mysqld_start hook" case hook.HOOK_DOES_NOT_EXIST: // hook doesn't exist, run mysqld_safe ourselves log.Infof("No mysqld_start hook, running mysqld_safe directly") dir, err := vtenv.VtMysqlRoot() if err != nil { return err } name = path.Join(dir, "bin/mysqld_safe") arg := []string{ "--defaults-file=" + mt.config.path} env := []string{os.ExpandEnv("LD_LIBRARY_PATH=$VT_MYSQL_ROOT/lib/mysql")} cmd := exec.Command(name, arg...) cmd.Dir = dir cmd.Env = env log.Infof("mysqlctl.Start mysqlWaitTime:%v %#v", mysqlWaitTime, cmd) _, err = cmd.StderrPipe() if err != nil { return nil } err = cmd.Start() if err != nil { return nil } // wait so we don't get a bunch of defunct processes go cmd.Wait() default: // hook failed, we report error return fmt.Errorf("mysqld_start hook failed: %v", hr.String()) } // give it some time to succeed - usually by the time the socket emerges // we are in good shape for i := mysqlWaitTime; i >= 0; i -= time.Second { _, statErr := os.Stat(mt.config.SocketFile) if statErr == nil { // Make sure the socket file isn't stale. conn, connErr := mt.createConnection() if connErr == nil { conn.Close() return nil } } else if !os.IsNotExist(statErr) { return statErr } time.Sleep(time.Second) } return errors.New(name + ": deadline exceeded waiting for " + mt.config.SocketFile) }
// Shutdown will stop the mysqld daemon that is running in the background. // // waitForMysqld: should the function block until mysqld has stopped? // This can actually take a *long* time if the buffer cache needs to be fully // flushed - on the order of 20-30 minutes. func (mysqld *Mysqld) Shutdown(waitForMysqld bool, mysqlWaitTime time.Duration) error { log.Infof("mysqlctl.Shutdown") // possibly mysql is already shutdown, check for a few files first _, socketPathErr := os.Stat(mysqld.config.SocketFile) _, pidPathErr := os.Stat(mysqld.config.PidFile) if socketPathErr != nil && pidPathErr != nil { log.Warningf("assuming shutdown - no socket, no pid file") return nil } // try the mysqld shutdown hook, if any h := hook.NewSimpleHook("mysqld_shutdown") hr := h.Execute() switch hr.ExitStatus { case hook.HOOK_SUCCESS: // hook exists and worked, we can keep going case hook.HOOK_DOES_NOT_EXIST: // hook doesn't exist, try mysqladmin log.Infof("No mysqld_shutdown hook, running mysqladmin directly") dir, err := vtenv.VtMysqlRoot() if err != nil { return err } name := path.Join(dir, "bin/mysqladmin") arg := []string{ "-u", "vt_dba", "-S", mysqld.config.SocketFile, "shutdown"} env := []string{ os.ExpandEnv("LD_LIBRARY_PATH=$VT_MYSQL_ROOT/lib/mysql"), } _, err = execCmd(name, arg, env, dir) if err != nil { return err } default: // hook failed, we report error return fmt.Errorf("mysqld_shutdown hook failed: %v", hr.String()) } // wait for mysqld to really stop. use the sock file as a proxy for that since // we can't call wait() in a process we didn't start. if waitForMysqld { for i := mysqlWaitTime; i >= 0; i -= time.Second { _, statErr := os.Stat(mysqld.config.SocketFile) if statErr != nil && os.IsNotExist(statErr) { return nil } log.Infof("Mysqld.Shutdown: sleeping for 1s waiting for socket file %v", mysqld.config.SocketFile) time.Sleep(time.Second) } return errors.New("gave up waiting for mysqld to stop") } return nil }
// 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 { log.Infof("mysqlctl.Init") err := mysqld.createDirs() if err != nil { log.Errorf("%s", err.Error()) return err } root, err := vtenv.VtRoot() if err != nil { log.Errorf("%s", err.Error()) return err } // Set up config files. if err = mysqld.initConfig(root); err != nil { log.Errorf("failed creating %v: %v", mysqld.config.path, err) return err } // Install data dir. mysqlRoot, err := vtenv.VtMysqlRoot() if err != nil { log.Errorf("%v", err) return err } log.Infof("Installing data dir with mysql_install_db") args := []string{ "--defaults-file=" + mysqld.config.path, "--basedir=" + mysqlRoot, } if _, err = execCmd(path.Join(mysqlRoot, "bin/mysql_install_db"), args, nil, mysqlRoot, nil); err != nil { log.Errorf("mysql_install_db failed: %v", err) return err } // Start mysqld. if err = mysqld.Start(ctx); err != nil { log.Errorf("failed starting, check %v", mysqld.config.ErrorLogPath) return err } // Run initial SQL file. sqlFile, err := os.Open(initDBSQLFile) if err != nil { return fmt.Errorf("can't open init_db_sql_file (%v): %v", initDBSQLFile, err) } defer sqlFile.Close() if err := mysqld.executeMysqlScript("root", sqlFile); err != nil { return fmt.Errorf("can't run init_db_sql_file (%v): %v", initDBSQLFile, err) } return nil }
func (mysqld *Mysqld) installDataDir() error { mysqlRoot, err := vtenv.VtMysqlRoot() if err != nil { return err } mysqldPath, err := binaryPath(mysqlRoot, "mysqld") if err != nil { return err } // Check mysqld version. _, version, err := execCmd(mysqldPath, []string{"--version"}, nil, mysqlRoot, nil) if err != nil { return err } if strings.Contains(version, "Ver 5.7.") { // MySQL 5.7 GA and up have deprecated mysql_install_db. // Instead, initialization is built into mysqld. log.Infof("Installing data dir with mysqld --initialize-insecure") args := []string{ "--defaults-file=" + mysqld.config.path, "--basedir=" + mysqlRoot, "--initialize-insecure", // Use empty 'root'@'localhost' password. } if _, _, err = execCmd(mysqldPath, args, nil, mysqlRoot, nil); err != nil { log.Errorf("mysqld --initialize-insecure failed: %v", err) return err } return nil } log.Infof("Installing data dir with mysql_install_db") args := []string{ "--defaults-file=" + mysqld.config.path, "--basedir=" + mysqlRoot, } cmdPath, err := binaryPath(mysqlRoot, "mysql_install_db") if err != nil { return err } if _, _, err = execCmd(cmdPath, args, nil, mysqlRoot, nil); err != nil { log.Errorf("mysql_install_db failed: %v", err) return err } return nil }
// executeMysqlScript executes a .sql script file with the mysql command line tool. func (mysqld *Mysqld) executeMysqlScript(user string, sql io.Reader) error { dir, err := vtenv.VtMysqlRoot() if err != nil { return err } name := path.Join(dir, "bin/mysql") arg := []string{"--batch", "-u", user, "-S", mysqld.config.SocketFile} env := []string{ "LD_LIBRARY_PATH=" + path.Join(dir, "lib/mysql"), } _, _, err = execCmd(name, arg, env, dir, sql) if err != nil { return err } return nil }
// ExecuteMysqlCommand executes some SQL commands using a mysql command line interface process func (mysqld *Mysqld) ExecuteMysqlCommand(sql string) error { dir, err := vtenv.VtMysqlRoot() if err != nil { return err } name := path.Join(dir, "bin/mysql") arg := []string{ "-u", "vt_dba", "-S", mysqld.config.SocketFile, "-e", sql} env := []string{ "LD_LIBRARY_PATH=" + path.Join(dir, "lib/mysql"), } _, err = execCmd(name, arg, env, dir) if err != nil { return err } return nil }
// RunMysqlUpgrade will run the mysql_upgrade program on the current install. // Will not be called when mysqld is running. func (mysqld *Mysqld) RunMysqlUpgrade() error { // Execute as remote action on mysqlctld if requested. if *socketFile != "" { log.Infof("executing Mysqld.RunMysqlUpgrade() remotely via mysqlctld server: %v", *socketFile) client, err := mysqlctlclient.New("unix", *socketFile) if err != nil { return fmt.Errorf("can't dial mysqlctld: %v", err) } defer client.Close() return client.RunMysqlUpgrade(context.TODO()) } // find mysql_upgrade. If not there, we do nothing. dir, err := vtenv.VtMysqlRoot() if err != nil { log.Warningf("VT_MYSQL_ROOT not set, skipping mysql_upgrade step: %v", err) return nil } name := path.Join(dir, "bin/mysql_upgrade") if _, err := os.Stat(name); err != nil { log.Warningf("mysql_upgrade binary not present, skipping it: %v", err) return nil } // run the program, if it fails, we fail args := []string{ // --defaults-file=* must be the first arg. "--defaults-file=" + mysqld.config.path, "--socket", mysqld.config.SocketFile, "--user", mysqld.dba.Uname, "--force", // Don't complain if it's already been upgraded. } if mysqld.dba.Pass != "" { // --password must be omitted entirely if empty, or else it will prompt. args = append(args, "--password", mysqld.dba.Pass) } cmd := exec.Command(name, args...) cmd.Env = []string{os.ExpandEnv("LD_LIBRARY_PATH=$VT_MYSQL_ROOT/lib/mysql")} out, err := cmd.CombinedOutput() log.Infof("mysql_upgrade output: %s", out) return err }
// openBinlog opens the binlog and returns a ReadCloser on them func (pc *PrimeCache) openBinlog(slavestat *slaveStatus) (io.ReadCloser, error) { dir, err := vtenv.VtMysqlRoot() if err != nil { return nil, err } cmd := exec.Command( path.Join(dir, "bin/mysqlbinlog"), fmt.Sprintf("--start-position=%v", slavestat.relayLogPos), path.Join(pc.relayLogsPath, slavestat.relayLogFile), ) stdout, err := cmd.StdoutPipe() if err != nil { return nil, err } err = cmd.Start() if err != nil { stdout.Close() return nil, err } return stdout, nil }
// executeMysqlScript executes a .sql script file with the mysql command line tool. func (mysqld *Mysqld) executeMysqlScript(connParams *sqldb.ConnParams, sql io.Reader) error { dir, err := vtenv.VtMysqlRoot() if err != nil { return err } name, err := binaryPath(dir, "mysql") if err != nil { return err } arg := []string{"--batch", "-u", connParams.Uname, "-S", mysqld.config.SocketFile} if connParams.Pass != "" { // -p must be omitted entirely if empty, or else it will prompt. arg = append(arg, "-p"+connParams.Pass) } env := []string{ "LD_LIBRARY_PATH=" + path.Join(dir, "lib/mysql"), } _, _, err = execCmd(name, arg, env, dir, sql) if err != nil { return err } return nil }
// MysqlBinlog launches mysqlbinlog and returns a ReadCloser into which its output // will be piped. The stderr will be redirected to the log. func (mbl *MysqlBinlog) Launch(dbname, filename string, pos int64) (stdout io.ReadCloser, err error) { dir, err := vtenv.VtMysqlRoot() if err != nil { return nil, err } mbl.cmd = exec.Command( path.Join(dir, "bin/mysqlbinlog"), fmt.Sprintf("--database=%s", dbname), fmt.Sprintf("--start-position=%d", pos), filename, ) stdout, err = mbl.cmd.StdoutPipe() if err != nil { return nil, err } mbl.cmd.Stderr = &logWrapper{} err = mbl.cmd.Start() if err != nil { stdout.Close() return nil, err } return stdout, nil }
// findVtMysqlbinlogDir finds the directory that contains vt_mysqlbinlog: // could be with the mysql distribution, or with the vt distribution func findVtMysqlbinlogDir() (string, error) { // first look in VtRoot dir, err := vtenv.VtRoot() if err == nil { if _, err = os.Stat(path.Join(dir, "bin/vt_mysqlbinlog")); err == nil { return dir, nil } } // then look in VtMysqlRoot dir, err = vtenv.VtMysqlRoot() if err == nil { if _, err = os.Stat(path.Join(dir, "bin/vt_mysqlbinlog")); err == nil { return dir, nil } } // then try current directory + bin/vt_mysqlbinlog if _, err = os.Stat("bin/vt_mysqlbinlog"); err == nil { return "", nil } return "", fmt.Errorf("Cannot find vt_mysqlbinlog binary") }
// Shutdown will stop the mysqld daemon that is running in the background. // // waitForMysqld: should the function block until mysqld has stopped? // This can actually take a *long* time if the buffer cache needs to be fully // 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 { log.Infof("Mysqld.Shutdown") // Execute as remote action on mysqlctld if requested. if *socketFile != "" { log.Infof("executing Mysqld.Shutdown() remotely via mysqlctld server: %v", *socketFile) client, err := mysqlctlclient.New("unix", *socketFile) if err != nil { return fmt.Errorf("can't dial mysqlctld: %v", err) } defer client.Close() return client.Shutdown(ctx, waitForMysqld) } // We're shutting down on purpose. We no longer want to be notified when // mysqld terminates. mysqld.mutex.Lock() if mysqld.cancelWaitCmd != nil { close(mysqld.cancelWaitCmd) mysqld.cancelWaitCmd = nil } 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) if socketPathErr != nil && pidPathErr != nil { log.Warningf("assuming mysqld already shut down - no socket, no pid file found") return nil } // try the mysqld shutdown hook, if any h := hook.NewSimpleHook("mysqld_shutdown") hr := h.Execute() switch hr.ExitStatus { case hook.HOOK_SUCCESS: // hook exists and worked, we can keep going case hook.HOOK_DOES_NOT_EXIST: // hook doesn't exist, try mysqladmin log.Infof("No mysqld_shutdown hook, running mysqladmin directly") dir, err := vtenv.VtMysqlRoot() if err != nil { return err } name := path.Join(dir, "bin/mysqladmin") arg := []string{ "-u", "vt_dba", "-S", mysqld.config.SocketFile, "shutdown"} env := []string{ os.ExpandEnv("LD_LIBRARY_PATH=$VT_MYSQL_ROOT/lib/mysql"), } _, err = execCmd(name, arg, env, dir) if err != nil { return err } default: // hook failed, we report error return fmt.Errorf("mysqld_shutdown hook failed: %v", hr.String()) } // Wait for mysqld to really stop. Use the sock file as a // proxy for that since we can't call wait() in a process we // didn't start. if waitForMysqld { for { select { case <-ctx.Done(): return errors.New("gave up waiting for mysqld to stop") default: } _, statErr := os.Stat(mysqld.config.SocketFile) if statErr != nil && os.IsNotExist(statErr) { return nil } log.Infof("Mysqld.Shutdown: sleeping for 1s waiting for socket file %v", mysqld.config.SocketFile) time.Sleep(time.Second) } } return nil }
// Start will start the mysql daemon, either by running the 'mysqld_start' // hook, or by running mysqld_safe in the background. // If a mysqlctld address is provided in a flag, Start will run remotely. func (mysqld *Mysqld) Start(ctx context.Context) error { // Execute as remote action on mysqlctld if requested. if *socketFile != "" { log.Infof("executing Mysqld.Start() remotely via mysqlctld server: %v", *socketFile) client, err := mysqlctlclient.New("unix", *socketFile) if err != nil { return fmt.Errorf("can't dial mysqlctld: %v", err) } defer client.Close() return client.Start(ctx) } var name string ts := fmt.Sprintf("Mysqld.Start(%v)", time.Now().Unix()) // try the mysqld start hook, if any switch hr := hook.NewSimpleHook("mysqld_start").Execute(); hr.ExitStatus { case hook.HOOK_SUCCESS: // hook exists and worked, we can keep going name = "mysqld_start hook" case hook.HOOK_DOES_NOT_EXIST: // hook doesn't exist, run mysqld_safe ourselves log.Infof("%v: No mysqld_start hook, running mysqld_safe directly", ts) dir, err := vtenv.VtMysqlRoot() if err != nil { return err } name = path.Join(dir, "bin/mysqld_safe") arg := []string{ "--defaults-file=" + mysqld.config.path} env := []string{os.ExpandEnv("LD_LIBRARY_PATH=$VT_MYSQL_ROOT/lib/mysql")} cmd := exec.Command(name, arg...) cmd.Dir = dir cmd.Env = env log.Infof("%v %#v", ts, cmd) stderr, err := cmd.StderrPipe() if err != nil { return nil } stdout, err := cmd.StdoutPipe() if err != nil { return nil } go func() { scanner := bufio.NewScanner(stderr) for scanner.Scan() { log.Infof("%v stderr: %v", ts, scanner.Text()) } }() go func() { scanner := bufio.NewScanner(stdout) for scanner.Scan() { log.Infof("%v stdout: %v", ts, scanner.Text()) } }() err = cmd.Start() if err != nil { return nil } mysqld.mutex.Lock() mysqld.cancelWaitCmd = make(chan struct{}) go func(cancel <-chan struct{}) { // Wait regardless of cancel, so we don't generate defunct processes. err := cmd.Wait() log.Infof("%v exit: %v", ts, err) // The process exited. Trigger OnTerm callbacks, unless we were cancelled. select { case <-cancel: default: mysqld.mutex.Lock() for _, callback := range mysqld.onTermFuncs { go callback() } mysqld.mutex.Unlock() } }(mysqld.cancelWaitCmd) mysqld.mutex.Unlock() default: // hook failed, we report error return fmt.Errorf("mysqld_start hook failed: %v", hr.String()) } // give it some time to succeed - usually by the time the socket emerges // we are in good shape for { select { case <-ctx.Done(): return errors.New(name + ": deadline exceeded waiting for " + mysqld.config.SocketFile) default: } _, statErr := os.Stat(mysqld.config.SocketFile) if statErr == nil { // Make sure the socket file isn't stale. conn, connErr := mysqld.dbaPool.Get(0) if connErr == nil { conn.Recycle() return nil } } else if !os.IsNotExist(statErr) { return statErr } log.Infof("%v: sleeping for 1s waiting for socket file %v", ts, mysqld.config.SocketFile) time.Sleep(time.Second) } }
// Start will start the mysql daemon, either by running the 'mysqld_start' // hook, or by running mysqld_safe in the background. // If a mysqlctld address is provided in a flag, Start will run remotely. func (mysqld *Mysqld) Start(ctx context.Context, mysqldArgs ...string) error { // Execute as remote action on mysqlctld if requested. if *socketFile != "" { log.Infof("executing Mysqld.Start() remotely via mysqlctld server: %v", *socketFile) client, err := mysqlctlclient.New("unix", *socketFile) if err != nil { return fmt.Errorf("can't dial mysqlctld: %v", err) } defer client.Close() return client.Start(ctx, mysqldArgs...) } var name string ts := fmt.Sprintf("Mysqld.Start(%v)", time.Now().Unix()) // try the mysqld start hook, if any switch hr := hook.NewHook("mysqld_start", mysqldArgs).Execute(); hr.ExitStatus { case hook.HOOK_SUCCESS: // hook exists and worked, we can keep going name = "mysqld_start hook" case hook.HOOK_DOES_NOT_EXIST: // hook doesn't exist, run mysqld_safe ourselves log.Infof("%v: No mysqld_start hook, running mysqld_safe directly", ts) dir, err := vtenv.VtMysqlRoot() if err != nil { return err } name, err = binaryPath(dir, "mysqld_safe") if err != nil { return err } arg := []string{ "--defaults-file=" + mysqld.config.path} arg = append(arg, mysqldArgs...) env := []string{os.ExpandEnv("LD_LIBRARY_PATH=$VT_MYSQL_ROOT/lib/mysql")} cmd := exec.Command(name, arg...) cmd.Dir = dir cmd.Env = env log.Infof("%v %#v", ts, cmd) stderr, err := cmd.StderrPipe() if err != nil { return nil } stdout, err := cmd.StdoutPipe() if err != nil { return nil } go func() { scanner := bufio.NewScanner(stderr) for scanner.Scan() { log.Infof("%v stderr: %v", ts, scanner.Text()) } }() go func() { scanner := bufio.NewScanner(stdout) for scanner.Scan() { log.Infof("%v stdout: %v", ts, scanner.Text()) } }() err = cmd.Start() if err != nil { return nil } mysqld.mutex.Lock() mysqld.cancelWaitCmd = make(chan struct{}) go func(cancel <-chan struct{}) { // Wait regardless of cancel, so we don't generate defunct processes. err := cmd.Wait() log.Infof("%v exit: %v", ts, err) // The process exited. Trigger OnTerm callbacks, unless we were cancelled. select { case <-cancel: default: mysqld.mutex.Lock() for _, callback := range mysqld.onTermFuncs { go callback() } mysqld.mutex.Unlock() } }(mysqld.cancelWaitCmd) mysqld.mutex.Unlock() default: // hook failed, we report error return fmt.Errorf("mysqld_start hook failed: %v", hr.String()) } return mysqld.Wait(ctx) }
// Start will start the mysql daemon, either by running the 'mysqld_start' // hook, or by running mysqld_safe in the background. func (mysqld *Mysqld) Start(mysqlWaitTime time.Duration) error { var name string ts := fmt.Sprintf("Mysqld.Start(%v)", time.Now().Unix()) // try the mysqld start hook, if any switch hr := hook.NewSimpleHook("mysqld_start").Execute(); hr.ExitStatus { case hook.HOOK_SUCCESS: // hook exists and worked, we can keep going name = "mysqld_start hook" case hook.HOOK_DOES_NOT_EXIST: // hook doesn't exist, run mysqld_safe ourselves log.Infof("%v: No mysqld_start hook, running mysqld_safe directly", ts) dir, err := vtenv.VtMysqlRoot() if err != nil { return err } name = path.Join(dir, "bin/mysqld_safe") arg := []string{ "--defaults-file=" + mysqld.config.path} env := []string{os.ExpandEnv("LD_LIBRARY_PATH=$VT_MYSQL_ROOT/lib/mysql")} cmd := exec.Command(name, arg...) cmd.Dir = dir cmd.Env = env log.Infof("%v mysqlWaitTime:%v %#v", ts, mysqlWaitTime, cmd) stderr, err := cmd.StderrPipe() if err != nil { return nil } stdout, err := cmd.StdoutPipe() if err != nil { return nil } go func() { scanner := bufio.NewScanner(stderr) for scanner.Scan() { log.Infof("%v stderr: %v", ts, scanner.Text()) } }() go func() { scanner := bufio.NewScanner(stdout) for scanner.Scan() { log.Infof("%v stdout: %v", ts, scanner.Text()) } }() err = cmd.Start() if err != nil { return nil } mysqld.done = make(chan struct{}) go func(done chan<- struct{}) { // wait so we don't get a bunch of defunct processes err := cmd.Wait() log.Infof("%v exit: %v", ts, err) close(done) }(mysqld.done) default: // hook failed, we report error return fmt.Errorf("mysqld_start hook failed: %v", hr.String()) } // give it some time to succeed - usually by the time the socket emerges // we are in good shape for i := mysqlWaitTime; i >= 0; i -= time.Second { _, statErr := os.Stat(mysqld.config.SocketFile) if statErr == nil { // Make sure the socket file isn't stale. conn, connErr := mysqld.dbaPool.Get(0) if connErr == nil { conn.Recycle() return nil } } else if !os.IsNotExist(statErr) { return statErr } log.Infof("%v: sleeping for 1s waiting for socket file %v", ts, mysqld.config.SocketFile) time.Sleep(time.Second) } return errors.New(name + ": deadline exceeded waiting for " + mysqld.config.SocketFile) }