vitess.io/vitess@v0.16.2/go/vt/mysqlctl/mysqld.go (about)

     1  /*
     2  Copyright 2019 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  /*
    18  Commands for controlling an external mysql process.
    19  
    20  Some commands are issued as exec'd tools, some are handled by connecting via
    21  the mysql protocol.
    22  */
    23  
    24  package mysqlctl
    25  
    26  import (
    27  	"bufio"
    28  	"bytes"
    29  	"context"
    30  	"errors"
    31  	"fmt"
    32  	"io"
    33  	"os"
    34  	"os/exec"
    35  	"path"
    36  	"path/filepath"
    37  	"regexp"
    38  	"strconv"
    39  	"strings"
    40  	"sync"
    41  	"time"
    42  
    43  	"github.com/spf13/pflag"
    44  
    45  	"vitess.io/vitess/config"
    46  	"vitess.io/vitess/go/mysql"
    47  	"vitess.io/vitess/go/vt/dbconfigs"
    48  	"vitess.io/vitess/go/vt/dbconnpool"
    49  	"vitess.io/vitess/go/vt/hook"
    50  	"vitess.io/vitess/go/vt/log"
    51  	"vitess.io/vitess/go/vt/mysqlctl/mysqlctlclient"
    52  	"vitess.io/vitess/go/vt/servenv"
    53  
    54  	vtenv "vitess.io/vitess/go/vt/env"
    55  )
    56  
    57  var (
    58  
    59  	// DisableActiveReparents is a flag to disable active
    60  	// reparents for safety reasons. It is used in three places:
    61  	// 1. in this file to skip registering the commands.
    62  	// 2. in vtctld so it can be exported to the UI (different
    63  	// package, that's why it's exported). That way we can disable
    64  	// menu items there, using features.
    65  	DisableActiveReparents bool
    66  
    67  	dbaPoolSize = 20
    68  	// DbaIdleTimeout is how often we will refresh the DBA connpool connections
    69  	DbaIdleTimeout = time.Minute
    70  	appPoolSize    = 40
    71  	appIdleTimeout = time.Minute
    72  
    73  	// PoolDynamicHostnameResolution is whether we should retry DNS resolution of hostname targets
    74  	// and reconnect if necessary
    75  	PoolDynamicHostnameResolution time.Duration
    76  
    77  	mycnfTemplateFile string
    78  	socketFile        string
    79  
    80  	replicationConnectRetry = 10 * time.Second
    81  
    82  	versionRegex = regexp.MustCompile(`Ver ([0-9]+)\.([0-9]+)\.([0-9]+)`)
    83  )
    84  
    85  // How many bytes from MySQL error log to sample for error messages
    86  const maxLogFileSampleSize = 4096
    87  
    88  // Mysqld is the object that represents a mysqld daemon running on this server.
    89  type Mysqld struct {
    90  	dbcfgs  *dbconfigs.DBConfigs
    91  	dbaPool *dbconnpool.ConnectionPool
    92  	appPool *dbconnpool.ConnectionPool
    93  
    94  	capabilities capabilitySet
    95  
    96  	// mutex protects the fields below.
    97  	mutex         sync.Mutex
    98  	onTermFuncs   []func()
    99  	cancelWaitCmd chan struct{}
   100  }
   101  
   102  func init() {
   103  	for _, cmd := range []string{"mysqlctl", "mysqlctld", "vtcombo", "vttablet", "vttestserver"} {
   104  		servenv.OnParseFor(cmd, registerMySQLDFlags)
   105  	}
   106  	for _, cmd := range []string{"vtcombo", "vttablet", "vttestserver", "vtctld", "vtctldclient"} {
   107  		servenv.OnParseFor(cmd, registerReparentFlags)
   108  	}
   109  	for _, cmd := range []string{"mysqlctl", "mysqlctld", "vtcombo", "vttablet", "vttestserver"} {
   110  		servenv.OnParseFor(cmd, registerPoolFlags)
   111  	}
   112  }
   113  
   114  func registerMySQLDFlags(fs *pflag.FlagSet) {
   115  	fs.DurationVar(&PoolDynamicHostnameResolution, "pool_hostname_resolve_interval", PoolDynamicHostnameResolution, "if set force an update to all hostnames and reconnect if changed, defaults to 0 (disabled)")
   116  	fs.StringVar(&mycnfTemplateFile, "mysqlctl_mycnf_template", mycnfTemplateFile, "template file to use for generating the my.cnf file during server init")
   117  	fs.StringVar(&socketFile, "mysqlctl_socket", socketFile, "socket file to use for remote mysqlctl actions (empty for local actions)")
   118  	fs.DurationVar(&replicationConnectRetry, "replication_connect_retry", replicationConnectRetry, "how long to wait in between replica reconnect attempts. Only precise to the second.")
   119  }
   120  
   121  func registerReparentFlags(fs *pflag.FlagSet) {
   122  	fs.BoolVar(&DisableActiveReparents, "disable_active_reparents", DisableActiveReparents, "if set, do not allow active reparents. Use this to protect a cluster using external reparents.")
   123  }
   124  
   125  func registerPoolFlags(fs *pflag.FlagSet) {
   126  	fs.IntVar(&dbaPoolSize, "dba_pool_size", dbaPoolSize, "Size of the connection pool for dba connections")
   127  	fs.DurationVar(&DbaIdleTimeout, "dba_idle_timeout", DbaIdleTimeout, "Idle timeout for dba connections")
   128  	fs.DurationVar(&appIdleTimeout, "app_idle_timeout", appIdleTimeout, "Idle timeout for app connections")
   129  	fs.IntVar(&appPoolSize, "app_pool_size", appPoolSize, "Size of the connection pool for app connections")
   130  }
   131  
   132  // NewMysqld creates a Mysqld object based on the provided configuration
   133  // and connection parameters.
   134  func NewMysqld(dbcfgs *dbconfigs.DBConfigs) *Mysqld {
   135  	result := &Mysqld{
   136  		dbcfgs: dbcfgs,
   137  	}
   138  
   139  	// Create and open the connection pool for dba access.
   140  	result.dbaPool = dbconnpool.NewConnectionPool("DbaConnPool", dbaPoolSize, DbaIdleTimeout, 0, PoolDynamicHostnameResolution)
   141  	result.dbaPool.Open(dbcfgs.DbaWithDB())
   142  
   143  	// Create and open the connection pool for app access.
   144  	result.appPool = dbconnpool.NewConnectionPool("AppConnPool", appPoolSize, appIdleTimeout, 0, PoolDynamicHostnameResolution)
   145  	result.appPool.Open(dbcfgs.AppWithDB())
   146  
   147  	/*
   148  	 Unmanaged tablets are special because the MYSQL_FLAVOR detection
   149  	 will not be accurate because the mysqld might not be the same
   150  	 one as the server started.
   151  
   152  	 This skips the panic that checks that we can detect a server,
   153  	 but also relies on none of the flavor detection features being
   154  	 used at runtime. Currently this assumption is guaranteed true.
   155  	*/
   156  	if dbconfigs.GlobalDBConfigs.HasGlobalSettings() {
   157  		log.Info("mysqld is unmanaged or remote. Skipping flavor detection")
   158  		return result
   159  	}
   160  	version, getErr := GetVersionString()
   161  	f, v, err := ParseVersionString(version)
   162  
   163  	/*
   164  	 By default Vitess searches in vtenv.VtMysqlRoot() for a mysqld binary.
   165  	 This is historically the VT_MYSQL_ROOT env, but if it is unset or empty,
   166  	 Vitess will search the PATH. See go/vt/env/env.go.
   167  
   168  	 A number of subdirs inside vtenv.VtMysqlRoot() will be searched, see
   169  	 func binaryPath() for context. If no mysqld binary is found (possibly
   170  	 because it is in a container or both VT_MYSQL_ROOT and VTROOT are set
   171  	 incorrectly), there will be a fallback to using the MYSQL_FLAVOR env
   172  	 variable.
   173  
   174  	 If MYSQL_FLAVOR is not defined, there will be a panic.
   175  
   176  	 Note: relying on MySQL_FLAVOR is not recommended, since for historical
   177  	 purposes "MySQL56" actually means MySQL 5.7, which is a very strange
   178  	 behavior.
   179  	*/
   180  
   181  	if getErr != nil || err != nil {
   182  		f, v, err = GetVersionFromEnv()
   183  		if err != nil {
   184  			vtenvMysqlRoot, _ := vtenv.VtMysqlRoot()
   185  			message := fmt.Sprintf(`could not auto-detect MySQL version. You may need to set your PATH so a mysqld binary can be found, or set the environment variable MYSQL_FLAVOR if mysqld is not available locally:
   186  	PATH: %s
   187  	VT_MYSQL_ROOT: %s
   188  	VTROOT: %s
   189  	vtenv.VtMysqlRoot(): %s
   190  	MYSQL_FLAVOR: %s
   191  	`,
   192  				os.Getenv("PATH"),
   193  				os.Getenv("VT_MYSQL_ROOT"),
   194  				os.Getenv("VTROOT"),
   195  				vtenvMysqlRoot,
   196  				os.Getenv("MYSQL_FLAVOR"))
   197  			panic(message)
   198  		}
   199  	}
   200  
   201  	log.Infof("Using flavor: %v, version: %v", f, v)
   202  	result.capabilities = newCapabilitySet(f, v)
   203  	return result
   204  }
   205  
   206  /*
   207  GetVersionFromEnv returns the flavor and an assumed version based on the legacy
   208  MYSQL_FLAVOR environment variable.
   209  
   210  The assumed version may not be accurate since the legacy variable only specifies
   211  broad families of compatible versions. However, the differences between those
   212  versions should only matter if Vitess is managing the lifecycle of mysqld, in which
   213  case we should have a local copy of the mysqld binary from which we can fetch
   214  the accurate version instead of falling back to this function (see GetVersionString).
   215  */
   216  func GetVersionFromEnv() (flavor MySQLFlavor, ver ServerVersion, err error) {
   217  	env := os.Getenv("MYSQL_FLAVOR")
   218  	switch env {
   219  	case "MariaDB":
   220  		return FlavorMariaDB, ServerVersion{10, 6, 11}, nil
   221  	case "MySQL80":
   222  		return FlavorMySQL, ServerVersion{8, 0, 11}, nil
   223  	case "MySQL56":
   224  		return FlavorMySQL, ServerVersion{5, 7, 10}, nil
   225  	}
   226  	return flavor, ver, fmt.Errorf("could not determine version from MYSQL_FLAVOR: %s", env)
   227  }
   228  
   229  // GetVersionString runs mysqld --version and returns its output as a string
   230  func GetVersionString() (string, error) {
   231  	mysqlRoot, err := vtenv.VtMysqlRoot()
   232  	if err != nil {
   233  		return "", err
   234  	}
   235  	mysqldPath, err := binaryPath(mysqlRoot, "mysqld")
   236  	if err != nil {
   237  		return "", err
   238  	}
   239  	_, version, err := execCmd(mysqldPath, []string{"--version"}, nil, mysqlRoot, nil)
   240  	if err != nil {
   241  		return "", err
   242  	}
   243  	return version, nil
   244  }
   245  
   246  // ParseVersionString parses the output of mysqld --version into a flavor and version
   247  func ParseVersionString(version string) (flavor MySQLFlavor, ver ServerVersion, err error) {
   248  	if strings.Contains(version, "Percona") {
   249  		flavor = FlavorPercona
   250  	} else if strings.Contains(version, "MariaDB") {
   251  		flavor = FlavorMariaDB
   252  	} else {
   253  		// OS distributed MySQL releases have a version string like:
   254  		// mysqld  Ver 5.7.27-0ubuntu0.19.04.1 for Linux on x86_64 ((Ubuntu))
   255  		flavor = FlavorMySQL
   256  	}
   257  	v := versionRegex.FindStringSubmatch(version)
   258  	if len(v) != 4 {
   259  		return flavor, ver, fmt.Errorf("could not parse server version from: %s", version)
   260  	}
   261  	ver.Major, err = strconv.Atoi(string(v[1]))
   262  	if err != nil {
   263  		return flavor, ver, fmt.Errorf("could not parse server version from: %s", version)
   264  	}
   265  	ver.Minor, err = strconv.Atoi(string(v[2]))
   266  	if err != nil {
   267  		return flavor, ver, fmt.Errorf("could not parse server version from: %s", version)
   268  	}
   269  	ver.Patch, err = strconv.Atoi(string(v[3]))
   270  	if err != nil {
   271  		return flavor, ver, fmt.Errorf("could not parse server version from: %s", version)
   272  	}
   273  
   274  	return
   275  }
   276  
   277  // RunMysqlUpgrade will run the mysql_upgrade program on the current
   278  // install.  Will be called only when mysqld is running with no
   279  // network and no grant tables.
   280  func (mysqld *Mysqld) RunMysqlUpgrade() error {
   281  	// Execute as remote action on mysqlctld if requested.
   282  	if socketFile != "" {
   283  		log.Infof("executing Mysqld.RunMysqlUpgrade() remotely via mysqlctld server: %v", socketFile)
   284  		client, err := mysqlctlclient.New("unix", socketFile)
   285  		if err != nil {
   286  			return fmt.Errorf("can't dial mysqlctld: %v", err)
   287  		}
   288  		defer client.Close()
   289  		return client.RunMysqlUpgrade(context.TODO())
   290  	}
   291  
   292  	if mysqld.capabilities.hasMySQLUpgradeInServer() {
   293  		log.Warningf("MySQL version has built-in upgrade, skipping RunMySQLUpgrade")
   294  		return nil
   295  	}
   296  
   297  	// Since we started mysql with --skip-grant-tables, we should
   298  	// be able to run mysql_upgrade without any valid user or
   299  	// password. However, mysql_upgrade executes a 'flush
   300  	// privileges' right in the middle, and then subsequent
   301  	// commands fail if we don't use valid credentials. So let's
   302  	// use dba credentials.
   303  	params, err := mysqld.dbcfgs.DbaConnector().MysqlParams()
   304  	if err != nil {
   305  		return err
   306  	}
   307  	defaultsFile, err := mysqld.defaultsExtraFile(params)
   308  	if err != nil {
   309  		return err
   310  	}
   311  	defer os.Remove(defaultsFile)
   312  
   313  	// Run the program, if it fails, we fail.  Note in this
   314  	// moment, mysqld is running with no grant tables on the local
   315  	// socket only, so this doesn't need any user or password.
   316  	args := []string{
   317  		// --defaults-file=* must be the first arg.
   318  		"--defaults-file=" + defaultsFile,
   319  		"--force", // Don't complain if it's already been upgraded.
   320  	}
   321  
   322  	// Find mysql_upgrade. If not there, we do nothing.
   323  	vtMysqlRoot, err := vtenv.VtMysqlRoot()
   324  	if err != nil {
   325  		log.Warningf("VT_MYSQL_ROOT not set, skipping mysql_upgrade step: %v", err)
   326  		return nil
   327  	}
   328  	name, err := binaryPath(vtMysqlRoot, "mysql_upgrade")
   329  	if err != nil {
   330  		log.Warningf("mysql_upgrade binary not present, skipping it: %v", err)
   331  		return nil
   332  	}
   333  
   334  	env, err := buildLdPaths()
   335  	if err != nil {
   336  		log.Warningf("skipping mysql_upgrade step: %v", err)
   337  		return nil
   338  	}
   339  
   340  	_, _, err = execCmd(name, args, env, "", nil)
   341  	return err
   342  }
   343  
   344  // Start will start the mysql daemon, either by running the
   345  // 'mysqld_start' hook, or by running mysqld_safe in the background.
   346  // If a mysqlctld address is provided in a flag, Start will run
   347  // remotely.  When waiting for mysqld to start, we will use
   348  // the dba user.
   349  func (mysqld *Mysqld) Start(ctx context.Context, cnf *Mycnf, mysqldArgs ...string) error {
   350  	// Execute as remote action on mysqlctld if requested.
   351  	if socketFile != "" {
   352  		log.Infof("executing Mysqld.Start() remotely via mysqlctld server: %v", socketFile)
   353  		client, err := mysqlctlclient.New("unix", socketFile)
   354  		if err != nil {
   355  			return fmt.Errorf("can't dial mysqlctld: %v", err)
   356  		}
   357  		defer client.Close()
   358  		return client.Start(ctx, mysqldArgs...)
   359  	}
   360  
   361  	if err := mysqld.startNoWait(ctx, cnf, mysqldArgs...); err != nil {
   362  		return err
   363  	}
   364  
   365  	return mysqld.Wait(ctx, cnf)
   366  }
   367  
   368  // startNoWait is the internal version of Start, and it doesn't wait.
   369  func (mysqld *Mysqld) startNoWait(ctx context.Context, cnf *Mycnf, mysqldArgs ...string) error {
   370  	var name string
   371  	ts := fmt.Sprintf("Mysqld.Start(%v)", time.Now().Unix())
   372  
   373  	// try the mysqld start hook, if any
   374  	switch hr := hook.NewHook("mysqld_start", mysqldArgs).Execute(); hr.ExitStatus {
   375  	case hook.HOOK_SUCCESS:
   376  		// hook exists and worked, we can keep going
   377  		name = "mysqld_start hook" // nolint
   378  	case hook.HOOK_DOES_NOT_EXIST:
   379  		// hook doesn't exist, run mysqld_safe ourselves
   380  		log.Infof("%v: No mysqld_start hook, running mysqld_safe directly", ts)
   381  		vtMysqlRoot, err := vtenv.VtMysqlRoot()
   382  		if err != nil {
   383  			return err
   384  		}
   385  		name, err = binaryPath(vtMysqlRoot, "mysqld_safe")
   386  		if err != nil {
   387  			// The movement to use systemd means that mysqld_safe is not always provided.
   388  			// This should not be considered an issue do not generate a warning.
   389  			log.Infof("%v: trying to launch mysqld instead", err)
   390  			name, err = binaryPath(vtMysqlRoot, "mysqld")
   391  			// If this also fails, return an error.
   392  			if err != nil {
   393  				return err
   394  			}
   395  		}
   396  		mysqlBaseDir, err := vtenv.VtMysqlBaseDir()
   397  		if err != nil {
   398  			return err
   399  		}
   400  		args := []string{
   401  			"--defaults-file=" + cnf.Path,
   402  			"--basedir=" + mysqlBaseDir,
   403  		}
   404  		args = append(args, mysqldArgs...)
   405  		env, err := buildLdPaths()
   406  		if err != nil {
   407  			return err
   408  		}
   409  
   410  		cmd := exec.Command(name, args...)
   411  		cmd.Dir = vtMysqlRoot
   412  		cmd.Env = env
   413  		log.Infof("%v %#v", ts, cmd)
   414  		stderr, err := cmd.StderrPipe()
   415  		if err != nil {
   416  			return err
   417  		}
   418  		stdout, err := cmd.StdoutPipe()
   419  		if err != nil {
   420  			return err
   421  		}
   422  		go func() {
   423  			scanner := bufio.NewScanner(stderr)
   424  			for scanner.Scan() {
   425  				log.Infof("%v stderr: %v", ts, scanner.Text())
   426  			}
   427  		}()
   428  		go func() {
   429  			scanner := bufio.NewScanner(stdout)
   430  			for scanner.Scan() {
   431  				log.Infof("%v stdout: %v", ts, scanner.Text())
   432  			}
   433  		}()
   434  		err = cmd.Start()
   435  		if err != nil {
   436  			return err
   437  		}
   438  
   439  		mysqld.mutex.Lock()
   440  		mysqld.cancelWaitCmd = make(chan struct{})
   441  		go func(cancel <-chan struct{}) {
   442  			// Wait regardless of cancel, so we don't generate defunct processes.
   443  			err := cmd.Wait()
   444  			log.Infof("%v exit: %v", ts, err)
   445  
   446  			// The process exited. Trigger OnTerm callbacks, unless we were cancelled.
   447  			select {
   448  			case <-cancel:
   449  			default:
   450  				mysqld.mutex.Lock()
   451  				for _, callback := range mysqld.onTermFuncs {
   452  					go callback()
   453  				}
   454  				mysqld.mutex.Unlock()
   455  			}
   456  		}(mysqld.cancelWaitCmd)
   457  		mysqld.mutex.Unlock()
   458  	default:
   459  		// hook failed, we report error
   460  		return fmt.Errorf("mysqld_start hook failed: %v", hr.String())
   461  	}
   462  
   463  	return nil
   464  }
   465  
   466  // Wait returns nil when mysqld is up and accepting connections. It
   467  // will use the dba credentials to try to connect. Use wait() with
   468  // different credentials if needed.
   469  func (mysqld *Mysqld) Wait(ctx context.Context, cnf *Mycnf) error {
   470  	params, err := mysqld.dbcfgs.DbaConnector().MysqlParams()
   471  	if err != nil {
   472  		return err
   473  	}
   474  
   475  	return mysqld.wait(ctx, cnf, params)
   476  }
   477  
   478  // wait is the internal version of Wait, that takes credentials.
   479  func (mysqld *Mysqld) wait(ctx context.Context, cnf *Mycnf, params *mysql.ConnParams) error {
   480  	log.Infof("Waiting for mysqld socket file (%v) to be ready...", cnf.SocketFile)
   481  
   482  	for {
   483  		select {
   484  		case <-ctx.Done():
   485  			return errors.New("deadline exceeded waiting for mysqld socket file to appear: " + cnf.SocketFile)
   486  		default:
   487  		}
   488  
   489  		_, statErr := os.Stat(cnf.SocketFile)
   490  		if statErr == nil {
   491  			// Make sure the socket file isn't stale.
   492  			conn, connErr := mysql.Connect(ctx, params)
   493  			if connErr == nil {
   494  				conn.Close()
   495  				return nil
   496  			}
   497  			log.Infof("mysqld socket file exists, but can't connect: %v", connErr)
   498  		} else if !os.IsNotExist(statErr) {
   499  			return fmt.Errorf("can't stat mysqld socket file: %v", statErr)
   500  		}
   501  		time.Sleep(1000 * time.Millisecond)
   502  	}
   503  }
   504  
   505  // Shutdown will stop the mysqld daemon that is running in the background.
   506  //
   507  // waitForMysqld: should the function block until mysqld has stopped?
   508  // This can actually take a *long* time if the buffer cache needs to be fully
   509  // flushed - on the order of 20-30 minutes.
   510  //
   511  // If a mysqlctld address is provided in a flag, Shutdown will run remotely.
   512  func (mysqld *Mysqld) Shutdown(ctx context.Context, cnf *Mycnf, waitForMysqld bool) error {
   513  	log.Infof("Mysqld.Shutdown")
   514  
   515  	// Execute as remote action on mysqlctld if requested.
   516  	if socketFile != "" {
   517  		log.Infof("executing Mysqld.Shutdown() remotely via mysqlctld server: %v", socketFile)
   518  		client, err := mysqlctlclient.New("unix", socketFile)
   519  		if err != nil {
   520  			return fmt.Errorf("can't dial mysqlctld: %v", err)
   521  		}
   522  		defer client.Close()
   523  		return client.Shutdown(ctx, waitForMysqld)
   524  	}
   525  
   526  	// We're shutting down on purpose. We no longer want to be notified when
   527  	// mysqld terminates.
   528  	mysqld.mutex.Lock()
   529  	if mysqld.cancelWaitCmd != nil {
   530  		close(mysqld.cancelWaitCmd)
   531  		mysqld.cancelWaitCmd = nil
   532  	}
   533  	mysqld.mutex.Unlock()
   534  
   535  	// possibly mysql is already shutdown, check for a few files first
   536  	_, socketPathErr := os.Stat(cnf.SocketFile)
   537  	_, pidPathErr := os.Stat(cnf.PidFile)
   538  	if os.IsNotExist(socketPathErr) && os.IsNotExist(pidPathErr) {
   539  		log.Warningf("assuming mysqld already shut down - no socket, no pid file found")
   540  		return nil
   541  	}
   542  
   543  	// try the mysqld shutdown hook, if any
   544  	h := hook.NewSimpleHook("mysqld_shutdown")
   545  	hr := h.ExecuteContext(ctx)
   546  	switch hr.ExitStatus {
   547  	case hook.HOOK_SUCCESS:
   548  		// hook exists and worked, we can keep going
   549  	case hook.HOOK_DOES_NOT_EXIST:
   550  		// hook doesn't exist, try mysqladmin
   551  		log.Infof("No mysqld_shutdown hook, running mysqladmin directly")
   552  		dir, err := vtenv.VtMysqlRoot()
   553  		if err != nil {
   554  			return err
   555  		}
   556  		name, err := binaryPath(dir, "mysqladmin")
   557  		if err != nil {
   558  			return err
   559  		}
   560  		params, err := mysqld.dbcfgs.DbaConnector().MysqlParams()
   561  		if err != nil {
   562  			return err
   563  		}
   564  		cnf, err := mysqld.defaultsExtraFile(params)
   565  		if err != nil {
   566  			return err
   567  		}
   568  		defer os.Remove(cnf)
   569  		args := []string{
   570  			"--defaults-extra-file=" + cnf,
   571  			"--shutdown-timeout=300",
   572  			"--connect-timeout=30",
   573  			"--wait=10",
   574  			"shutdown",
   575  		}
   576  		env, err := buildLdPaths()
   577  		if err != nil {
   578  			return err
   579  		}
   580  		if _, _, err = execCmd(name, args, env, dir, nil); err != nil {
   581  			return err
   582  		}
   583  	default:
   584  		// hook failed, we report error
   585  		return fmt.Errorf("mysqld_shutdown hook failed: %v", hr.String())
   586  	}
   587  
   588  	// Wait for mysqld to really stop. Use the socket and pid files as a
   589  	// proxy for that since we can't call wait() in a process we
   590  	// didn't start.
   591  	if waitForMysqld {
   592  		log.Infof("Mysqld.Shutdown: waiting for socket file (%v) and pid file (%v) to disappear",
   593  			cnf.SocketFile, cnf.PidFile)
   594  
   595  		for {
   596  			select {
   597  			case <-ctx.Done():
   598  				return errors.New("gave up waiting for mysqld to stop")
   599  			default:
   600  			}
   601  
   602  			_, socketPathErr = os.Stat(cnf.SocketFile)
   603  			_, pidPathErr = os.Stat(cnf.PidFile)
   604  			if os.IsNotExist(socketPathErr) && os.IsNotExist(pidPathErr) {
   605  				return nil
   606  			}
   607  			time.Sleep(100 * time.Millisecond)
   608  		}
   609  	}
   610  	return nil
   611  }
   612  
   613  // execCmd searches the PATH for a command and runs it, logging the output.
   614  // If input is not nil, pipe it to the command's stdin.
   615  func execCmd(name string, args, env []string, dir string, input io.Reader) (cmd *exec.Cmd, output string, err error) {
   616  	cmdPath, _ := exec.LookPath(name)
   617  	log.Infof("execCmd: %v %v %v", name, cmdPath, args)
   618  
   619  	cmd = exec.Command(cmdPath, args...)
   620  	cmd.Env = env
   621  	cmd.Dir = dir
   622  	if input != nil {
   623  		cmd.Stdin = input
   624  	}
   625  	out, err := cmd.CombinedOutput()
   626  	output = string(out)
   627  	if err != nil {
   628  		log.Infof("execCmd: %v failed: %v", name, err)
   629  		err = fmt.Errorf("%v: %v, output: %v", name, err, output)
   630  	}
   631  	log.Infof("execCmd: %v output: %v", name, output)
   632  	return cmd, output, err
   633  }
   634  
   635  // binaryPath does a limited path lookup for a command,
   636  // searching only within sbin and bin in the given root.
   637  func binaryPath(root, binary string) (string, error) {
   638  	subdirs := []string{"sbin", "bin", "libexec", "scripts"}
   639  	for _, subdir := range subdirs {
   640  		binPath := path.Join(root, subdir, binary)
   641  		if _, err := os.Stat(binPath); err == nil {
   642  			return binPath, nil
   643  		}
   644  	}
   645  	return "", fmt.Errorf("%s not found in any of %s/{%s}",
   646  		binary, root, strings.Join(subdirs, ","))
   647  }
   648  
   649  // InitConfig will create the default directory structure for the mysqld process,
   650  // generate / configure a my.cnf file.
   651  func (mysqld *Mysqld) InitConfig(cnf *Mycnf) error {
   652  	log.Infof("mysqlctl.InitConfig")
   653  	err := mysqld.createDirs(cnf)
   654  	if err != nil {
   655  		log.Errorf("%s", err.Error())
   656  		return err
   657  	}
   658  	// Set up config files.
   659  	if err = mysqld.initConfig(cnf, cnf.Path); err != nil {
   660  		log.Errorf("failed creating %v: %v", cnf.Path, err)
   661  		return err
   662  	}
   663  	return nil
   664  }
   665  
   666  // Init will create the default directory structure for the mysqld process,
   667  // generate / configure a my.cnf file install a skeleton database,
   668  // and apply the provided initial SQL file.
   669  func (mysqld *Mysqld) Init(ctx context.Context, cnf *Mycnf, initDBSQLFile string) error {
   670  	log.Infof("mysqlctl.Init")
   671  	err := mysqld.InitConfig(cnf)
   672  	if err != nil {
   673  		log.Errorf("%s", err.Error())
   674  		return err
   675  	}
   676  	// Install data dir.
   677  	if err = mysqld.installDataDir(cnf); err != nil {
   678  		return err
   679  	}
   680  
   681  	// Start mysqld. We do not use Start, as we have to wait using
   682  	// the root user.
   683  	if err = mysqld.startNoWait(ctx, cnf); err != nil {
   684  		log.Errorf("failed starting mysqld: %v\n%v", err, readTailOfMysqldErrorLog(cnf.ErrorLogPath))
   685  		return err
   686  	}
   687  
   688  	// Wait for mysqld to be ready, using root credentials, as no
   689  	// user is created yet.
   690  	params := &mysql.ConnParams{
   691  		Uname:      "root",
   692  		UnixSocket: cnf.SocketFile,
   693  	}
   694  	if err = mysqld.wait(ctx, cnf, params); err != nil {
   695  		log.Errorf("failed starting mysqld in time: %v\n%v", err, readTailOfMysqldErrorLog(cnf.ErrorLogPath))
   696  		return err
   697  	}
   698  
   699  	if initDBSQLFile == "" { // default to built-in
   700  		if err := mysqld.executeMysqlScript(params, strings.NewReader(config.DefaultInitDB)); err != nil {
   701  			return fmt.Errorf("failed to initialize mysqld: %v", err)
   702  		}
   703  		return nil
   704  	}
   705  
   706  	// else, user specified an init db file
   707  	sqlFile, err := os.Open(initDBSQLFile)
   708  	if err != nil {
   709  		return fmt.Errorf("can't open init_db_sql_file (%v): %v", initDBSQLFile, err)
   710  	}
   711  	defer sqlFile.Close()
   712  	if err := mysqld.executeMysqlScript(params, sqlFile); err != nil {
   713  		return fmt.Errorf("can't run init_db_sql_file (%v): %v", initDBSQLFile, err)
   714  	}
   715  	return nil
   716  }
   717  
   718  // For debugging purposes show the last few lines of the MySQL error log.
   719  // Return a suggestion (string) if the file is non regular or can not be opened.
   720  // This helps prevent cases where the error log is symlinked to /dev/stderr etc,
   721  // In which case the user can manually open the file.
   722  func readTailOfMysqldErrorLog(fileName string) string {
   723  	fileInfo, err := os.Stat(fileName)
   724  	if err != nil {
   725  		return fmt.Sprintf("could not stat mysql error log (%v): %v", fileName, err)
   726  	}
   727  	if !fileInfo.Mode().IsRegular() {
   728  		return fmt.Sprintf("mysql error log file is not a regular file: %v", fileName)
   729  	}
   730  	file, err := os.Open(fileName)
   731  	if err != nil {
   732  		return fmt.Sprintf("could not open mysql error log (%v): %v", fileName, err)
   733  	}
   734  	defer file.Close()
   735  	startPos := int64(0)
   736  	if fileInfo.Size() > maxLogFileSampleSize {
   737  		startPos = fileInfo.Size() - maxLogFileSampleSize
   738  	}
   739  	// Show the last few KB of the MySQL error log.
   740  	buf := make([]byte, maxLogFileSampleSize)
   741  	flen, err := file.ReadAt(buf, startPos)
   742  	if err != nil && err != io.EOF {
   743  		return fmt.Sprintf("could not read mysql error log (%v): %v", fileName, err)
   744  	}
   745  	return fmt.Sprintf("tail of mysql error log (%v):\n%s", fileName, buf[:flen])
   746  }
   747  
   748  func (mysqld *Mysqld) installDataDir(cnf *Mycnf) error {
   749  	mysqlRoot, err := vtenv.VtMysqlRoot()
   750  	if err != nil {
   751  		return err
   752  	}
   753  	mysqldPath, err := binaryPath(mysqlRoot, "mysqld")
   754  	if err != nil {
   755  		return err
   756  	}
   757  
   758  	mysqlBaseDir, err := vtenv.VtMysqlBaseDir()
   759  	if err != nil {
   760  		return err
   761  	}
   762  	if mysqld.capabilities.hasInitializeInServer() {
   763  		log.Infof("Installing data dir with mysqld --initialize-insecure")
   764  		args := []string{
   765  			"--defaults-file=" + cnf.Path,
   766  			"--basedir=" + mysqlBaseDir,
   767  			"--initialize-insecure", // Use empty 'root'@'localhost' password.
   768  		}
   769  		if _, _, err = execCmd(mysqldPath, args, nil, mysqlRoot, nil); err != nil {
   770  			log.Errorf("mysqld --initialize-insecure failed: %v\n%v", err, readTailOfMysqldErrorLog(cnf.ErrorLogPath))
   771  			return err
   772  		}
   773  		return nil
   774  	}
   775  
   776  	log.Infof("Installing data dir with mysql_install_db")
   777  	args := []string{
   778  		"--defaults-file=" + cnf.Path,
   779  		"--basedir=" + mysqlBaseDir,
   780  	}
   781  	if mysqld.capabilities.hasMaria104InstallDb() {
   782  		args = append(args, "--auth-root-authentication-method=normal")
   783  	}
   784  	cmdPath, err := binaryPath(mysqlRoot, "mysql_install_db")
   785  	if err != nil {
   786  		return err
   787  	}
   788  	if _, _, err = execCmd(cmdPath, args, nil, mysqlRoot, nil); err != nil {
   789  		log.Errorf("mysql_install_db failed: %v\n%v", err, readTailOfMysqldErrorLog(cnf.ErrorLogPath))
   790  		return err
   791  	}
   792  	return nil
   793  }
   794  
   795  func (mysqld *Mysqld) initConfig(cnf *Mycnf, outFile string) error {
   796  	var err error
   797  	var configData string
   798  
   799  	env := make(map[string]string)
   800  	envVars := []string{"KEYSPACE", "SHARD", "TABLET_TYPE", "TABLET_ID", "TABLET_DIR", "MYSQL_PORT"}
   801  	for _, v := range envVars {
   802  		env[v] = os.Getenv(v)
   803  	}
   804  
   805  	switch hr := hook.NewHookWithEnv("make_mycnf", nil, env).Execute(); hr.ExitStatus {
   806  	case hook.HOOK_DOES_NOT_EXIST:
   807  		log.Infof("make_mycnf hook doesn't exist, reading template files")
   808  		configData, err = cnf.makeMycnf(mysqld.getMycnfTemplate())
   809  	case hook.HOOK_SUCCESS:
   810  		configData, err = cnf.fillMycnfTemplate(hr.Stdout)
   811  	default:
   812  		return fmt.Errorf("make_mycnf hook failed(%v): %v", hr.ExitStatus, hr.Stderr)
   813  	}
   814  	if err != nil {
   815  		return err
   816  	}
   817  
   818  	return os.WriteFile(outFile, []byte(configData), 0664)
   819  }
   820  
   821  func (mysqld *Mysqld) getMycnfTemplate() string {
   822  	if mycnfTemplateFile != "" {
   823  		data, err := os.ReadFile(mycnfTemplateFile)
   824  		if err != nil {
   825  			log.Fatalf("template file specified by -mysqlctl_mycnf_template could not be read: %v", mycnfTemplateFile)
   826  		}
   827  		return string(data) // use only specified template
   828  	}
   829  	myTemplateSource := new(bytes.Buffer)
   830  	myTemplateSource.WriteString("[mysqld]\n")
   831  	myTemplateSource.WriteString(config.MycnfDefault)
   832  
   833  	// database flavor + version specific file.
   834  	// {flavor}{major}{minor}.cnf
   835  	f := FlavorMariaDB
   836  	if mysqld.capabilities.isMySQLLike() {
   837  		f = FlavorMySQL
   838  	}
   839  	var versionConfig string
   840  	switch f {
   841  	case FlavorPercona, FlavorMySQL:
   842  		switch mysqld.capabilities.version.Major {
   843  		case 5:
   844  			if mysqld.capabilities.version.Minor == 7 {
   845  				versionConfig = config.MycnfMySQL57
   846  			} else {
   847  				log.Infof("this version of Vitess does not include built-in support for %v %v", mysqld.capabilities.flavor, mysqld.capabilities.version)
   848  			}
   849  		case 8:
   850  			versionConfig = config.MycnfMySQL80
   851  		default:
   852  			log.Infof("this version of Vitess does not include built-in support for %v %v", mysqld.capabilities.flavor, mysqld.capabilities.version)
   853  		}
   854  	case FlavorMariaDB:
   855  		switch mysqld.capabilities.version.Major {
   856  		case 10:
   857  			versionConfig = config.MycnfMariaDB10
   858  		default:
   859  			log.Infof("this version of Vitess does not include built-in support for %v %v", mysqld.capabilities.flavor, mysqld.capabilities.version)
   860  		}
   861  	}
   862  
   863  	myTemplateSource.WriteString(versionConfig)
   864  
   865  	if extraCnf := os.Getenv("EXTRA_MY_CNF"); extraCnf != "" {
   866  		parts := strings.Split(extraCnf, ":")
   867  		for _, path := range parts {
   868  			data, dataErr := os.ReadFile(path)
   869  			if dataErr != nil {
   870  				log.Infof("could not open config file for mycnf: %v", path)
   871  				continue
   872  			}
   873  			myTemplateSource.WriteString("## " + path + "\n")
   874  			myTemplateSource.Write(data)
   875  		}
   876  	}
   877  	return myTemplateSource.String()
   878  }
   879  
   880  // RefreshConfig attempts to recreate the my.cnf from templates, and log and
   881  // swap in to place if it's updated. It keeps a copy of the last version in case fallback is required.
   882  // Should be called from a stable replica, server_id is not regenerated.
   883  func (mysqld *Mysqld) RefreshConfig(ctx context.Context, cnf *Mycnf) error {
   884  	// Execute as remote action on mysqlctld if requested.
   885  	if socketFile != "" {
   886  		log.Infof("executing Mysqld.RefreshConfig() remotely via mysqlctld server: %v", socketFile)
   887  		client, err := mysqlctlclient.New("unix", socketFile)
   888  		if err != nil {
   889  			return fmt.Errorf("can't dial mysqlctld: %v", err)
   890  		}
   891  		defer client.Close()
   892  		return client.RefreshConfig(ctx)
   893  	}
   894  
   895  	log.Info("Checking for updates to my.cnf")
   896  	f, err := os.CreateTemp(path.Dir(cnf.Path), "my.cnf")
   897  	if err != nil {
   898  		return fmt.Errorf("could not create temp file: %v", err)
   899  	}
   900  
   901  	defer os.Remove(f.Name())
   902  	err = mysqld.initConfig(cnf, f.Name())
   903  	if err != nil {
   904  		return fmt.Errorf("could not initConfig in %v: %v", f.Name(), err)
   905  	}
   906  
   907  	existing, err := os.ReadFile(cnf.Path)
   908  	if err != nil {
   909  		return fmt.Errorf("could not read existing file %v: %v", cnf.Path, err)
   910  	}
   911  	updated, err := os.ReadFile(f.Name())
   912  	if err != nil {
   913  		return fmt.Errorf("could not read updated file %v: %v", f.Name(), err)
   914  	}
   915  
   916  	if bytes.Equal(existing, updated) {
   917  		log.Infof("No changes to my.cnf. Continuing.")
   918  		return nil
   919  	}
   920  
   921  	backupPath := cnf.Path + ".previous"
   922  	err = os.Rename(cnf.Path, backupPath)
   923  	if err != nil {
   924  		return fmt.Errorf("could not back up existing %v: %v", cnf.Path, err)
   925  	}
   926  	err = os.Rename(f.Name(), cnf.Path)
   927  	if err != nil {
   928  		return fmt.Errorf("could not move %v to %v: %v", f.Name(), cnf.Path, err)
   929  	}
   930  	log.Infof("Updated my.cnf. Backup of previous version available in %v", backupPath)
   931  
   932  	return nil
   933  }
   934  
   935  // ReinitConfig updates the config file as if Mysqld is initializing. At the
   936  // moment it only randomizes ServerID because it's not safe to restore a replica
   937  // from a backup and then give it the same ServerID as before, MySQL can then
   938  // skip transactions in the replication stream with the same server_id.
   939  func (mysqld *Mysqld) ReinitConfig(ctx context.Context, cnf *Mycnf) error {
   940  	log.Infof("Mysqld.ReinitConfig")
   941  
   942  	// Execute as remote action on mysqlctld if requested.
   943  	if socketFile != "" {
   944  		log.Infof("executing Mysqld.ReinitConfig() remotely via mysqlctld server: %v", socketFile)
   945  		client, err := mysqlctlclient.New("unix", socketFile)
   946  		if err != nil {
   947  			return fmt.Errorf("can't dial mysqlctld: %v", err)
   948  		}
   949  		defer client.Close()
   950  		return client.ReinitConfig(ctx)
   951  	}
   952  
   953  	if err := cnf.RandomizeMysqlServerID(); err != nil {
   954  		return err
   955  	}
   956  	return mysqld.initConfig(cnf, cnf.Path)
   957  }
   958  
   959  func (mysqld *Mysqld) createDirs(cnf *Mycnf) error {
   960  	tabletDir := cnf.TabletDir()
   961  	log.Infof("creating directory %s", tabletDir)
   962  	if err := os.MkdirAll(tabletDir, os.ModePerm); err != nil {
   963  		return err
   964  	}
   965  	for _, dir := range TopLevelDirs() {
   966  		if err := mysqld.createTopDir(cnf, dir); err != nil {
   967  			return err
   968  		}
   969  	}
   970  	for _, dir := range cnf.directoryList() {
   971  		log.Infof("creating directory %s", dir)
   972  		if err := os.MkdirAll(dir, os.ModePerm); err != nil {
   973  			return err
   974  		}
   975  		// FIXME(msolomon) validate permissions?
   976  	}
   977  	return nil
   978  }
   979  
   980  // createTopDir creates a top level directory under TabletDir.
   981  // However, if a directory of the same name already exists under
   982  // vtenv.VtDataRoot(), it creates a directory named after the tablet
   983  // id under that directory, and then creates a symlink under TabletDir
   984  // that points to the newly created directory.  For example, if
   985  // /vt/data is present, it will create the following structure:
   986  // /vt/data/vt_xxxx /vt/vt_xxxx/data -> /vt/data/vt_xxxx
   987  func (mysqld *Mysqld) createTopDir(cnf *Mycnf, dir string) error {
   988  	tabletDir := cnf.TabletDir()
   989  	vtname := path.Base(tabletDir)
   990  	target := path.Join(vtenv.VtDataRoot(), dir)
   991  	_, err := os.Lstat(target)
   992  	if err != nil {
   993  		if os.IsNotExist(err) {
   994  			topdir := path.Join(tabletDir, dir)
   995  			log.Infof("creating directory %s", topdir)
   996  			return os.MkdirAll(topdir, os.ModePerm)
   997  		}
   998  		return err
   999  	}
  1000  	linkto := path.Join(target, vtname)
  1001  	source := path.Join(tabletDir, dir)
  1002  	log.Infof("creating directory %s", linkto)
  1003  	err = os.MkdirAll(linkto, os.ModePerm)
  1004  	if err != nil {
  1005  		return err
  1006  	}
  1007  	log.Infof("creating symlink %s -> %s", source, linkto)
  1008  	return os.Symlink(linkto, source)
  1009  }
  1010  
  1011  // Teardown will shutdown the running daemon, and delete the root directory.
  1012  func (mysqld *Mysqld) Teardown(ctx context.Context, cnf *Mycnf, force bool) error {
  1013  	log.Infof("mysqlctl.Teardown")
  1014  	if err := mysqld.Shutdown(ctx, cnf, true); err != nil {
  1015  		log.Warningf("failed mysqld shutdown: %v", err.Error())
  1016  		if !force {
  1017  			return err
  1018  		}
  1019  	}
  1020  	var removalErr error
  1021  	for _, dir := range TopLevelDirs() {
  1022  		qdir := path.Join(cnf.TabletDir(), dir)
  1023  		if err := deleteTopDir(qdir); err != nil {
  1024  			removalErr = err
  1025  		}
  1026  	}
  1027  	return removalErr
  1028  }
  1029  
  1030  func deleteTopDir(dir string) (removalErr error) {
  1031  	fi, err := os.Lstat(dir)
  1032  	if err != nil {
  1033  		log.Errorf("error deleting dir %v: %v", dir, err.Error())
  1034  		removalErr = err
  1035  	} else if fi.Mode()&os.ModeSymlink != 0 {
  1036  		target, err := filepath.EvalSymlinks(dir)
  1037  		if err != nil {
  1038  			log.Errorf("could not resolve symlink %v: %v", dir, err.Error())
  1039  			removalErr = err
  1040  		}
  1041  		log.Infof("remove data dir (symlinked) %v", target)
  1042  		if err = os.RemoveAll(target); err != nil {
  1043  			log.Errorf("failed removing %v: %v", target, err.Error())
  1044  			removalErr = err
  1045  		}
  1046  	}
  1047  	log.Infof("remove data dir %v", dir)
  1048  	if err = os.RemoveAll(dir); err != nil {
  1049  		log.Errorf("failed removing %v: %v", dir, err.Error())
  1050  		removalErr = err
  1051  	}
  1052  	return
  1053  }
  1054  
  1055  // executeMysqlScript executes a .sql script from an io.Reader with the mysql
  1056  // command line tool. It uses the connParams as is, not adding credentials.
  1057  func (mysqld *Mysqld) executeMysqlScript(connParams *mysql.ConnParams, sql io.Reader) error {
  1058  	dir, err := vtenv.VtMysqlRoot()
  1059  	if err != nil {
  1060  		return err
  1061  	}
  1062  	name, err := binaryPath(dir, "mysql")
  1063  	if err != nil {
  1064  		return err
  1065  	}
  1066  	cnf, err := mysqld.defaultsExtraFile(connParams)
  1067  	if err != nil {
  1068  		return err
  1069  	}
  1070  	defer os.Remove(cnf)
  1071  	args := []string{
  1072  		"--defaults-extra-file=" + cnf,
  1073  		"--batch",
  1074  	}
  1075  	env, err := buildLdPaths()
  1076  	if err != nil {
  1077  		return err
  1078  	}
  1079  	_, _, err = execCmd(name, args, env, dir, sql)
  1080  	if err != nil {
  1081  		return err
  1082  	}
  1083  	return nil
  1084  }
  1085  
  1086  // defaultsExtraFile returns the filename for a temporary config file
  1087  // that contains the user, password and socket file to connect to
  1088  // mysqld.  We write a temporary config file so the password is never
  1089  // passed as a command line parameter.  Note os.CreateTemp uses 0600
  1090  // as permissions, so only the local user can read the file.  The
  1091  // returned temporary file should be removed after use, typically in a
  1092  // 'defer os.Remove()' statement.
  1093  func (mysqld *Mysqld) defaultsExtraFile(connParams *mysql.ConnParams) (string, error) {
  1094  	var contents string
  1095  	connParams.Pass = strings.Replace(connParams.Pass, "#", "\\#", -1)
  1096  	if connParams.UnixSocket == "" {
  1097  		contents = fmt.Sprintf(`
  1098  [client]
  1099  user=%v
  1100  password=%v
  1101  host=%v
  1102  port=%v
  1103  `, connParams.Uname, connParams.Pass, connParams.Host, connParams.Port)
  1104  	} else {
  1105  		contents = fmt.Sprintf(`
  1106  [client]
  1107  user=%v
  1108  password=%v
  1109  socket=%v
  1110  `, connParams.Uname, connParams.Pass, connParams.UnixSocket)
  1111  	}
  1112  
  1113  	tmpfile, err := os.CreateTemp("", "example")
  1114  	if err != nil {
  1115  		return "", err
  1116  	}
  1117  	name := tmpfile.Name()
  1118  	if _, err := tmpfile.Write([]byte(contents)); err != nil {
  1119  		tmpfile.Close()
  1120  		os.Remove(name)
  1121  		return "", err
  1122  	}
  1123  	if err := tmpfile.Close(); err != nil {
  1124  		os.Remove(name)
  1125  		return "", err
  1126  	}
  1127  	return name, nil
  1128  }
  1129  
  1130  // GetAppConnection returns a connection from the app pool.
  1131  // Recycle needs to be called on the result.
  1132  func (mysqld *Mysqld) GetAppConnection(ctx context.Context) (*dbconnpool.PooledDBConnection, error) {
  1133  	return mysqld.appPool.Get(ctx)
  1134  }
  1135  
  1136  // GetDbaConnection creates a new DBConnection.
  1137  func (mysqld *Mysqld) GetDbaConnection(ctx context.Context) (*dbconnpool.DBConnection, error) {
  1138  	return dbconnpool.NewDBConnection(ctx, mysqld.dbcfgs.DbaConnector())
  1139  }
  1140  
  1141  // GetAllPrivsConnection creates a new DBConnection.
  1142  func (mysqld *Mysqld) GetAllPrivsConnection(ctx context.Context) (*dbconnpool.DBConnection, error) {
  1143  	return dbconnpool.NewDBConnection(ctx, mysqld.dbcfgs.AllPrivsWithDB())
  1144  }
  1145  
  1146  // Close will close this instance of Mysqld. It will wait for all dba
  1147  // queries to be finished.
  1148  func (mysqld *Mysqld) Close() {
  1149  	if mysqld.dbaPool != nil {
  1150  		mysqld.dbaPool.Close()
  1151  	}
  1152  	if mysqld.appPool != nil {
  1153  		mysqld.appPool.Close()
  1154  	}
  1155  }
  1156  
  1157  // OnTerm registers a function to be called if mysqld terminates for any
  1158  // reason other than a call to Mysqld.Shutdown(). This only works if mysqld
  1159  // was actually started by calling Start() on this Mysqld instance.
  1160  func (mysqld *Mysqld) OnTerm(f func()) {
  1161  	mysqld.mutex.Lock()
  1162  	defer mysqld.mutex.Unlock()
  1163  	mysqld.onTermFuncs = append(mysqld.onTermFuncs, f)
  1164  }
  1165  
  1166  func buildLdPaths() ([]string, error) {
  1167  	vtMysqlRoot, err := vtenv.VtMysqlRoot()
  1168  	if err != nil {
  1169  		return []string{}, err
  1170  	}
  1171  
  1172  	ldPaths := []string{
  1173  		fmt.Sprintf("LD_LIBRARY_PATH=%s/lib/mysql", vtMysqlRoot),
  1174  		os.ExpandEnv("LD_PRELOAD=$LD_PRELOAD"),
  1175  	}
  1176  
  1177  	return ldPaths, nil
  1178  }
  1179  
  1180  // GetVersionString is part of the MysqlDeamon interface.
  1181  func (mysqld *Mysqld) GetVersionString() string {
  1182  	return fmt.Sprintf("%d.%d.%d", mysqld.capabilities.version.Major, mysqld.capabilities.version.Minor, mysqld.capabilities.version.Patch)
  1183  }
  1184  
  1185  // GetVersionComment gets the version comment.
  1186  func (mysqld *Mysqld) GetVersionComment(ctx context.Context) string {
  1187  	qr, err := mysqld.FetchSuperQuery(ctx, "select @@global.version_comment")
  1188  	if err != nil {
  1189  		return ""
  1190  	}
  1191  	if len(qr.Rows) != 1 {
  1192  		return ""
  1193  	}
  1194  	res := qr.Named().Row()
  1195  	versionComment, _ := res.ToString("@@global.version_comment")
  1196  	return versionComment
  1197  }
  1198  
  1199  // applyBinlogFile extracts a binary log file and applies it to MySQL. It is the equivalent of:
  1200  // $ mysqlbinlog --include-gtids binlog.file | mysql
  1201  func (mysqld *Mysqld) applyBinlogFile(binlogFile string, includeGTIDs mysql.GTIDSet) error {
  1202  	var pipe io.ReadCloser
  1203  	var mysqlbinlogCmd *exec.Cmd
  1204  	var mysqlCmd *exec.Cmd
  1205  
  1206  	dir, err := vtenv.VtMysqlRoot()
  1207  	if err != nil {
  1208  		return err
  1209  	}
  1210  	env, err := buildLdPaths()
  1211  	if err != nil {
  1212  		return err
  1213  	}
  1214  	{
  1215  		name, err := binaryPath(dir, "mysqlbinlog")
  1216  		if err != nil {
  1217  			return err
  1218  		}
  1219  		args := []string{}
  1220  		if gtids := includeGTIDs.String(); gtids != "" {
  1221  			args = append(args,
  1222  				"--include-gtids",
  1223  				gtids,
  1224  			)
  1225  		}
  1226  		args = append(args, binlogFile)
  1227  
  1228  		mysqlbinlogCmd = exec.Command(name, args...)
  1229  		mysqlbinlogCmd.Dir = dir
  1230  		mysqlbinlogCmd.Env = env
  1231  		log.Infof("applyBinlogFile: running %#v", mysqlbinlogCmd)
  1232  		pipe, err = mysqlbinlogCmd.StdoutPipe() // to be piped into mysql
  1233  		if err != nil {
  1234  			return err
  1235  		}
  1236  	}
  1237  	{
  1238  		name, err := binaryPath(dir, "mysql")
  1239  		if err != nil {
  1240  			return err
  1241  		}
  1242  		params, err := mysqld.dbcfgs.DbaConnector().MysqlParams()
  1243  		if err != nil {
  1244  			return err
  1245  		}
  1246  		cnf, err := mysqld.defaultsExtraFile(params)
  1247  		if err != nil {
  1248  			return err
  1249  		}
  1250  		defer os.Remove(cnf)
  1251  		args := []string{
  1252  			"--defaults-extra-file=" + cnf,
  1253  		}
  1254  		mysqlCmd = exec.Command(name, args...)
  1255  		mysqlCmd.Dir = dir
  1256  		mysqlCmd.Env = env
  1257  		mysqlCmd.Stdin = pipe // piped from mysqlbinlog
  1258  	}
  1259  	// Run both processes, piped:
  1260  	if err := mysqlbinlogCmd.Start(); err != nil {
  1261  		return err
  1262  	}
  1263  	if err := mysqlCmd.Start(); err != nil {
  1264  		return err
  1265  	}
  1266  	// Wait for both to complete:
  1267  	if err := mysqlbinlogCmd.Wait(); err != nil {
  1268  		return err
  1269  	}
  1270  	if err := mysqlCmd.Wait(); err != nil {
  1271  		return err
  1272  	}
  1273  	return nil
  1274  }