vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletmanager/rpc_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  package tabletmanager
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"strconv"
    23  	"strings"
    24  	"time"
    25  
    26  	"github.com/spf13/pflag"
    27  
    28  	"vitess.io/vitess/go/mysql"
    29  	"vitess.io/vitess/go/vt/log"
    30  	"vitess.io/vitess/go/vt/logutil"
    31  	"vitess.io/vitess/go/vt/mysqlctl"
    32  	"vitess.io/vitess/go/vt/proto/vtrpc"
    33  	"vitess.io/vitess/go/vt/servenv"
    34  	"vitess.io/vitess/go/vt/topo/topoproto"
    35  	"vitess.io/vitess/go/vt/vterrors"
    36  
    37  	replicationdatapb "vitess.io/vitess/go/vt/proto/replicationdata"
    38  	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
    39  )
    40  
    41  var setSuperReadOnly bool
    42  var disableReplicationManager bool
    43  
    44  func registerReplicationFlags(fs *pflag.FlagSet) {
    45  	fs.BoolVar(&setSuperReadOnly, "use_super_read_only", setSuperReadOnly, "Set super_read_only flag when performing planned failover.")
    46  	fs.BoolVar(&disableReplicationManager, "disable-replication-manager", disableReplicationManager, "Disable replication manager to prevent replication repairs.")
    47  	fs.MarkDeprecated("disable-replication-manager", "Replication manager is deleted")
    48  }
    49  
    50  func init() {
    51  	servenv.OnParseFor("vtcombo", registerReplicationFlags)
    52  	servenv.OnParseFor("vttablet", registerReplicationFlags)
    53  }
    54  
    55  // ReplicationStatus returns the replication status
    56  func (tm *TabletManager) ReplicationStatus(ctx context.Context) (*replicationdatapb.Status, error) {
    57  	status, err := tm.MysqlDaemon.ReplicationStatus()
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  	return mysql.ReplicationStatusToProto(status), nil
    62  }
    63  
    64  // FullStatus returns the full status of MySQL including the replication information, semi-sync information, GTID information among others
    65  func (tm *TabletManager) FullStatus(ctx context.Context) (*replicationdatapb.FullStatus, error) {
    66  	// Server ID - "select @@global.server_id"
    67  	serverID, err := tm.MysqlDaemon.GetServerID(ctx)
    68  	if err != nil {
    69  		return nil, err
    70  	}
    71  
    72  	// Server UUID - "select @@global.server_uuid"
    73  	serverUUID, err := tm.MysqlDaemon.GetServerUUID(ctx)
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  
    78  	// Replication status - "SHOW REPLICA STATUS"
    79  	replicationStatus, err := tm.MysqlDaemon.ReplicationStatus()
    80  	var replicationStatusProto *replicationdatapb.Status
    81  	if err != nil && err != mysql.ErrNotReplica {
    82  		return nil, err
    83  	}
    84  	if err == nil {
    85  		replicationStatusProto = mysql.ReplicationStatusToProto(replicationStatus)
    86  	}
    87  
    88  	// Primary status - "SHOW MASTER STATUS"
    89  	primaryStatus, err := tm.MysqlDaemon.PrimaryStatus(ctx)
    90  	var primaryStatusProto *replicationdatapb.PrimaryStatus
    91  	if err != nil && err != mysql.ErrNoPrimaryStatus {
    92  		return nil, err
    93  	}
    94  	if err == nil {
    95  		primaryStatusProto = mysql.PrimaryStatusToProto(primaryStatus)
    96  	}
    97  
    98  	// Purged GTID set
    99  	purgedGTIDs, err := tm.MysqlDaemon.GetGTIDPurged(ctx)
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  
   104  	// Version string "majorVersion.minorVersion.patchRelease"
   105  	version := tm.MysqlDaemon.GetVersionString()
   106  
   107  	// Version comment "select @@global.version_comment"
   108  	versionComment := tm.MysqlDaemon.GetVersionComment(ctx)
   109  
   110  	// Read only - "SHOW VARIABLES LIKE 'read_only'"
   111  	readOnly, err := tm.MysqlDaemon.IsReadOnly()
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  
   116  	// Binlog Information - "select @@global.binlog_format, @@global.log_bin, @@global.log_slave_updates, @@global.binlog_row_image"
   117  	binlogFormat, logBin, logReplicaUpdates, binlogRowImage, err := tm.MysqlDaemon.GetBinlogInformation(ctx)
   118  	if err != nil {
   119  		return nil, err
   120  	}
   121  
   122  	// GTID Mode - "select @@global.gtid_mode" - Only applicable for MySQL variants
   123  	gtidMode, err := tm.MysqlDaemon.GetGTIDMode(ctx)
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  
   128  	// Semi sync settings - "show global variables like 'rpl_semi_sync_%_enabled'"
   129  	primarySemiSync, replicaSemiSync := tm.MysqlDaemon.SemiSyncEnabled()
   130  
   131  	// Semi sync status - "show status like 'Rpl_semi_sync_%_status'"
   132  	primarySemiSyncStatus, replicaSemiSyncStatus := tm.MysqlDaemon.SemiSyncStatus()
   133  
   134  	//  Semi sync clients count - "show status like 'semi_sync_primary_clients'"
   135  	semiSyncClients := tm.MysqlDaemon.SemiSyncClients()
   136  
   137  	// Semi sync settings - "show status like 'rpl_semi_sync_%'
   138  	semiSyncTimeout, semiSyncNumReplicas := tm.MysqlDaemon.SemiSyncSettings()
   139  
   140  	return &replicationdatapb.FullStatus{
   141  		ServerId:                    serverID,
   142  		ServerUuid:                  serverUUID,
   143  		ReplicationStatus:           replicationStatusProto,
   144  		PrimaryStatus:               primaryStatusProto,
   145  		GtidPurged:                  mysql.EncodePosition(purgedGTIDs),
   146  		Version:                     version,
   147  		VersionComment:              versionComment,
   148  		ReadOnly:                    readOnly,
   149  		GtidMode:                    gtidMode,
   150  		BinlogFormat:                binlogFormat,
   151  		BinlogRowImage:              binlogRowImage,
   152  		LogBinEnabled:               logBin,
   153  		LogReplicaUpdates:           logReplicaUpdates,
   154  		SemiSyncPrimaryEnabled:      primarySemiSync,
   155  		SemiSyncReplicaEnabled:      replicaSemiSync,
   156  		SemiSyncPrimaryStatus:       primarySemiSyncStatus,
   157  		SemiSyncReplicaStatus:       replicaSemiSyncStatus,
   158  		SemiSyncPrimaryClients:      semiSyncClients,
   159  		SemiSyncPrimaryTimeout:      semiSyncTimeout,
   160  		SemiSyncWaitForReplicaCount: semiSyncNumReplicas,
   161  	}, nil
   162  }
   163  
   164  // PrimaryStatus returns the replication status for a primary tablet.
   165  func (tm *TabletManager) PrimaryStatus(ctx context.Context) (*replicationdatapb.PrimaryStatus, error) {
   166  	status, err := tm.MysqlDaemon.PrimaryStatus(ctx)
   167  	if err != nil {
   168  		return nil, err
   169  	}
   170  	return mysql.PrimaryStatusToProto(status), nil
   171  }
   172  
   173  // PrimaryPosition returns the position of a primary database
   174  func (tm *TabletManager) PrimaryPosition(ctx context.Context) (string, error) {
   175  	pos, err := tm.MysqlDaemon.PrimaryPosition()
   176  	if err != nil {
   177  		return "", err
   178  	}
   179  	return mysql.EncodePosition(pos), nil
   180  }
   181  
   182  // WaitForPosition waits until replication reaches the desired position
   183  func (tm *TabletManager) WaitForPosition(ctx context.Context, pos string) error {
   184  	log.Infof("WaitForPosition: %v", pos)
   185  	mpos, err := mysql.DecodePosition(pos)
   186  	if err != nil {
   187  		return err
   188  	}
   189  	return tm.MysqlDaemon.WaitSourcePos(ctx, mpos)
   190  }
   191  
   192  // StopReplication will stop the mysql. Works both when Vitess manages
   193  // replication or not (using hook if not).
   194  func (tm *TabletManager) StopReplication(ctx context.Context) error {
   195  	log.Infof("StopReplication")
   196  	if err := tm.lock(ctx); err != nil {
   197  		return err
   198  	}
   199  	defer tm.unlock()
   200  
   201  	return tm.stopReplicationLocked(ctx)
   202  }
   203  
   204  func (tm *TabletManager) stopReplicationLocked(ctx context.Context) error {
   205  	return tm.MysqlDaemon.StopReplication(tm.hookExtraEnv())
   206  }
   207  
   208  func (tm *TabletManager) stopIOThreadLocked(ctx context.Context) error {
   209  	return tm.MysqlDaemon.StopIOThread(ctx)
   210  }
   211  
   212  // StopReplicationMinimum will stop the replication after it reaches at least the
   213  // provided position. Works both when Vitess manages
   214  // replication or not (using hook if not).
   215  func (tm *TabletManager) StopReplicationMinimum(ctx context.Context, position string, waitTime time.Duration) (string, error) {
   216  	log.Infof("StopReplicationMinimum: position: %v waitTime: %v", position, waitTime)
   217  	if err := tm.lock(ctx); err != nil {
   218  		return "", err
   219  	}
   220  	defer tm.unlock()
   221  
   222  	pos, err := mysql.DecodePosition(position)
   223  	if err != nil {
   224  		return "", err
   225  	}
   226  	waitCtx, cancel := context.WithTimeout(ctx, waitTime)
   227  	defer cancel()
   228  	if err := tm.MysqlDaemon.WaitSourcePos(waitCtx, pos); err != nil {
   229  		return "", err
   230  	}
   231  	if err := tm.stopReplicationLocked(ctx); err != nil {
   232  		return "", err
   233  	}
   234  	pos, err = tm.MysqlDaemon.PrimaryPosition()
   235  	if err != nil {
   236  		return "", err
   237  	}
   238  	return mysql.EncodePosition(pos), nil
   239  }
   240  
   241  // StartReplication will start the mysql. Works both when Vitess manages
   242  // replication or not (using hook if not).
   243  func (tm *TabletManager) StartReplication(ctx context.Context, semiSync bool) error {
   244  	log.Infof("StartReplication")
   245  	if err := tm.lock(ctx); err != nil {
   246  		return err
   247  	}
   248  	defer tm.unlock()
   249  
   250  	if err := tm.fixSemiSync(tm.Tablet().Type, convertBoolToSemiSyncAction(semiSync)); err != nil {
   251  		return err
   252  	}
   253  	return tm.MysqlDaemon.StartReplication(tm.hookExtraEnv())
   254  }
   255  
   256  // StartReplicationUntilAfter will start the replication and let it catch up
   257  // until and including the transactions in `position`
   258  func (tm *TabletManager) StartReplicationUntilAfter(ctx context.Context, position string, waitTime time.Duration) error {
   259  	log.Infof("StartReplicationUntilAfter: position: %v waitTime: %v", position, waitTime)
   260  	if err := tm.lock(ctx); err != nil {
   261  		return err
   262  	}
   263  	defer tm.unlock()
   264  
   265  	waitCtx, cancel := context.WithTimeout(ctx, waitTime)
   266  	defer cancel()
   267  
   268  	pos, err := mysql.DecodePosition(position)
   269  	if err != nil {
   270  		return err
   271  	}
   272  
   273  	return tm.MysqlDaemon.StartReplicationUntilAfter(waitCtx, pos)
   274  }
   275  
   276  // GetReplicas returns the address of all the replicas
   277  func (tm *TabletManager) GetReplicas(ctx context.Context) ([]string, error) {
   278  	return mysqlctl.FindReplicas(tm.MysqlDaemon)
   279  }
   280  
   281  // ResetReplication completely resets the replication on the host.
   282  // All binary and relay logs are flushed. All replication positions are reset.
   283  func (tm *TabletManager) ResetReplication(ctx context.Context) error {
   284  	log.Infof("ResetReplication")
   285  	if err := tm.lock(ctx); err != nil {
   286  		return err
   287  	}
   288  	defer tm.unlock()
   289  
   290  	return tm.MysqlDaemon.ResetReplication(ctx)
   291  }
   292  
   293  // InitPrimary enables writes and returns the replication position.
   294  func (tm *TabletManager) InitPrimary(ctx context.Context, semiSync bool) (string, error) {
   295  	log.Infof("InitPrimary with semiSync as %t", semiSync)
   296  	if err := tm.lock(ctx); err != nil {
   297  		return "", err
   298  	}
   299  	defer tm.unlock()
   300  
   301  	if setSuperReadOnly {
   302  		// Setting super_read_only off so that we can run the DDL commands
   303  		if err := tm.MysqlDaemon.SetSuperReadOnly(false); err != nil {
   304  			if strings.Contains(err.Error(), strconv.Itoa(mysql.ERUnknownSystemVariable)) {
   305  				log.Warningf("server does not know about super_read_only, continuing anyway...")
   306  			} else {
   307  				return "", err
   308  			}
   309  		}
   310  	}
   311  
   312  	// we need to generate a binlog entry so that we can get the current position.
   313  	cmd := mysqlctl.GenerateInitialBinlogEntry()
   314  	if err := tm.MysqlDaemon.ExecuteSuperQueryList(ctx, []string{cmd}); err != nil {
   315  		return "", err
   316  	}
   317  
   318  	// get the current replication position
   319  	pos, err := tm.MysqlDaemon.PrimaryPosition()
   320  	if err != nil {
   321  		return "", err
   322  	}
   323  
   324  	// Set the server read-write, from now on we can accept real
   325  	// client writes. Note that if semi-sync replication is enabled,
   326  	// we'll still need some replicas to be able to commit transactions.
   327  	if err := tm.changeTypeLocked(ctx, topodatapb.TabletType_PRIMARY, DBActionSetReadWrite, convertBoolToSemiSyncAction(semiSync)); err != nil {
   328  		return "", err
   329  	}
   330  
   331  	// Enforce semi-sync after changing the tablet type to PRIMARY. Otherwise, the
   332  	// primary will hang while trying to create the database.
   333  	if err := tm.fixSemiSync(topodatapb.TabletType_PRIMARY, convertBoolToSemiSyncAction(semiSync)); err != nil {
   334  		return "", err
   335  	}
   336  
   337  	return mysql.EncodePosition(pos), nil
   338  }
   339  
   340  // PopulateReparentJournal adds an entry into the reparent_journal table.
   341  func (tm *TabletManager) PopulateReparentJournal(ctx context.Context, timeCreatedNS int64, actionName string, primaryAlias *topodatapb.TabletAlias, position string) error {
   342  	log.Infof("PopulateReparentJournal: action: %v parent: %v  position: %v timeCreatedNS: %d actionName: %s primaryAlias: %s",
   343  		actionName, primaryAlias, position, timeCreatedNS, actionName, primaryAlias)
   344  	pos, err := mysql.DecodePosition(position)
   345  	if err != nil {
   346  		return err
   347  	}
   348  
   349  	cmds := []string{mysqlctl.PopulateReparentJournal(timeCreatedNS, actionName, topoproto.TabletAliasString(primaryAlias), pos)}
   350  
   351  	return tm.MysqlDaemon.ExecuteSuperQueryList(ctx, cmds)
   352  }
   353  
   354  // InitReplica sets replication primary and position, and waits for the
   355  // reparent_journal table entry up to context timeout
   356  func (tm *TabletManager) InitReplica(ctx context.Context, parent *topodatapb.TabletAlias, position string, timeCreatedNS int64, semiSync bool) error {
   357  	log.Infof("InitReplica: parent: %v  position: %v  timeCreatedNS: %d  semisync: %t", parent, position, timeCreatedNS, semiSync)
   358  	if err := tm.lock(ctx); err != nil {
   359  		return err
   360  	}
   361  	defer tm.unlock()
   362  
   363  	// If we were a primary type, switch our type to replica.  This
   364  	// is used on the old primary when using InitShardPrimary with
   365  	// -force, and the new primary is different from the old primary.
   366  	if tm.Tablet().Type == topodatapb.TabletType_PRIMARY {
   367  		if err := tm.changeTypeLocked(ctx, topodatapb.TabletType_REPLICA, DBActionNone, convertBoolToSemiSyncAction(semiSync)); err != nil {
   368  			return err
   369  		}
   370  	}
   371  
   372  	pos, err := mysql.DecodePosition(position)
   373  	if err != nil {
   374  		return err
   375  	}
   376  	ti, err := tm.TopoServer.GetTablet(ctx, parent)
   377  	if err != nil {
   378  		return err
   379  	}
   380  
   381  	// If using semi-sync, we need to enable it before connecting to primary.
   382  	// If we were a primary type, we need to switch back to replica settings.
   383  	// Otherwise we won't be able to commit anything.
   384  	tt := tm.Tablet().Type
   385  	if tt == topodatapb.TabletType_PRIMARY {
   386  		tt = topodatapb.TabletType_REPLICA
   387  	}
   388  	if err := tm.fixSemiSync(tt, convertBoolToSemiSyncAction(semiSync)); err != nil {
   389  		return err
   390  	}
   391  
   392  	if err := tm.MysqlDaemon.SetReplicationPosition(ctx, pos); err != nil {
   393  		return err
   394  	}
   395  	if err := tm.MysqlDaemon.SetReplicationSource(ctx, ti.Tablet.MysqlHostname, int(ti.Tablet.MysqlPort), false /* stopReplicationBefore */, true /* startReplicationAfter */); err != nil {
   396  		return err
   397  	}
   398  
   399  	// wait until we get the replicated row, or our context times out
   400  	return tm.MysqlDaemon.WaitForReparentJournal(ctx, timeCreatedNS)
   401  }
   402  
   403  // DemotePrimary prepares a PRIMARY tablet to give up leadership to another tablet.
   404  //
   405  // It attemps to idempotently ensure the following guarantees upon returning
   406  // successfully:
   407  //   - No future writes will be accepted.
   408  //   - No writes are in-flight.
   409  //   - MySQL is in read-only mode.
   410  //   - Semi-sync settings are consistent with a REPLICA tablet.
   411  //
   412  // If necessary, it waits for all in-flight writes to complete or time out.
   413  //
   414  // It should be safe to call this on a PRIMARY tablet that was already demoted,
   415  // or on a tablet that already transitioned to REPLICA.
   416  //
   417  // If a step fails in the middle, it will try to undo any changes it made.
   418  func (tm *TabletManager) DemotePrimary(ctx context.Context) (*replicationdatapb.PrimaryStatus, error) {
   419  	log.Infof("DemotePrimary")
   420  	// The public version always reverts on partial failure.
   421  	return tm.demotePrimary(ctx, true /* revertPartialFailure */)
   422  }
   423  
   424  // demotePrimary implements DemotePrimary with an additional, private option.
   425  //
   426  // If revertPartialFailure is true, and a step fails in the middle, it will try
   427  // to undo any changes it made.
   428  func (tm *TabletManager) demotePrimary(ctx context.Context, revertPartialFailure bool) (primaryStatus *replicationdatapb.PrimaryStatus, finalErr error) {
   429  	if err := tm.lock(ctx); err != nil {
   430  		return nil, err
   431  	}
   432  	defer tm.unlock()
   433  
   434  	tablet := tm.Tablet()
   435  	wasPrimary := tablet.Type == topodatapb.TabletType_PRIMARY
   436  	wasServing := tm.QueryServiceControl.IsServing()
   437  	wasReadOnly, err := tm.MysqlDaemon.IsReadOnly()
   438  	if err != nil {
   439  		return nil, err
   440  	}
   441  
   442  	// If we are a primary tablet and not yet read-only, stop accepting new
   443  	// queries and wait for in-flight queries to complete. If we are not primary,
   444  	// or if we are already read-only, there's no need to stop the queryservice
   445  	// in order to ensure the guarantee we are being asked to provide, which is
   446  	// that no writes are occurring.
   447  	if wasPrimary && !wasReadOnly {
   448  		// Note that this may block until the transaction timeout if clients
   449  		// don't finish their transactions in time. Even if some transactions
   450  		// have to be killed at the end of their timeout, this will be
   451  		// considered successful. If we are already not serving, this will be
   452  		// idempotent.
   453  		log.Infof("DemotePrimary disabling query service")
   454  		if err := tm.QueryServiceControl.SetServingType(tablet.Type, logutil.ProtoToTime(tablet.PrimaryTermStartTime), false, "demotion in progress"); err != nil {
   455  			return nil, vterrors.Wrap(err, "SetServingType(serving=false) failed")
   456  		}
   457  		defer func() {
   458  			if finalErr != nil && revertPartialFailure && wasServing {
   459  				if err := tm.QueryServiceControl.SetServingType(tablet.Type, logutil.ProtoToTime(tablet.PrimaryTermStartTime), true, ""); err != nil {
   460  					log.Warningf("SetServingType(serving=true) failed during revert: %v", err)
   461  				}
   462  			}
   463  		}()
   464  	}
   465  
   466  	// Now that we know no writes are in-flight and no new writes can occur,
   467  	// set MySQL to read-only mode. If we are already read-only because of a
   468  	// previous demotion, or because we are not primary anyway, this should be
   469  	// idempotent.
   470  	if setSuperReadOnly {
   471  		// Setting super_read_only also sets read_only
   472  		if err := tm.MysqlDaemon.SetSuperReadOnly(true); err != nil {
   473  			if strings.Contains(err.Error(), strconv.Itoa(mysql.ERUnknownSystemVariable)) {
   474  				log.Warningf("server does not know about super_read_only, continuing anyway...")
   475  			} else {
   476  				return nil, err
   477  			}
   478  		}
   479  	} else {
   480  		if err := tm.MysqlDaemon.SetReadOnly(true); err != nil {
   481  			return nil, err
   482  		}
   483  	}
   484  	defer func() {
   485  		if finalErr != nil && revertPartialFailure && !wasReadOnly {
   486  			// setting read_only OFF will also set super_read_only OFF if it was set
   487  			if err := tm.MysqlDaemon.SetReadOnly(false); err != nil {
   488  				log.Warningf("SetReadOnly(false) failed during revert: %v", err)
   489  			}
   490  		}
   491  	}()
   492  
   493  	// Here, we check if the primary side semi sync is enabled or not. If it isn't enabled then we do not need to take any action.
   494  	// If it is enabled then we should turn it off and revert in case of failure.
   495  	if tm.isPrimarySideSemiSyncEnabled() {
   496  		// If using semi-sync, we need to disable primary-side.
   497  		if err := tm.fixSemiSync(topodatapb.TabletType_REPLICA, SemiSyncActionSet); err != nil {
   498  			return nil, err
   499  		}
   500  		defer func() {
   501  			if finalErr != nil && revertPartialFailure && wasPrimary {
   502  				// enable primary-side semi-sync again
   503  				if err := tm.fixSemiSync(topodatapb.TabletType_PRIMARY, SemiSyncActionSet); err != nil {
   504  					log.Warningf("fixSemiSync(PRIMARY) failed during revert: %v", err)
   505  				}
   506  			}
   507  		}()
   508  	}
   509  
   510  	// Return the current replication position.
   511  	status, err := tm.MysqlDaemon.PrimaryStatus(ctx)
   512  	if err != nil {
   513  		return nil, err
   514  	}
   515  	return mysql.PrimaryStatusToProto(status), nil
   516  }
   517  
   518  // UndoDemotePrimary reverts a previous call to DemotePrimary
   519  // it sets read-only to false, fixes semi-sync
   520  // and returns its primary position.
   521  func (tm *TabletManager) UndoDemotePrimary(ctx context.Context, semiSync bool) error {
   522  	log.Infof("UndoDemotePrimary")
   523  	if err := tm.lock(ctx); err != nil {
   524  		return err
   525  	}
   526  	defer tm.unlock()
   527  
   528  	// If using semi-sync, we need to enable source-side.
   529  	if err := tm.fixSemiSync(topodatapb.TabletType_PRIMARY, convertBoolToSemiSyncAction(semiSync)); err != nil {
   530  		return err
   531  	}
   532  
   533  	// Now, set the server read-only false.
   534  	if err := tm.MysqlDaemon.SetReadOnly(false); err != nil {
   535  		return err
   536  	}
   537  
   538  	// Update serving graph
   539  	tablet := tm.Tablet()
   540  	log.Infof("UndoDemotePrimary re-enabling query service")
   541  	if err := tm.QueryServiceControl.SetServingType(tablet.Type, logutil.ProtoToTime(tablet.PrimaryTermStartTime), true, ""); err != nil {
   542  		return vterrors.Wrap(err, "SetServingType(serving=true) failed")
   543  	}
   544  	return nil
   545  }
   546  
   547  // ReplicaWasPromoted promotes a replica to primary, no questions asked.
   548  func (tm *TabletManager) ReplicaWasPromoted(ctx context.Context) error {
   549  	log.Infof("ReplicaWasPromoted")
   550  	if err := tm.lock(ctx); err != nil {
   551  		return err
   552  	}
   553  	defer tm.unlock()
   554  	return tm.changeTypeLocked(ctx, topodatapb.TabletType_PRIMARY, DBActionNone, SemiSyncActionNone)
   555  }
   556  
   557  // ResetReplicationParameters resets the replica replication parameters
   558  func (tm *TabletManager) ResetReplicationParameters(ctx context.Context) error {
   559  	log.Infof("ResetReplicationParameters")
   560  	if err := tm.lock(ctx); err != nil {
   561  		return err
   562  	}
   563  	defer tm.unlock()
   564  
   565  	err := tm.MysqlDaemon.StopReplication(tm.hookExtraEnv())
   566  	if err != nil {
   567  		return err
   568  	}
   569  
   570  	err = tm.MysqlDaemon.ResetReplicationParameters(ctx)
   571  	if err != nil {
   572  		return err
   573  	}
   574  	return nil
   575  }
   576  
   577  // SetReplicationSource sets replication primary, and waits for the
   578  // reparent_journal table entry up to context timeout
   579  func (tm *TabletManager) SetReplicationSource(ctx context.Context, parentAlias *topodatapb.TabletAlias, timeCreatedNS int64, waitPosition string, forceStartReplication bool, semiSync bool) error {
   580  	log.Infof("SetReplicationSource: parent: %v  position: %s force: %v semiSync: %v timeCreatedNS: %d", parentAlias, waitPosition, forceStartReplication, semiSync, timeCreatedNS)
   581  	if err := tm.lock(ctx); err != nil {
   582  		return err
   583  	}
   584  	defer tm.unlock()
   585  
   586  	// setReplicationSourceLocked also fixes the semi-sync. In case the tablet type is primary it assumes that it will become a replica if SetReplicationSource
   587  	// is called, so we always call fixSemiSync with a non-primary tablet type. This will always set the source side replication to false.
   588  	return tm.setReplicationSourceLocked(ctx, parentAlias, timeCreatedNS, waitPosition, forceStartReplication, convertBoolToSemiSyncAction(semiSync))
   589  }
   590  
   591  func (tm *TabletManager) setReplicationSourceRepairReplication(ctx context.Context, parentAlias *topodatapb.TabletAlias, timeCreatedNS int64, waitPosition string, forceStartReplication bool) (err error) {
   592  	parent, err := tm.TopoServer.GetTablet(ctx, parentAlias)
   593  	if err != nil {
   594  		return err
   595  	}
   596  
   597  	ctx, unlock, lockErr := tm.TopoServer.LockShard(ctx, parent.Tablet.GetKeyspace(), parent.Tablet.GetShard(), fmt.Sprintf("repairReplication to %v as parent)", topoproto.TabletAliasString(parentAlias)))
   598  	if lockErr != nil {
   599  		return lockErr
   600  	}
   601  
   602  	defer unlock(&err)
   603  
   604  	return tm.setReplicationSourceLocked(ctx, parentAlias, timeCreatedNS, waitPosition, forceStartReplication, SemiSyncActionNone)
   605  }
   606  
   607  func (tm *TabletManager) setReplicationSourceSemiSyncNoAction(ctx context.Context, parentAlias *topodatapb.TabletAlias, timeCreatedNS int64, waitPosition string, forceStartReplication bool) error {
   608  	log.Infof("SetReplicationSource: parent: %v  position: %v force: %v", parentAlias, waitPosition, forceStartReplication)
   609  	if err := tm.lock(ctx); err != nil {
   610  		return err
   611  	}
   612  	defer tm.unlock()
   613  
   614  	return tm.setReplicationSourceLocked(ctx, parentAlias, timeCreatedNS, waitPosition, forceStartReplication, SemiSyncActionNone)
   615  }
   616  
   617  func (tm *TabletManager) setReplicationSourceLocked(ctx context.Context, parentAlias *topodatapb.TabletAlias, timeCreatedNS int64, waitPosition string, forceStartReplication bool, semiSync SemiSyncAction) (err error) {
   618  	// Change our type to REPLICA if we used to be PRIMARY.
   619  	// Being sent SetReplicationSource means another PRIMARY has been successfully promoted,
   620  	// so we convert to REPLICA first, since we want to do it even if other
   621  	// steps fail below.
   622  	// Note it is important to check for PRIMARY here so that we don't
   623  	// unintentionally change the type of RDONLY tablets
   624  	tablet := tm.Tablet()
   625  	if tablet.Type == topodatapb.TabletType_PRIMARY {
   626  		if err := tm.tmState.ChangeTabletType(ctx, topodatapb.TabletType_REPLICA, DBActionNone); err != nil {
   627  			return err
   628  		}
   629  	}
   630  
   631  	// See if we were replicating at all, and should be replicating.
   632  	wasReplicating := false
   633  	shouldbeReplicating := false
   634  	status, err := tm.MysqlDaemon.ReplicationStatus()
   635  	if err == mysql.ErrNotReplica {
   636  		// This is a special error that means we actually succeeded in reading
   637  		// the status, but the status is empty because replication is not
   638  		// configured. We assume this means we used to be a primary, so we always
   639  		// try to start replicating once we are told who the new primary is.
   640  		shouldbeReplicating = true
   641  		// Since we continue in the case of this error, make sure 'status' is
   642  		// in a known, empty state.
   643  		status = mysql.ReplicationStatus{}
   644  	} else if err != nil {
   645  		// Abort on any other non-nil error.
   646  		return err
   647  	}
   648  	if status.IOHealthy() || status.SQLHealthy() {
   649  		wasReplicating = true
   650  		shouldbeReplicating = true
   651  	}
   652  	if forceStartReplication {
   653  		shouldbeReplicating = true
   654  	}
   655  
   656  	// If using semi-sync, we need to enable it before connecting to primary.
   657  	// If we are currently PRIMARY, assume we are about to become REPLICA.
   658  	tabletType := tm.Tablet().Type
   659  	if tabletType == topodatapb.TabletType_PRIMARY {
   660  		tabletType = topodatapb.TabletType_REPLICA
   661  	}
   662  	if err := tm.fixSemiSync(tabletType, semiSync); err != nil {
   663  		return err
   664  	}
   665  	// Update the primary/source address only if needed.
   666  	// We don't want to interrupt replication for no reason.
   667  	if parentAlias == nil {
   668  		// if there is no primary in the shard, return an error so that we can retry
   669  		return vterrors.New(vtrpc.Code_FAILED_PRECONDITION, "Shard primaryAlias is nil")
   670  	}
   671  	parent, err := tm.TopoServer.GetTablet(ctx, parentAlias)
   672  	if err != nil {
   673  		return err
   674  	}
   675  	host := parent.Tablet.MysqlHostname
   676  	port := int(parent.Tablet.MysqlPort)
   677  	// We want to reset the replication parameters and set replication source again when forceStartReplication is provided
   678  	// because sometimes MySQL gets stuck due to improper initialization of master info structure or related failures and throws errors like
   679  	// ERROR 1201 (HY000): Could not initialize master info structure; more error messages can be found in the MySQL error log
   680  	// These errors can only be resolved by resetting the replication parameters, otherwise START SLAVE fails. So when this RPC
   681  	// gets called from VTOrc or replication manager to fix the replication in these cases with forceStartReplication, we should also
   682  	// reset the replication parameters and set the source port information again.
   683  	if status.SourceHost != host || status.SourcePort != port || forceStartReplication {
   684  		// This handles reseting the replication parameters, changing the address and then starting the replication.
   685  		if err := tm.MysqlDaemon.SetReplicationSource(ctx, host, port, wasReplicating, shouldbeReplicating); err != nil {
   686  			if err := tm.handleRelayLogError(err); err != nil {
   687  				return err
   688  			}
   689  		}
   690  	} else if shouldbeReplicating {
   691  		// The address is correct. We need to restart replication so that any semi-sync changes if any
   692  		// are taken into account
   693  		if err := tm.MysqlDaemon.StopReplication(tm.hookExtraEnv()); err != nil {
   694  			if err := tm.handleRelayLogError(err); err != nil {
   695  				return err
   696  			}
   697  		}
   698  		if err := tm.MysqlDaemon.StartReplication(tm.hookExtraEnv()); err != nil {
   699  			if err := tm.handleRelayLogError(err); err != nil {
   700  				return err
   701  			}
   702  		}
   703  	}
   704  
   705  	// If needed, wait until we replicate to the specified point, or our context
   706  	// times out. Callers can specify the point to wait for as either a
   707  	// GTID-based replication position or a Vitess reparent journal entry,
   708  	// or both.
   709  	if shouldbeReplicating {
   710  		log.Infof("Set up MySQL replication; should now be replicating from %s at %s", parentAlias, waitPosition)
   711  		if waitPosition != "" {
   712  			pos, err := mysql.DecodePosition(waitPosition)
   713  			if err != nil {
   714  				return err
   715  			}
   716  			if err := tm.MysqlDaemon.WaitSourcePos(ctx, pos); err != nil {
   717  				return err
   718  			}
   719  		}
   720  		if timeCreatedNS != 0 {
   721  			if err := tm.MysqlDaemon.WaitForReparentJournal(ctx, timeCreatedNS); err != nil {
   722  				return err
   723  			}
   724  		}
   725  	}
   726  
   727  	return nil
   728  }
   729  
   730  // ReplicaWasRestarted updates the parent record for a tablet.
   731  func (tm *TabletManager) ReplicaWasRestarted(ctx context.Context, parent *topodatapb.TabletAlias) error {
   732  	log.Infof("ReplicaWasRestarted: parent: %v", parent)
   733  	if err := tm.lock(ctx); err != nil {
   734  		return err
   735  	}
   736  	defer tm.unlock()
   737  
   738  	// Only change type of former PRIMARY tablets.
   739  	// Don't change type of RDONLY
   740  	tablet := tm.Tablet()
   741  	if tablet.Type != topodatapb.TabletType_PRIMARY {
   742  		return nil
   743  	}
   744  	return tm.tmState.ChangeTabletType(ctx, topodatapb.TabletType_REPLICA, DBActionNone)
   745  }
   746  
   747  // StopReplicationAndGetStatus stops MySQL replication, and returns the
   748  // current status.
   749  func (tm *TabletManager) StopReplicationAndGetStatus(ctx context.Context, stopReplicationMode replicationdatapb.StopReplicationMode) (StopReplicationAndGetStatusResponse, error) {
   750  	log.Infof("StopReplicationAndGetStatus: mode: %v", stopReplicationMode)
   751  	if err := tm.lock(ctx); err != nil {
   752  		return StopReplicationAndGetStatusResponse{}, err
   753  	}
   754  	defer tm.unlock()
   755  
   756  	// Get the status before we stop replication.
   757  	// Doing this first allows us to return the status in the case that stopping replication
   758  	// returns an error, so a user can optionally inspect the status before a stop was called.
   759  	rs, err := tm.MysqlDaemon.ReplicationStatus()
   760  	if err != nil {
   761  		return StopReplicationAndGetStatusResponse{}, vterrors.Wrap(err, "before status failed")
   762  	}
   763  	before := mysql.ReplicationStatusToProto(rs)
   764  
   765  	if stopReplicationMode == replicationdatapb.StopReplicationMode_IOTHREADONLY {
   766  		if !rs.IOHealthy() {
   767  			return StopReplicationAndGetStatusResponse{
   768  				Status: &replicationdatapb.StopReplicationStatus{
   769  					Before: before,
   770  					After:  before,
   771  				},
   772  			}, nil
   773  		}
   774  		if err := tm.stopIOThreadLocked(ctx); err != nil {
   775  			return StopReplicationAndGetStatusResponse{
   776  				Status: &replicationdatapb.StopReplicationStatus{
   777  					Before: before,
   778  				},
   779  			}, vterrors.Wrap(err, "stop io thread failed")
   780  		}
   781  	} else {
   782  		if !rs.Healthy() {
   783  			// no replication is running, just return what we got
   784  			return StopReplicationAndGetStatusResponse{
   785  				Status: &replicationdatapb.StopReplicationStatus{
   786  					Before: before,
   787  					After:  before,
   788  				},
   789  			}, nil
   790  		}
   791  		if err := tm.stopReplicationLocked(ctx); err != nil {
   792  			return StopReplicationAndGetStatusResponse{
   793  				Status: &replicationdatapb.StopReplicationStatus{
   794  					Before: before,
   795  				},
   796  			}, vterrors.Wrap(err, "stop replication failed")
   797  		}
   798  	}
   799  
   800  	// Get the status after we stop replication so we have up to date position and relay log positions.
   801  	rsAfter, err := tm.MysqlDaemon.ReplicationStatus()
   802  	if err != nil {
   803  		return StopReplicationAndGetStatusResponse{
   804  			Status: &replicationdatapb.StopReplicationStatus{
   805  				Before: before,
   806  			},
   807  		}, vterrors.Wrap(err, "acquiring replication status failed")
   808  	}
   809  	after := mysql.ReplicationStatusToProto(rsAfter)
   810  
   811  	rs.Position = rsAfter.Position
   812  	rs.RelayLogPosition = rsAfter.RelayLogPosition
   813  	rs.FilePosition = rsAfter.FilePosition
   814  	rs.RelayLogSourceBinlogEquivalentPosition = rsAfter.RelayLogSourceBinlogEquivalentPosition
   815  
   816  	return StopReplicationAndGetStatusResponse{
   817  		Status: &replicationdatapb.StopReplicationStatus{
   818  			Before: before,
   819  			After:  after,
   820  		},
   821  	}, nil
   822  }
   823  
   824  // StopReplicationAndGetStatusResponse holds the original hybrid Status struct, as well as a new Status field, which
   825  // hold the result of show replica status called before stopping replication, and after stopping replication.
   826  type StopReplicationAndGetStatusResponse struct {
   827  	// Status represents the replication status call right before, and right after telling the replica to stop.
   828  	Status *replicationdatapb.StopReplicationStatus
   829  }
   830  
   831  // PromoteReplica makes the current tablet the primary
   832  func (tm *TabletManager) PromoteReplica(ctx context.Context, semiSync bool) (string, error) {
   833  	log.Infof("PromoteReplica")
   834  	if err := tm.lock(ctx); err != nil {
   835  		return "", err
   836  	}
   837  	defer tm.unlock()
   838  
   839  	pos, err := tm.MysqlDaemon.Promote(tm.hookExtraEnv())
   840  	if err != nil {
   841  		return "", err
   842  	}
   843  
   844  	// If using semi-sync, we need to enable it before going read-write.
   845  	if err := tm.fixSemiSync(topodatapb.TabletType_PRIMARY, convertBoolToSemiSyncAction(semiSync)); err != nil {
   846  		return "", err
   847  	}
   848  
   849  	if err := tm.changeTypeLocked(ctx, topodatapb.TabletType_PRIMARY, DBActionSetReadWrite, SemiSyncActionNone); err != nil {
   850  		return "", err
   851  	}
   852  	return mysql.EncodePosition(pos), nil
   853  }
   854  
   855  func isPrimaryEligible(tabletType topodatapb.TabletType) bool {
   856  	switch tabletType {
   857  	case topodatapb.TabletType_PRIMARY, topodatapb.TabletType_REPLICA:
   858  		return true
   859  	}
   860  
   861  	return false
   862  }
   863  
   864  func (tm *TabletManager) fixSemiSync(tabletType topodatapb.TabletType, semiSync SemiSyncAction) error {
   865  	switch semiSync {
   866  	case SemiSyncActionNone:
   867  		return nil
   868  	case SemiSyncActionSet:
   869  		// Always enable replica-side since it doesn't hurt to keep it on for a primary.
   870  		// The primary-side needs to be off for a replica, or else it will get stuck.
   871  		return tm.MysqlDaemon.SetSemiSyncEnabled(tabletType == topodatapb.TabletType_PRIMARY, true)
   872  	case SemiSyncActionUnset:
   873  		return tm.MysqlDaemon.SetSemiSyncEnabled(false, false)
   874  	default:
   875  		return vterrors.Errorf(vtrpc.Code_INTERNAL, "Unknown SemiSyncAction - %v", semiSync)
   876  	}
   877  }
   878  
   879  func (tm *TabletManager) isPrimarySideSemiSyncEnabled() bool {
   880  	semiSyncEnabled, _ := tm.MysqlDaemon.SemiSyncEnabled()
   881  	return semiSyncEnabled
   882  }
   883  
   884  func (tm *TabletManager) fixSemiSyncAndReplication(tabletType topodatapb.TabletType, semiSync SemiSyncAction) error {
   885  	if semiSync == SemiSyncActionNone {
   886  		// Semi-sync handling is not required.
   887  		return nil
   888  	}
   889  
   890  	if tabletType == topodatapb.TabletType_PRIMARY {
   891  		// Primary is special. It is always handled at the
   892  		// right time by the reparent operations, it doesn't
   893  		// need to be fixed.
   894  		return nil
   895  	}
   896  
   897  	if err := tm.fixSemiSync(tabletType, semiSync); err != nil {
   898  		return vterrors.Wrapf(err, "failed to fixSemiSync(%v)", tabletType)
   899  	}
   900  
   901  	// If replication is running, but the status is wrong,
   902  	// we should restart replication. First, let's make sure
   903  	// replication is running.
   904  	status, err := tm.MysqlDaemon.ReplicationStatus()
   905  	if err != nil {
   906  		// Replication is not configured, nothing to do.
   907  		return nil
   908  	}
   909  	if !status.IOHealthy() {
   910  		// IO thread is not running, nothing to do.
   911  		return nil
   912  	}
   913  
   914  	//shouldAck := semiSync == SemiSyncActionSet
   915  	shouldAck := isPrimaryEligible(tabletType)
   916  	acking, err := tm.MysqlDaemon.SemiSyncReplicationStatus()
   917  	if err != nil {
   918  		return vterrors.Wrap(err, "failed to get SemiSyncReplicationStatus")
   919  	}
   920  	if shouldAck == acking {
   921  		return nil
   922  	}
   923  
   924  	// We need to restart replication
   925  	log.Infof("Restarting replication for semi-sync flag change to take effect from %v to %v", acking, shouldAck)
   926  	if err := tm.MysqlDaemon.StopReplication(tm.hookExtraEnv()); err != nil {
   927  		return vterrors.Wrap(err, "failed to StopReplication")
   928  	}
   929  	if err := tm.MysqlDaemon.StartReplication(tm.hookExtraEnv()); err != nil {
   930  		return vterrors.Wrap(err, "failed to StartReplication")
   931  	}
   932  	return nil
   933  }
   934  
   935  func (tm *TabletManager) handleRelayLogError(err error) error {
   936  	// attempt to fix this error:
   937  	// Slave failed to initialize relay log info structure from the repository (errno 1872) (sqlstate HY000) during query: START SLAVE
   938  	// see https://bugs.mysql.com/bug.php?id=83713 or https://github.com/vitessio/vitess/issues/5067
   939  	if strings.Contains(err.Error(), "Slave failed to initialize relay log info structure from the repository") {
   940  		// Stop, reset and start replication again to resolve this error
   941  		if err := tm.MysqlDaemon.RestartReplication(tm.hookExtraEnv()); err != nil {
   942  			return err
   943  		}
   944  		return nil
   945  	}
   946  	return err
   947  }
   948  
   949  // repairReplication tries to connect this server to whoever is
   950  // the current primary of the shard, and start replicating.
   951  func (tm *TabletManager) repairReplication(ctx context.Context) error {
   952  	tablet := tm.Tablet()
   953  
   954  	si, err := tm.TopoServer.GetShard(ctx, tablet.Keyspace, tablet.Shard)
   955  	if err != nil {
   956  		return err
   957  	}
   958  	if !si.HasPrimary() {
   959  		return fmt.Errorf("no primary tablet for shard %v/%v", tablet.Keyspace, tablet.Shard)
   960  	}
   961  
   962  	if topoproto.TabletAliasEqual(si.PrimaryAlias, tablet.Alias) {
   963  		// The shard record says we are primary, but we disagree; we wouldn't
   964  		// reach this point unless we were told to check replication.
   965  		// Hopefully someone is working on fixing that, but in any case,
   966  		// we should not try to reparent to ourselves.
   967  		return fmt.Errorf("shard %v/%v record claims tablet %v is primary, but its type is %v", tablet.Keyspace, tablet.Shard, topoproto.TabletAliasString(tablet.Alias), tablet.Type)
   968  	}
   969  	return tm.setReplicationSourceRepairReplication(ctx, si.PrimaryAlias, 0, "", true)
   970  }