vitess.io/vitess@v0.16.2/go/vt/mysqlctl/replication.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  Handle creating replicas and setting up the replication streams.
    19  */
    20  
    21  package mysqlctl
    22  
    23  import (
    24  	"errors"
    25  	"fmt"
    26  	"net"
    27  	"strconv"
    28  	"strings"
    29  	"time"
    30  
    31  	"vitess.io/vitess/go/vt/vtgate/evalengine"
    32  
    33  	"context"
    34  
    35  	"vitess.io/vitess/go/mysql"
    36  	"vitess.io/vitess/go/netutil"
    37  	"vitess.io/vitess/go/vt/hook"
    38  	"vitess.io/vitess/go/vt/log"
    39  )
    40  
    41  // WaitForReplicationStart waits until the deadline for replication to start.
    42  // This validates the current primary is correct and can be connected to.
    43  func WaitForReplicationStart(mysqld MysqlDaemon, replicaStartDeadline int) error {
    44  	var rowMap map[string]string
    45  	for replicaWait := 0; replicaWait < replicaStartDeadline; replicaWait++ {
    46  		status, err := mysqld.ReplicationStatus()
    47  		if err != nil {
    48  			return err
    49  		}
    50  
    51  		if status.Running() {
    52  			return nil
    53  		}
    54  		time.Sleep(time.Second)
    55  	}
    56  
    57  	errorKeys := []string{"Last_Error", "Last_IO_Error", "Last_SQL_Error"}
    58  	errs := make([]string, 0, len(errorKeys))
    59  	for _, key := range errorKeys {
    60  		if rowMap[key] != "" {
    61  			errs = append(errs, key+": "+rowMap[key])
    62  		}
    63  	}
    64  	if len(errs) != 0 {
    65  		return errors.New(strings.Join(errs, ", "))
    66  	}
    67  	return nil
    68  }
    69  
    70  // StartReplication starts replication.
    71  func (mysqld *Mysqld) StartReplication(hookExtraEnv map[string]string) error {
    72  	ctx := context.TODO()
    73  	conn, err := getPoolReconnect(ctx, mysqld.dbaPool)
    74  	if err != nil {
    75  		return err
    76  	}
    77  	defer conn.Recycle()
    78  
    79  	if err := mysqld.executeSuperQueryListConn(ctx, conn, []string{conn.StartReplicationCommand()}); err != nil {
    80  		return err
    81  	}
    82  
    83  	h := hook.NewSimpleHook("postflight_start_slave")
    84  	h.ExtraEnv = hookExtraEnv
    85  	return h.ExecuteOptional()
    86  }
    87  
    88  // StartReplicationUntilAfter starts replication until replication has come to `targetPos`, then it stops replication
    89  func (mysqld *Mysqld) StartReplicationUntilAfter(ctx context.Context, targetPos mysql.Position) error {
    90  	conn, err := getPoolReconnect(ctx, mysqld.dbaPool)
    91  	if err != nil {
    92  		return err
    93  	}
    94  	defer conn.Recycle()
    95  
    96  	queries := []string{conn.StartReplicationUntilAfterCommand(targetPos)}
    97  
    98  	return mysqld.executeSuperQueryListConn(ctx, conn, queries)
    99  }
   100  
   101  // StartSQLThreadUntilAfter starts replication's SQL thread(s) until replication has come to `targetPos`, then it stops it
   102  func (mysqld *Mysqld) StartSQLThreadUntilAfter(ctx context.Context, targetPos mysql.Position) error {
   103  	conn, err := getPoolReconnect(ctx, mysqld.dbaPool)
   104  	if err != nil {
   105  		return err
   106  	}
   107  	defer conn.Recycle()
   108  
   109  	queries := []string{conn.StartSQLThreadUntilAfterCommand(targetPos)}
   110  
   111  	return mysqld.executeSuperQueryListConn(ctx, conn, queries)
   112  }
   113  
   114  // StopReplication stops replication.
   115  func (mysqld *Mysqld) StopReplication(hookExtraEnv map[string]string) error {
   116  	h := hook.NewSimpleHook("preflight_stop_slave")
   117  	h.ExtraEnv = hookExtraEnv
   118  	if err := h.ExecuteOptional(); err != nil {
   119  		return err
   120  	}
   121  	ctx := context.TODO()
   122  	conn, err := getPoolReconnect(ctx, mysqld.dbaPool)
   123  	if err != nil {
   124  		return err
   125  	}
   126  	defer conn.Recycle()
   127  
   128  	return mysqld.executeSuperQueryListConn(ctx, conn, []string{conn.StopReplicationCommand()})
   129  }
   130  
   131  // StopIOThread stops a replica's IO thread only.
   132  func (mysqld *Mysqld) StopIOThread(ctx context.Context) error {
   133  	conn, err := getPoolReconnect(ctx, mysqld.dbaPool)
   134  	if err != nil {
   135  		return err
   136  	}
   137  	defer conn.Recycle()
   138  
   139  	return mysqld.executeSuperQueryListConn(ctx, conn, []string{conn.StopIOThreadCommand()})
   140  }
   141  
   142  // StopSQLThread stops a replica's SQL thread(s) only.
   143  func (mysqld *Mysqld) StopSQLThread(ctx context.Context) error {
   144  	conn, err := getPoolReconnect(ctx, mysqld.dbaPool)
   145  	if err != nil {
   146  		return err
   147  	}
   148  	defer conn.Recycle()
   149  
   150  	return mysqld.executeSuperQueryListConn(ctx, conn, []string{conn.StopSQLThreadCommand()})
   151  }
   152  
   153  // RestartReplication stops, resets and starts replication.
   154  func (mysqld *Mysqld) RestartReplication(hookExtraEnv map[string]string) error {
   155  	h := hook.NewSimpleHook("preflight_stop_slave")
   156  	h.ExtraEnv = hookExtraEnv
   157  	if err := h.ExecuteOptional(); err != nil {
   158  		return err
   159  	}
   160  	ctx := context.TODO()
   161  	conn, err := getPoolReconnect(ctx, mysqld.dbaPool)
   162  	if err != nil {
   163  		return err
   164  	}
   165  	defer conn.Recycle()
   166  
   167  	if err := mysqld.executeSuperQueryListConn(ctx, conn, conn.RestartReplicationCommands()); err != nil {
   168  		return err
   169  	}
   170  
   171  	h = hook.NewSimpleHook("postflight_start_slave")
   172  	h.ExtraEnv = hookExtraEnv
   173  	return h.ExecuteOptional()
   174  }
   175  
   176  // GetMysqlPort returns mysql port
   177  func (mysqld *Mysqld) GetMysqlPort() (int32, error) {
   178  	qr, err := mysqld.FetchSuperQuery(context.TODO(), "SHOW VARIABLES LIKE 'port'")
   179  	if err != nil {
   180  		return 0, err
   181  	}
   182  	if len(qr.Rows) != 1 {
   183  		return 0, errors.New("no port variable in mysql")
   184  	}
   185  	utemp, err := evalengine.ToUint64(qr.Rows[0][1])
   186  	if err != nil {
   187  		return 0, err
   188  	}
   189  	return int32(utemp), nil
   190  }
   191  
   192  // GetServerID returns mysql server id
   193  func (mysqld *Mysqld) GetServerID(ctx context.Context) (uint32, error) {
   194  	qr, err := mysqld.FetchSuperQuery(ctx, "select @@global.server_id")
   195  	if err != nil {
   196  		return 0, err
   197  	}
   198  	if len(qr.Rows) != 1 {
   199  		return 0, errors.New("no server_id in mysql")
   200  	}
   201  	utemp, err := evalengine.ToUint64(qr.Rows[0][0])
   202  	if err != nil {
   203  		return 0, err
   204  	}
   205  	return uint32(utemp), nil
   206  }
   207  
   208  // GetServerUUID returns mysql server uuid
   209  func (mysqld *Mysqld) GetServerUUID(ctx context.Context) (string, error) {
   210  	conn, err := getPoolReconnect(ctx, mysqld.dbaPool)
   211  	if err != nil {
   212  		return "", err
   213  	}
   214  	defer conn.Recycle()
   215  
   216  	return conn.GetServerUUID()
   217  }
   218  
   219  // IsReadOnly return true if the instance is read only
   220  func (mysqld *Mysqld) IsReadOnly() (bool, error) {
   221  	qr, err := mysqld.FetchSuperQuery(context.TODO(), "SHOW VARIABLES LIKE 'read_only'")
   222  	if err != nil {
   223  		return true, err
   224  	}
   225  	if len(qr.Rows) != 1 {
   226  		return true, errors.New("no read_only variable in mysql")
   227  	}
   228  	if qr.Rows[0][1].ToString() == "ON" {
   229  		return true, nil
   230  	}
   231  	return false, nil
   232  }
   233  
   234  // SetReadOnly set/unset the read_only flag
   235  func (mysqld *Mysqld) SetReadOnly(on bool) error {
   236  	// temp logging, to be removed in v17
   237  	var newState string
   238  	switch on {
   239  	case false:
   240  		newState = "ReadWrite"
   241  	case true:
   242  		newState = "ReadOnly"
   243  	}
   244  	log.Infof("SetReadOnly setting connection setting of %s:%d to : %s",
   245  		mysqld.dbcfgs.Host, mysqld.dbcfgs.Port, newState)
   246  
   247  	query := "SET GLOBAL read_only = "
   248  	if on {
   249  		query += "ON"
   250  	} else {
   251  		query += "OFF"
   252  	}
   253  	return mysqld.ExecuteSuperQuery(context.TODO(), query)
   254  }
   255  
   256  // SetSuperReadOnly set/unset the super_read_only flag
   257  func (mysqld *Mysqld) SetSuperReadOnly(on bool) error {
   258  	query := "SET GLOBAL super_read_only = "
   259  	if on {
   260  		query += "ON"
   261  	} else {
   262  		query += "OFF"
   263  	}
   264  	return mysqld.ExecuteSuperQuery(context.TODO(), query)
   265  }
   266  
   267  // WaitSourcePos lets replicas wait to given replication position
   268  func (mysqld *Mysqld) WaitSourcePos(ctx context.Context, targetPos mysql.Position) error {
   269  	// Get a connection.
   270  	conn, err := getPoolReconnect(ctx, mysqld.dbaPool)
   271  	if err != nil {
   272  		return err
   273  	}
   274  	defer conn.Recycle()
   275  
   276  	// First check if filePos flavored Position was passed in. If so, we can't defer to the flavor in the connection,
   277  	// unless that flavor is also filePos.
   278  	waitCommandName := "WaitUntilPositionCommand"
   279  	var query string
   280  	if targetPos.MatchesFlavor(mysql.FilePosFlavorID) {
   281  		// If we are the primary, WaitUntilFilePositionCommand will fail.
   282  		// But position is most likely reached. So, check the position
   283  		// first.
   284  		mpos, err := conn.PrimaryFilePosition()
   285  		if err != nil {
   286  			return fmt.Errorf("WaitSourcePos: PrimaryFilePosition failed: %v", err)
   287  		}
   288  		if mpos.AtLeast(targetPos) {
   289  			return nil
   290  		}
   291  
   292  		// Find the query to run, run it.
   293  		query, err = conn.WaitUntilFilePositionCommand(ctx, targetPos)
   294  		if err != nil {
   295  			return err
   296  		}
   297  		waitCommandName = "WaitUntilFilePositionCommand"
   298  	} else {
   299  		// If we are the primary, WaitUntilPositionCommand will fail.
   300  		// But position is most likely reached. So, check the position
   301  		// first.
   302  		mpos, err := conn.PrimaryPosition()
   303  		if err != nil {
   304  			return fmt.Errorf("WaitSourcePos: PrimaryPosition failed: %v", err)
   305  		}
   306  		if mpos.AtLeast(targetPos) {
   307  			return nil
   308  		}
   309  
   310  		// Find the query to run, run it.
   311  		query, err = conn.WaitUntilPositionCommand(ctx, targetPos)
   312  		if err != nil {
   313  			return err
   314  		}
   315  	}
   316  
   317  	qr, err := mysqld.FetchSuperQuery(ctx, query)
   318  	if err != nil {
   319  		return fmt.Errorf("%v(%v) failed: %v", waitCommandName, query, err)
   320  	}
   321  
   322  	if len(qr.Rows) != 1 || len(qr.Rows[0]) != 1 {
   323  		return fmt.Errorf("unexpected result format from %v(%v): %#v", waitCommandName, query, qr)
   324  	}
   325  	result := qr.Rows[0][0]
   326  	if result.IsNull() {
   327  		return fmt.Errorf("%v(%v) failed: replication is probably stopped", waitCommandName, query)
   328  	}
   329  	if result.ToString() == "-1" {
   330  		return fmt.Errorf("timed out waiting for position %v", targetPos)
   331  	}
   332  	return nil
   333  }
   334  
   335  // ReplicationStatus returns the server replication status
   336  func (mysqld *Mysqld) ReplicationStatus() (mysql.ReplicationStatus, error) {
   337  	conn, err := getPoolReconnect(context.TODO(), mysqld.dbaPool)
   338  	if err != nil {
   339  		return mysql.ReplicationStatus{}, err
   340  	}
   341  	defer conn.Recycle()
   342  
   343  	return conn.ShowReplicationStatus()
   344  }
   345  
   346  // PrimaryStatus returns the primary replication statuses
   347  func (mysqld *Mysqld) PrimaryStatus(ctx context.Context) (mysql.PrimaryStatus, error) {
   348  	conn, err := getPoolReconnect(ctx, mysqld.dbaPool)
   349  	if err != nil {
   350  		return mysql.PrimaryStatus{}, err
   351  	}
   352  	defer conn.Recycle()
   353  
   354  	return conn.ShowPrimaryStatus()
   355  }
   356  
   357  // GetGTIDPurged returns the gtid purged statuses
   358  func (mysqld *Mysqld) GetGTIDPurged(ctx context.Context) (mysql.Position, error) {
   359  	conn, err := getPoolReconnect(ctx, mysqld.dbaPool)
   360  	if err != nil {
   361  		return mysql.Position{}, err
   362  	}
   363  	defer conn.Recycle()
   364  
   365  	return conn.GetGTIDPurged()
   366  }
   367  
   368  // PrimaryPosition returns the primary replication position.
   369  func (mysqld *Mysqld) PrimaryPosition() (mysql.Position, error) {
   370  	conn, err := getPoolReconnect(context.TODO(), mysqld.dbaPool)
   371  	if err != nil {
   372  		return mysql.Position{}, err
   373  	}
   374  	defer conn.Recycle()
   375  
   376  	return conn.PrimaryPosition()
   377  }
   378  
   379  // SetReplicationPosition sets the replication position at which the replica will resume
   380  // when its replication is started.
   381  func (mysqld *Mysqld) SetReplicationPosition(ctx context.Context, pos mysql.Position) error {
   382  	conn, err := getPoolReconnect(ctx, mysqld.dbaPool)
   383  	if err != nil {
   384  		return err
   385  	}
   386  	defer conn.Recycle()
   387  
   388  	cmds := conn.SetReplicationPositionCommands(pos)
   389  	log.Infof("Executing commands to set replication position: %v", cmds)
   390  	return mysqld.executeSuperQueryListConn(ctx, conn, cmds)
   391  }
   392  
   393  // SetReplicationSource makes the provided host / port the primary. It optionally
   394  // stops replication before, and starts it after.
   395  func (mysqld *Mysqld) SetReplicationSource(ctx context.Context, host string, port int, replicationStopBefore bool, replicationStartAfter bool) error {
   396  	params, err := mysqld.dbcfgs.ReplConnector().MysqlParams()
   397  	if err != nil {
   398  		return err
   399  	}
   400  	conn, err := getPoolReconnect(ctx, mysqld.dbaPool)
   401  	if err != nil {
   402  		return err
   403  	}
   404  	defer conn.Recycle()
   405  
   406  	cmds := []string{}
   407  	if replicationStopBefore {
   408  		cmds = append(cmds, conn.StopReplicationCommand())
   409  	}
   410  	// Reset replication parameters commands makes the instance forget the source host port
   411  	// This is required because sometimes MySQL gets stuck due to improper initialization of
   412  	// master info structure or related failures and throws errors like
   413  	// ERROR 1201 (HY000): Could not initialize master info structure; more error messages can be found in the MySQL error log
   414  	// These errors can only be resolved by resetting the replication parameters, otherwise START SLAVE fails.
   415  	// Therefore, we have elected to always reset the replication parameters whenever we try to set the source host port
   416  	// Since there is no real overhead, but it makes this function robust enough to also handle failures like these.
   417  	cmds = append(cmds, conn.ResetReplicationParametersCommands()...)
   418  	smc := conn.SetReplicationSourceCommand(params, host, port, int(replicationConnectRetry.Seconds()))
   419  	cmds = append(cmds, smc)
   420  	if replicationStartAfter {
   421  		cmds = append(cmds, conn.StartReplicationCommand())
   422  	}
   423  	return mysqld.executeSuperQueryListConn(ctx, conn, cmds)
   424  }
   425  
   426  // ResetReplication resets all replication for this host.
   427  func (mysqld *Mysqld) ResetReplication(ctx context.Context) error {
   428  	conn, connErr := getPoolReconnect(ctx, mysqld.dbaPool)
   429  	if connErr != nil {
   430  		return connErr
   431  	}
   432  	defer conn.Recycle()
   433  
   434  	cmds := conn.ResetReplicationCommands()
   435  	return mysqld.executeSuperQueryListConn(ctx, conn, cmds)
   436  }
   437  
   438  // ResetReplicationParameters resets the replica replication parameters for this host.
   439  func (mysqld *Mysqld) ResetReplicationParameters(ctx context.Context) error {
   440  	conn, connErr := getPoolReconnect(ctx, mysqld.dbaPool)
   441  	if connErr != nil {
   442  		return connErr
   443  	}
   444  	defer conn.Recycle()
   445  
   446  	cmds := conn.ResetReplicationParametersCommands()
   447  	return mysqld.executeSuperQueryListConn(ctx, conn, cmds)
   448  }
   449  
   450  // +------+---------+---------------------+------+-------------+------+----------------------------------------------------------------+------------------+
   451  // | Id   | User    | Host                | db   | Command     | Time | State                                                          | Info             |
   452  // +------+---------+---------------------+------+-------------+------+----------------------------------------------------------------+------------------+
   453  // | 9792 | vt_repl | host:port           | NULL | Binlog Dump |   54 | Has sent all binlog to slave; waiting for binlog to be updated | NULL             |
   454  // | 9797 | vt_dba  | localhost           | NULL | Query       |    0 | NULL                                                           | show processlist |
   455  // +------+---------+---------------------+------+-------------+------+----------------------------------------------------------------+------------------+
   456  //
   457  // Array indices for the results of SHOW PROCESSLIST.
   458  const (
   459  	colConnectionID = iota //nolint
   460  	colUsername            //nolint
   461  	colClientAddr
   462  	colDbName //nolint
   463  	colCommand
   464  )
   465  
   466  const (
   467  	// this is the command used by mysql replicas
   468  	binlogDumpCommand = "Binlog Dump"
   469  )
   470  
   471  // FindReplicas gets IP addresses for all currently connected replicas.
   472  func FindReplicas(mysqld MysqlDaemon) ([]string, error) {
   473  	qr, err := mysqld.FetchSuperQuery(context.TODO(), "SHOW PROCESSLIST")
   474  	if err != nil {
   475  		return nil, err
   476  	}
   477  	addrs := make([]string, 0, 32)
   478  	for _, row := range qr.Rows {
   479  		// Check for prefix, since it could be "Binlog Dump GTID".
   480  		if strings.HasPrefix(row[colCommand].ToString(), binlogDumpCommand) {
   481  			host := row[colClientAddr].ToString()
   482  			if host == "localhost" {
   483  				// If we have a local binlog streamer, it will
   484  				// show up as being connected
   485  				// from 'localhost' through the local
   486  				// socket. Ignore it.
   487  				continue
   488  			}
   489  			host, _, err = netutil.SplitHostPort(host)
   490  			if err != nil {
   491  				return nil, fmt.Errorf("FindReplicas: malformed addr %v", err)
   492  			}
   493  			var ips []string
   494  			ips, err = net.LookupHost(host)
   495  			if err != nil {
   496  				return nil, fmt.Errorf("FindReplicas: LookupHost failed %v", err)
   497  			}
   498  			addrs = append(addrs, ips...)
   499  		}
   500  	}
   501  
   502  	return addrs, nil
   503  }
   504  
   505  // EnableBinlogPlayback prepares the server to play back events from a binlog stream.
   506  // Whatever it does for a given flavor, it must be idempotent.
   507  func (mysqld *Mysqld) EnableBinlogPlayback() error {
   508  	// Get a connection.
   509  	conn, err := getPoolReconnect(context.TODO(), mysqld.dbaPool)
   510  	if err != nil {
   511  		return err
   512  	}
   513  	defer conn.Recycle()
   514  
   515  	// See if we have a command to run, and run it.
   516  	cmd := conn.EnableBinlogPlaybackCommand()
   517  	if cmd == "" {
   518  		return nil
   519  	}
   520  	if err := mysqld.ExecuteSuperQuery(context.TODO(), cmd); err != nil {
   521  		log.Errorf("EnableBinlogPlayback: cannot run query '%v': %v", cmd, err)
   522  		return fmt.Errorf("EnableBinlogPlayback: cannot run query '%v': %v", cmd, err)
   523  	}
   524  
   525  	log.Info("EnableBinlogPlayback: successfully ran %v", cmd)
   526  	return nil
   527  }
   528  
   529  // DisableBinlogPlayback returns the server to the normal state after streaming.
   530  // Whatever it does for a given flavor, it must be idempotent.
   531  func (mysqld *Mysqld) DisableBinlogPlayback() error {
   532  	// Get a connection.
   533  	conn, err := getPoolReconnect(context.TODO(), mysqld.dbaPool)
   534  	if err != nil {
   535  		return err
   536  	}
   537  	defer conn.Recycle()
   538  
   539  	// See if we have a command to run, and run it.
   540  	cmd := conn.DisableBinlogPlaybackCommand()
   541  	if cmd == "" {
   542  		return nil
   543  	}
   544  	if err := mysqld.ExecuteSuperQuery(context.TODO(), cmd); err != nil {
   545  		log.Errorf("DisableBinlogPlayback: cannot run query '%v': %v", cmd, err)
   546  		return fmt.Errorf("DisableBinlogPlayback: cannot run query '%v': %v", cmd, err)
   547  	}
   548  
   549  	log.Info("DisableBinlogPlayback: successfully ran '%v'", cmd)
   550  	return nil
   551  }
   552  
   553  // GetBinlogInformation gets the binlog format, whether binlog is enabled and if updates on replica logging is enabled.
   554  func (mysqld *Mysqld) GetBinlogInformation(ctx context.Context) (string, bool, bool, string, error) {
   555  	qr, err := mysqld.FetchSuperQuery(ctx, "select @@global.binlog_format, @@global.log_bin, @@global.log_slave_updates, @@global.binlog_row_image")
   556  	if err != nil {
   557  		return "", false, false, "", err
   558  	}
   559  	if len(qr.Rows) != 1 {
   560  		return "", false, false, "", errors.New("unable to read global variables binlog_format, log_bin, log_slave_updates, gtid_mode, binlog_rowge")
   561  	}
   562  	res := qr.Named().Row()
   563  	binlogFormat, err := res.ToString("@@global.binlog_format")
   564  	if err != nil {
   565  		return "", false, false, "", err
   566  	}
   567  	logBin, err := res.ToInt64("@@global.log_bin")
   568  	if err != nil {
   569  		return "", false, false, "", err
   570  	}
   571  	logReplicaUpdates, err := res.ToInt64("@@global.log_slave_updates")
   572  	if err != nil {
   573  		return "", false, false, "", err
   574  	}
   575  	binlogRowImage, err := res.ToString("@@global.binlog_row_image")
   576  	if err != nil {
   577  		return "", false, false, "", err
   578  	}
   579  	return binlogFormat, logBin == 1, logReplicaUpdates == 1, binlogRowImage, nil
   580  }
   581  
   582  // GetGTIDMode gets the GTID mode for the server
   583  func (mysqld *Mysqld) GetGTIDMode(ctx context.Context) (string, error) {
   584  	conn, err := getPoolReconnect(ctx, mysqld.dbaPool)
   585  	if err != nil {
   586  		return "", err
   587  	}
   588  	defer conn.Recycle()
   589  
   590  	return conn.GetGTIDMode()
   591  }
   592  
   593  // FlushBinaryLogs is part of the MysqlDaemon interface.
   594  func (mysqld *Mysqld) FlushBinaryLogs(ctx context.Context) (err error) {
   595  	_, err = mysqld.FetchSuperQuery(ctx, "FLUSH BINARY LOGS")
   596  	return err
   597  }
   598  
   599  // GetBinaryLogs is part of the MysqlDaemon interface.
   600  func (mysqld *Mysqld) GetBinaryLogs(ctx context.Context) (binaryLogs []string, err error) {
   601  	qr, err := mysqld.FetchSuperQuery(ctx, "SHOW BINARY LOGS")
   602  	if err != nil {
   603  		return binaryLogs, err
   604  	}
   605  	for _, row := range qr.Rows {
   606  		binaryLogs = append(binaryLogs, row[0].ToString())
   607  	}
   608  	return binaryLogs, err
   609  }
   610  
   611  // GetPreviousGTIDs is part of the MysqlDaemon interface.
   612  func (mysqld *Mysqld) GetPreviousGTIDs(ctx context.Context, binlog string) (previousGtids string, err error) {
   613  	query := fmt.Sprintf("SHOW BINLOG EVENTS IN '%s' LIMIT 2", binlog)
   614  	qr, err := mysqld.FetchSuperQuery(ctx, query)
   615  	if err != nil {
   616  		return previousGtids, err
   617  	}
   618  	previousGtidsFound := false
   619  	for _, row := range qr.Named().Rows {
   620  		if row.AsString("Event_type", "") == "Previous_gtids" {
   621  			previousGtids = row.AsString("Info", "")
   622  			previousGtidsFound = true
   623  		}
   624  	}
   625  	if !previousGtidsFound {
   626  		return previousGtids, fmt.Errorf("GetPreviousGTIDs: previous GTIDs not found")
   627  	}
   628  	return previousGtids, nil
   629  }
   630  
   631  // SetSemiSyncEnabled enables or disables semi-sync replication for
   632  // primary and/or replica mode.
   633  func (mysqld *Mysqld) SetSemiSyncEnabled(primary, replica bool) error {
   634  	log.Infof("Setting semi-sync mode: primary=%v, replica=%v", primary, replica)
   635  
   636  	// Convert bool to int.
   637  	var p, s int
   638  	if primary {
   639  		p = 1
   640  	}
   641  	if replica {
   642  		s = 1
   643  	}
   644  
   645  	err := mysqld.ExecuteSuperQuery(context.TODO(), fmt.Sprintf(
   646  		"SET GLOBAL rpl_semi_sync_master_enabled = %v, GLOBAL rpl_semi_sync_slave_enabled = %v",
   647  		p, s))
   648  	if err != nil {
   649  		return fmt.Errorf("can't set semi-sync mode: %v; make sure plugins are loaded in my.cnf", err)
   650  	}
   651  	return nil
   652  }
   653  
   654  // SemiSyncEnabled returns whether semi-sync is enabled for primary or replica.
   655  // If the semi-sync plugin is not loaded, we assume semi-sync is disabled.
   656  func (mysqld *Mysqld) SemiSyncEnabled() (primary, replica bool) {
   657  	vars, err := mysqld.fetchVariables(context.TODO(), "rpl_semi_sync_%_enabled")
   658  	if err != nil {
   659  		return false, false
   660  	}
   661  	primary = (vars["rpl_semi_sync_master_enabled"] == "ON")
   662  	replica = (vars["rpl_semi_sync_slave_enabled"] == "ON")
   663  	return primary, replica
   664  }
   665  
   666  // SemiSyncStatus returns the current status of semi-sync for primary and replica.
   667  func (mysqld *Mysqld) SemiSyncStatus() (primary, replica bool) {
   668  	vars, err := mysqld.fetchStatuses(context.TODO(), "Rpl_semi_sync_%_status")
   669  	if err != nil {
   670  		return false, false
   671  	}
   672  	primary = vars["Rpl_semi_sync_master_status"] == "ON"
   673  	replica = vars["Rpl_semi_sync_slave_status"] == "ON"
   674  	return primary, replica
   675  }
   676  
   677  // SemiSyncClients returns the number of semi-sync clients for the primary.
   678  func (mysqld *Mysqld) SemiSyncClients() uint32 {
   679  	qr, err := mysqld.FetchSuperQuery(context.TODO(), "SHOW STATUS LIKE 'Rpl_semi_sync_master_clients'")
   680  	if err != nil {
   681  		return 0
   682  	}
   683  	if len(qr.Rows) != 1 {
   684  		return 0
   685  	}
   686  	countStr := qr.Rows[0][1].ToString()
   687  	count, _ := strconv.ParseUint(countStr, 10, 0)
   688  	return uint32(count)
   689  }
   690  
   691  // SemiSyncSettings returns the settings of semi-sync which includes the timeout and the number of replicas to wait for.
   692  func (mysqld *Mysqld) SemiSyncSettings() (timeout uint64, numReplicas uint32) {
   693  	vars, err := mysqld.fetchVariables(context.TODO(), "rpl_semi_sync_%")
   694  	if err != nil {
   695  		return 0, 0
   696  	}
   697  	timeout, _ = strconv.ParseUint(vars["rpl_semi_sync_master_timeout"], 10, 0)
   698  	numReplicasUint, _ := strconv.ParseUint(vars["rpl_semi_sync_master_wait_for_slave_count"], 10, 0)
   699  	return timeout, uint32(numReplicasUint)
   700  }
   701  
   702  // SemiSyncReplicationStatus returns whether semi-sync is currently used by replication.
   703  func (mysqld *Mysqld) SemiSyncReplicationStatus() (bool, error) {
   704  	qr, err := mysqld.FetchSuperQuery(context.TODO(), "SHOW STATUS LIKE 'rpl_semi_sync_slave_status'")
   705  	if err != nil {
   706  		return false, err
   707  	}
   708  	if len(qr.Rows) != 1 {
   709  		return false, errors.New("no rpl_semi_sync_slave_status variable in mysql")
   710  	}
   711  	if qr.Rows[0][1].ToString() == "ON" {
   712  		return true, nil
   713  	}
   714  	return false, nil
   715  }