vitess.io/vitess@v0.16.2/go/vt/wrangler/testlib/backup_test.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 testlib
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"os"
    23  	"path"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/stretchr/testify/assert"
    28  	"github.com/stretchr/testify/require"
    29  
    30  	"vitess.io/vitess/go/vt/discovery"
    31  
    32  	"vitess.io/vitess/go/mysql"
    33  	"vitess.io/vitess/go/mysql/fakesqldb"
    34  	"vitess.io/vitess/go/sqltypes"
    35  	"vitess.io/vitess/go/vt/logutil"
    36  	"vitess.io/vitess/go/vt/mysqlctl"
    37  	"vitess.io/vitess/go/vt/mysqlctl/backupstorage"
    38  	"vitess.io/vitess/go/vt/mysqlctl/filebackupstorage"
    39  	"vitess.io/vitess/go/vt/topo"
    40  	"vitess.io/vitess/go/vt/topo/memorytopo"
    41  	"vitess.io/vitess/go/vt/topo/topoproto"
    42  	"vitess.io/vitess/go/vt/vttablet/tmclient"
    43  	"vitess.io/vitess/go/vt/wrangler"
    44  
    45  	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
    46  )
    47  
    48  type compressionDetails struct {
    49  	CompressionEngineName   string
    50  	ExternalCompressorCmd   string
    51  	ExternalCompressorExt   string
    52  	ExternalDecompressorCmd string
    53  }
    54  
    55  func TestBackupRestore(t *testing.T) {
    56  	defer setDefaultCompressionFlag()
    57  	err := testBackupRestore(t, nil)
    58  	require.NoError(t, err)
    59  }
    60  
    61  func TestBackupRestoreWithPargzip(t *testing.T) {
    62  	defer setDefaultCompressionFlag()
    63  	cDetails := &compressionDetails{
    64  		CompressionEngineName: "pargzip",
    65  	}
    66  
    67  	err := testBackupRestore(t, cDetails)
    68  	require.NoError(t, err)
    69  }
    70  
    71  func setDefaultCompressionFlag() {
    72  	mysqlctl.CompressionEngineName = "pgzip"
    73  	mysqlctl.ExternalCompressorCmd = ""
    74  	mysqlctl.ExternalCompressorExt = ""
    75  	mysqlctl.ExternalDecompressorCmd = ""
    76  }
    77  
    78  func testBackupRestore(t *testing.T, cDetails *compressionDetails) error {
    79  	delay := discovery.GetTabletPickerRetryDelay()
    80  	defer func() {
    81  		discovery.SetTabletPickerRetryDelay(delay)
    82  	}()
    83  	discovery.SetTabletPickerRetryDelay(5 * time.Millisecond)
    84  
    85  	// Initialize our environment
    86  	ctx := context.Background()
    87  	db := fakesqldb.New(t)
    88  	defer db.Close()
    89  	ts := memorytopo.NewServer("cell1", "cell2")
    90  	wr := wrangler.New(logutil.NewConsoleLogger(), ts, tmclient.NewTabletManagerClient())
    91  	vp := NewVtctlPipe(t, ts)
    92  	defer vp.Close()
    93  
    94  	// Set up mock query results.
    95  	db.AddQuery(mysqlctl.GenerateInitialBinlogEntry(), &sqltypes.Result{})
    96  	db.AddQuery("BEGIN", &sqltypes.Result{})
    97  	db.AddQuery("COMMIT", &sqltypes.Result{})
    98  	db.AddQueryPattern(`SET @@session\.sql_log_bin = .*`, &sqltypes.Result{})
    99  
   100  	// Initialize our temp dirs
   101  	root := t.TempDir()
   102  
   103  	// Initialize BackupStorage
   104  	fbsRoot := path.Join(root, "fbs")
   105  	filebackupstorage.FileBackupStorageRoot = fbsRoot
   106  	backupstorage.BackupStorageImplementation = "file"
   107  	if cDetails != nil {
   108  		if cDetails.CompressionEngineName != "" {
   109  			mysqlctl.CompressionEngineName = cDetails.CompressionEngineName
   110  		}
   111  		if cDetails.ExternalCompressorCmd != "" {
   112  			mysqlctl.ExternalCompressorCmd = cDetails.ExternalCompressorCmd
   113  		}
   114  		if cDetails.ExternalCompressorExt != "" {
   115  			mysqlctl.ExternalCompressorExt = cDetails.ExternalCompressorExt
   116  		}
   117  		if cDetails.ExternalDecompressorCmd != "" {
   118  			mysqlctl.ExternalDecompressorCmd = cDetails.ExternalDecompressorCmd
   119  		}
   120  	}
   121  
   122  	// Initialize the fake mysql root directories
   123  	sourceInnodbDataDir := path.Join(root, "source_innodb_data")
   124  	sourceInnodbLogDir := path.Join(root, "source_innodb_log")
   125  	sourceDataDir := path.Join(root, "source_data")
   126  	sourceDataDbDir := path.Join(sourceDataDir, "vt_db")
   127  	for _, s := range []string{sourceInnodbDataDir, sourceInnodbLogDir, sourceDataDbDir} {
   128  		require.NoError(t, os.MkdirAll(s, os.ModePerm))
   129  	}
   130  
   131  	needIt, err := needInnoDBRedoLogSubdir()
   132  	require.NoError(t, err)
   133  	if needIt {
   134  		newPath := path.Join(sourceInnodbLogDir, mysql.DynamicRedoLogSubdir)
   135  		require.NoError(t, os.Mkdir(newPath, os.ModePerm))
   136  		require.NoError(t, os.WriteFile(path.Join(newPath, "#ib_redo1"), []byte("innodb log 1 contents"), os.ModePerm))
   137  	} else {
   138  		require.NoError(t, os.WriteFile(path.Join(sourceInnodbLogDir, "innodb_log_1"), []byte("innodb log 1 contents"), os.ModePerm))
   139  	}
   140  
   141  	require.NoError(t, os.WriteFile(path.Join(sourceInnodbDataDir, "innodb_data_1"), []byte("innodb data 1 contents"), os.ModePerm))
   142  	require.NoError(t, os.WriteFile(path.Join(sourceDataDbDir, "db.opt"), []byte("db opt file"), os.ModePerm))
   143  
   144  	// create a primary tablet, set its primary position
   145  	primary := NewFakeTablet(t, wr, "cell1", 0, topodatapb.TabletType_PRIMARY, db)
   146  	primary.FakeMysqlDaemon.ReadOnly = false
   147  	primary.FakeMysqlDaemon.Replicating = false
   148  	primary.FakeMysqlDaemon.CurrentPrimaryPosition = mysql.Position{
   149  		GTIDSet: mysql.MariadbGTIDSet{
   150  			2: mysql.MariadbGTID{
   151  				Domain:   2,
   152  				Server:   123,
   153  				Sequence: 457,
   154  			},
   155  		},
   156  	}
   157  
   158  	// start primary so that replica can fetch primary position from it
   159  	primary.StartActionLoop(t, wr)
   160  	defer primary.StopActionLoop(t)
   161  
   162  	// create a single tablet, set it up so we can do backups
   163  	// set its position same as that of primary so that backup doesn't wait for catchup
   164  	sourceTablet := NewFakeTablet(t, wr, "cell1", 1, topodatapb.TabletType_REPLICA, db)
   165  	sourceTablet.FakeMysqlDaemon.ReadOnly = true
   166  	sourceTablet.FakeMysqlDaemon.Replicating = true
   167  	sourceTablet.FakeMysqlDaemon.SetReplicationSourceInputs = []string{fmt.Sprintf("%s:%d", primary.Tablet.MysqlHostname, primary.Tablet.MysqlPort)}
   168  	sourceTablet.FakeMysqlDaemon.CurrentPrimaryPosition = mysql.Position{
   169  		GTIDSet: mysql.MariadbGTIDSet{
   170  			2: mysql.MariadbGTID{
   171  				Domain:   2,
   172  				Server:   123,
   173  				Sequence: 457,
   174  			},
   175  		},
   176  	}
   177  	sourceTablet.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{
   178  		// These 4 statements come from tablet startup
   179  		"STOP SLAVE",
   180  		"RESET SLAVE ALL",
   181  		"FAKE SET MASTER",
   182  		"START SLAVE",
   183  		// This first set of STOP and START commands come from
   184  		// the builtinBackupEngine implementation which stops the replication
   185  		// while taking the backup
   186  		"STOP SLAVE",
   187  		"START SLAVE",
   188  		// These commands come from SetReplicationSource RPC called
   189  		// to set the correct primary and semi-sync after Backup has concluded
   190  		"STOP SLAVE",
   191  		"RESET SLAVE ALL",
   192  		"FAKE SET MASTER",
   193  		"START SLAVE",
   194  	}
   195  	sourceTablet.StartActionLoop(t, wr)
   196  	defer sourceTablet.StopActionLoop(t)
   197  
   198  	sourceTablet.TM.Cnf = &mysqlctl.Mycnf{
   199  		DataDir:               sourceDataDir,
   200  		InnodbDataHomeDir:     sourceInnodbDataDir,
   201  		InnodbLogGroupHomeDir: sourceInnodbLogDir,
   202  	}
   203  
   204  	// run the backup
   205  	require.NoError(t, vp.Run([]string{"Backup", topoproto.TabletAliasString(sourceTablet.Tablet.Alias)}))
   206  
   207  	// verify the full status
   208  	require.NoError(t, sourceTablet.FakeMysqlDaemon.CheckSuperQueryList())
   209  	assert.True(t, sourceTablet.FakeMysqlDaemon.Replicating)
   210  	assert.True(t, sourceTablet.FakeMysqlDaemon.Running)
   211  
   212  	backupTime := time.Now()
   213  
   214  	// create a destination tablet, set it up so we can do restores
   215  	destTablet := NewFakeTablet(t, wr, "cell1", 2, topodatapb.TabletType_REPLICA, db)
   216  	destTablet.FakeMysqlDaemon.ReadOnly = true
   217  	destTablet.FakeMysqlDaemon.Replicating = true
   218  	destTablet.FakeMysqlDaemon.CurrentPrimaryPosition = mysql.Position{
   219  		GTIDSet: mysql.MariadbGTIDSet{
   220  			2: mysql.MariadbGTID{
   221  				Domain:   2,
   222  				Server:   123,
   223  				Sequence: 457,
   224  			},
   225  		},
   226  	}
   227  	destTablet.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{
   228  		// These 4 statements come from tablet startup
   229  		"STOP SLAVE",
   230  		"RESET SLAVE ALL",
   231  		"FAKE SET MASTER",
   232  		"START SLAVE",
   233  		"STOP SLAVE",
   234  		"RESET SLAVE ALL",
   235  		"FAKE SET SLAVE POSITION",
   236  		"STOP SLAVE",
   237  		"RESET SLAVE ALL",
   238  		"FAKE SET MASTER",
   239  		"START SLAVE",
   240  	}
   241  	destTablet.FakeMysqlDaemon.FetchSuperQueryMap = map[string]*sqltypes.Result{
   242  		"SHOW DATABASES": {},
   243  	}
   244  	destTablet.FakeMysqlDaemon.SetReplicationPositionPos = sourceTablet.FakeMysqlDaemon.CurrentPrimaryPosition
   245  	destTablet.FakeMysqlDaemon.SetReplicationSourceInputs = append(destTablet.FakeMysqlDaemon.SetReplicationSourceInputs, topoproto.MysqlAddr(primary.Tablet))
   246  
   247  	destTablet.StartActionLoop(t, wr)
   248  	defer destTablet.StopActionLoop(t)
   249  
   250  	destTablet.TM.Cnf = &mysqlctl.Mycnf{
   251  		DataDir:               sourceDataDir,
   252  		InnodbDataHomeDir:     sourceInnodbDataDir,
   253  		InnodbLogGroupHomeDir: sourceInnodbLogDir,
   254  		BinLogPath:            path.Join(root, "bin-logs/filename_prefix"),
   255  		RelayLogPath:          path.Join(root, "relay-logs/filename_prefix"),
   256  		RelayLogIndexPath:     path.Join(root, "relay-log.index"),
   257  		RelayLogInfoPath:      path.Join(root, "relay-log.info"),
   258  	}
   259  
   260  	err = destTablet.TM.RestoreData(ctx, logutil.NewConsoleLogger(), 0 /* waitForBackupInterval */, false /* deleteBeforeRestore */, time.Time{} /* backupTime */)
   261  	if err != nil {
   262  		return err
   263  	}
   264  	// verify the full status
   265  	require.NoError(t, destTablet.FakeMysqlDaemon.CheckSuperQueryList(), "destTablet.FakeMysqlDaemon.CheckSuperQueryList failed")
   266  	assert.True(t, destTablet.FakeMysqlDaemon.Replicating)
   267  	assert.True(t, destTablet.FakeMysqlDaemon.Running)
   268  
   269  	// Initialize mycnf, required for restore
   270  	primaryInnodbDataDir := path.Join(root, "primary_innodb_data")
   271  	primaryInnodbLogDir := path.Join(root, "primary_innodb_log")
   272  	primaryDataDir := path.Join(root, "primary_data")
   273  	primary.TM.Cnf = &mysqlctl.Mycnf{
   274  		DataDir:               primaryDataDir,
   275  		InnodbDataHomeDir:     primaryInnodbDataDir,
   276  		InnodbLogGroupHomeDir: primaryInnodbLogDir,
   277  		BinLogPath:            path.Join(root, "bin-logs/filename_prefix"),
   278  		RelayLogPath:          path.Join(root, "relay-logs/filename_prefix"),
   279  		RelayLogIndexPath:     path.Join(root, "relay-log.index"),
   280  		RelayLogInfoPath:      path.Join(root, "relay-log.info"),
   281  	}
   282  
   283  	primary.FakeMysqlDaemon.FetchSuperQueryMap = map[string]*sqltypes.Result{
   284  		"SHOW DATABASES": {},
   285  	}
   286  	primary.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{
   287  		"STOP SLAVE",
   288  		"RESET SLAVE ALL",
   289  		"FAKE SET SLAVE POSITION",
   290  		"RESET SLAVE ALL",
   291  		"FAKE SET MASTER",
   292  		"START SLAVE",
   293  	}
   294  
   295  	primary.FakeMysqlDaemon.SetReplicationPositionPos = primary.FakeMysqlDaemon.CurrentPrimaryPosition
   296  
   297  	// restore primary from latest backup
   298  	require.NoError(t, primary.TM.RestoreData(ctx, logutil.NewConsoleLogger(), 0 /* waitForBackupInterval */, false /* deleteBeforeRestore */, time.Time{} /* restoreFromBackupTs */),
   299  		"RestoreData failed")
   300  	// tablet was created as PRIMARY, so it's baseTabletType is PRIMARY
   301  	assert.Equal(t, topodatapb.TabletType_PRIMARY, primary.Tablet.Type)
   302  	assert.False(t, primary.FakeMysqlDaemon.Replicating)
   303  	assert.True(t, primary.FakeMysqlDaemon.Running)
   304  
   305  	// restore primary when database already exists
   306  	// checkNoDb should return false
   307  	// so fake the necessary queries
   308  	primary.FakeMysqlDaemon.FetchSuperQueryMap = map[string]*sqltypes.Result{
   309  		"SHOW DATABASES":                      {Rows: [][]sqltypes.Value{{sqltypes.NewVarBinary("vt_test_keyspace")}}},
   310  		"SHOW TABLES FROM `vt_test_keyspace`": {Rows: [][]sqltypes.Value{{sqltypes.NewVarBinary("a")}}},
   311  	}
   312  
   313  	// Test restore with the backup timestamp
   314  	require.NoError(t, primary.TM.RestoreData(ctx, logutil.NewConsoleLogger(), 0 /* waitForBackupInterval */, false /* deleteBeforeRestore */, backupTime),
   315  		"RestoreData with backup timestamp failed")
   316  	assert.Equal(t, topodatapb.TabletType_PRIMARY, primary.Tablet.Type)
   317  	assert.False(t, primary.FakeMysqlDaemon.Replicating)
   318  	assert.True(t, primary.FakeMysqlDaemon.Running)
   319  	return nil
   320  }
   321  
   322  // TestBackupRestoreLagged tests the changes made in https://github.com/vitessio/vitess/pull/5000
   323  // While doing a backup or a restore, we wait for a change of the replica's position before completing the action
   324  // This is because otherwise ReplicationLagSeconds is not accurate and the tablet may go into SERVING when it should not
   325  func TestBackupRestoreLagged(t *testing.T) {
   326  	delay := discovery.GetTabletPickerRetryDelay()
   327  	defer func() {
   328  		discovery.SetTabletPickerRetryDelay(delay)
   329  	}()
   330  	discovery.SetTabletPickerRetryDelay(5 * time.Millisecond)
   331  
   332  	// Initialize our environment
   333  	ctx := context.Background()
   334  	db := fakesqldb.New(t)
   335  	defer db.Close()
   336  	ts := memorytopo.NewServer("cell1", "cell2")
   337  	wr := wrangler.New(logutil.NewConsoleLogger(), ts, tmclient.NewTabletManagerClient())
   338  	vp := NewVtctlPipe(t, ts)
   339  	defer vp.Close()
   340  
   341  	// Set up mock query results.
   342  	db.AddQuery(mysqlctl.GenerateInitialBinlogEntry(), &sqltypes.Result{})
   343  	db.AddQuery("BEGIN", &sqltypes.Result{})
   344  	db.AddQuery("COMMIT", &sqltypes.Result{})
   345  	db.AddQueryPattern(`SET @@session\.sql_log_bin = .*`, &sqltypes.Result{})
   346  
   347  	// Initialize our temp dirs
   348  	root := t.TempDir()
   349  
   350  	// Initialize BackupStorage
   351  	fbsRoot := path.Join(root, "fbs")
   352  	filebackupstorage.FileBackupStorageRoot = fbsRoot
   353  	backupstorage.BackupStorageImplementation = "file"
   354  
   355  	// Initialize the fake mysql root directories
   356  	sourceInnodbDataDir := path.Join(root, "source_innodb_data")
   357  	sourceInnodbLogDir := path.Join(root, "source_innodb_log")
   358  	sourceDataDir := path.Join(root, "source_data")
   359  	sourceDataDbDir := path.Join(sourceDataDir, "vt_db")
   360  	for _, s := range []string{sourceInnodbDataDir, sourceInnodbLogDir, sourceDataDbDir} {
   361  		require.NoError(t, os.MkdirAll(s, os.ModePerm))
   362  	}
   363  	require.NoError(t, os.WriteFile(path.Join(sourceInnodbDataDir, "innodb_data_1"), []byte("innodb data 1 contents"), os.ModePerm))
   364  
   365  	needIt, err := needInnoDBRedoLogSubdir()
   366  	require.NoError(t, err)
   367  	if needIt {
   368  		newPath := path.Join(sourceInnodbLogDir, mysql.DynamicRedoLogSubdir)
   369  		require.NoError(t, os.Mkdir(newPath, os.ModePerm))
   370  		require.NoError(t, os.WriteFile(path.Join(newPath, "#ib_redo1"), []byte("innodb log 1 contents"), os.ModePerm))
   371  	} else {
   372  		require.NoError(t, os.WriteFile(path.Join(sourceInnodbLogDir, "innodb_log_1"), []byte("innodb log 1 contents"), os.ModePerm))
   373  	}
   374  
   375  	require.NoError(t, os.WriteFile(path.Join(sourceDataDbDir, "db.opt"), []byte("db opt file"), os.ModePerm))
   376  
   377  	// create a primary tablet, set its position
   378  	primary := NewFakeTablet(t, wr, "cell1", 0, topodatapb.TabletType_PRIMARY, db)
   379  	primary.FakeMysqlDaemon.ReadOnly = false
   380  	primary.FakeMysqlDaemon.Replicating = false
   381  	primary.FakeMysqlDaemon.CurrentPrimaryPosition = mysql.Position{
   382  		GTIDSet: mysql.MariadbGTIDSet{
   383  			2: mysql.MariadbGTID{
   384  				Domain:   2,
   385  				Server:   123,
   386  				Sequence: 457,
   387  			},
   388  		},
   389  	}
   390  
   391  	// start primary so that replica can fetch primary position from it
   392  	primary.StartActionLoop(t, wr)
   393  	defer primary.StopActionLoop(t)
   394  
   395  	// create a single tablet, set it up so we can do backups
   396  	// set its position same as that of primary so that backup doesn't wait for catchup
   397  	sourceTablet := NewFakeTablet(t, wr, "cell1", 1, topodatapb.TabletType_REPLICA, db)
   398  	sourceTablet.FakeMysqlDaemon.ReadOnly = true
   399  	sourceTablet.FakeMysqlDaemon.Replicating = true
   400  	sourceTablet.FakeMysqlDaemon.CurrentPrimaryPosition = mysql.Position{
   401  		GTIDSet: mysql.MariadbGTIDSet{
   402  			2: mysql.MariadbGTID{
   403  				Domain:   2,
   404  				Server:   123,
   405  				Sequence: 456,
   406  			},
   407  		},
   408  	}
   409  	sourceTablet.FakeMysqlDaemon.SetReplicationSourceInputs = []string{fmt.Sprintf("%s:%d", primary.Tablet.MysqlHostname, primary.Tablet.MysqlPort)}
   410  	sourceTablet.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{
   411  		// These 4 statements come from tablet startup
   412  		"STOP SLAVE",
   413  		"RESET SLAVE ALL",
   414  		"FAKE SET MASTER",
   415  		"START SLAVE",
   416  		// This first set of STOP and START commands come from
   417  		// the builtinBackupEngine implementation which stops the replication
   418  		// while taking the backup
   419  		"STOP SLAVE",
   420  		"START SLAVE",
   421  		// These commands come from SetReplicationSource RPC called
   422  		// to set the correct primary and semi-sync after Backup has concluded
   423  		"STOP SLAVE",
   424  		"RESET SLAVE ALL",
   425  		"FAKE SET MASTER",
   426  		"START SLAVE",
   427  	}
   428  	sourceTablet.StartActionLoop(t, wr)
   429  	defer sourceTablet.StopActionLoop(t)
   430  
   431  	sourceTablet.TM.Cnf = &mysqlctl.Mycnf{
   432  		DataDir:               sourceDataDir,
   433  		InnodbDataHomeDir:     sourceInnodbDataDir,
   434  		InnodbLogGroupHomeDir: sourceInnodbLogDir,
   435  	}
   436  
   437  	errCh := make(chan error, 1)
   438  	go func(ctx context.Context, tablet *FakeTablet) {
   439  		errCh <- vp.Run([]string{"Backup", topoproto.TabletAliasString(tablet.Tablet.Alias)})
   440  	}(ctx, sourceTablet)
   441  
   442  	timer := time.NewTicker(1 * time.Second)
   443  	<-timer.C
   444  	sourceTablet.FakeMysqlDaemon.CurrentPrimaryPositionLocked(mysql.Position{
   445  		GTIDSet: mysql.MariadbGTIDSet{
   446  			2: mysql.MariadbGTID{
   447  				Domain:   2,
   448  				Server:   123,
   449  				Sequence: 457,
   450  			},
   451  		},
   452  	})
   453  
   454  	timer2 := time.NewTicker(5 * time.Second)
   455  	select {
   456  	case err := <-errCh:
   457  		require.Nil(t, err)
   458  		// verify the full status
   459  		// verify the full status
   460  		require.NoError(t, sourceTablet.FakeMysqlDaemon.CheckSuperQueryList())
   461  		assert.True(t, sourceTablet.FakeMysqlDaemon.Replicating)
   462  		assert.True(t, sourceTablet.FakeMysqlDaemon.Running)
   463  		assert.Equal(t, primary.FakeMysqlDaemon.CurrentPrimaryPosition, sourceTablet.FakeMysqlDaemon.CurrentPrimaryPosition)
   464  	case <-timer2.C:
   465  		require.FailNow(t, "Backup timed out")
   466  	}
   467  
   468  	// create a destination tablet, set it up so we can do restores
   469  	destTablet := NewFakeTablet(t, wr, "cell1", 2, topodatapb.TabletType_REPLICA, db)
   470  	destTablet.FakeMysqlDaemon.ReadOnly = true
   471  	destTablet.FakeMysqlDaemon.Replicating = true
   472  	destTablet.FakeMysqlDaemon.CurrentPrimaryPosition = mysql.Position{
   473  		GTIDSet: mysql.MariadbGTIDSet{
   474  			2: mysql.MariadbGTID{
   475  				Domain:   2,
   476  				Server:   123,
   477  				Sequence: 456,
   478  			},
   479  		},
   480  	}
   481  	destTablet.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{
   482  		// These 4 statements come from tablet startup
   483  		"STOP SLAVE",
   484  		"RESET SLAVE ALL",
   485  		"FAKE SET MASTER",
   486  		"START SLAVE",
   487  		"STOP SLAVE",
   488  		"RESET SLAVE ALL",
   489  		"FAKE SET SLAVE POSITION",
   490  		"STOP SLAVE",
   491  		"RESET SLAVE ALL",
   492  		"FAKE SET MASTER",
   493  		"START SLAVE",
   494  	}
   495  	destTablet.FakeMysqlDaemon.FetchSuperQueryMap = map[string]*sqltypes.Result{
   496  		"SHOW DATABASES": {},
   497  	}
   498  	destTablet.FakeMysqlDaemon.SetReplicationPositionPos = destTablet.FakeMysqlDaemon.CurrentPrimaryPosition
   499  	destTablet.FakeMysqlDaemon.SetReplicationSourceInputs = append(destTablet.FakeMysqlDaemon.SetReplicationSourceInputs, topoproto.MysqlAddr(primary.Tablet))
   500  
   501  	destTablet.StartActionLoop(t, wr)
   502  	defer destTablet.StopActionLoop(t)
   503  
   504  	destTablet.TM.Cnf = &mysqlctl.Mycnf{
   505  		DataDir:               sourceDataDir,
   506  		InnodbDataHomeDir:     sourceInnodbDataDir,
   507  		InnodbLogGroupHomeDir: sourceInnodbLogDir,
   508  		BinLogPath:            path.Join(root, "bin-logs/filename_prefix"),
   509  		RelayLogPath:          path.Join(root, "relay-logs/filename_prefix"),
   510  		RelayLogIndexPath:     path.Join(root, "relay-log.index"),
   511  		RelayLogInfoPath:      path.Join(root, "relay-log.info"),
   512  	}
   513  
   514  	errCh = make(chan error, 1)
   515  	go func(ctx context.Context, tablet *FakeTablet) {
   516  		errCh <- tablet.TM.RestoreData(ctx, logutil.NewConsoleLogger(), 0 /* waitForBackupInterval */, false /* deleteBeforeRestore */, time.Time{} /* restoreFromBackupTs */)
   517  	}(ctx, destTablet)
   518  
   519  	timer = time.NewTicker(1 * time.Second)
   520  	<-timer.C
   521  	destTablet.FakeMysqlDaemon.CurrentPrimaryPositionLocked(mysql.Position{
   522  		GTIDSet: mysql.MariadbGTIDSet{
   523  			2: mysql.MariadbGTID{
   524  				Domain:   2,
   525  				Server:   123,
   526  				Sequence: 457,
   527  			},
   528  		},
   529  	})
   530  
   531  	timer2 = time.NewTicker(5 * time.Second)
   532  	select {
   533  	case err := <-errCh:
   534  		require.Nil(t, err)
   535  		// verify the full status
   536  		require.NoError(t, destTablet.FakeMysqlDaemon.CheckSuperQueryList(), "destTablet.FakeMysqlDaemon.CheckSuperQueryList failed")
   537  		assert.True(t, destTablet.FakeMysqlDaemon.Replicating)
   538  		assert.True(t, destTablet.FakeMysqlDaemon.Running)
   539  		assert.Equal(t, primary.FakeMysqlDaemon.CurrentPrimaryPosition, destTablet.FakeMysqlDaemon.CurrentPrimaryPosition)
   540  	case <-timer2.C:
   541  		require.FailNow(t, "Restore timed out")
   542  	}
   543  }
   544  
   545  func TestRestoreUnreachablePrimary(t *testing.T) {
   546  	delay := discovery.GetTabletPickerRetryDelay()
   547  	defer func() {
   548  		discovery.SetTabletPickerRetryDelay(delay)
   549  	}()
   550  	discovery.SetTabletPickerRetryDelay(5 * time.Millisecond)
   551  
   552  	// Initialize our environment
   553  	ctx := context.Background()
   554  	db := fakesqldb.New(t)
   555  	defer db.Close()
   556  	ts := memorytopo.NewServer("cell1")
   557  	wr := wrangler.New(logutil.NewConsoleLogger(), ts, tmclient.NewTabletManagerClient())
   558  	vp := NewVtctlPipe(t, ts)
   559  	defer vp.Close()
   560  
   561  	// Set up mock query results.
   562  	db.AddQuery(mysqlctl.GenerateInitialBinlogEntry(), &sqltypes.Result{})
   563  	db.AddQuery("BEGIN", &sqltypes.Result{})
   564  	db.AddQuery("COMMIT", &sqltypes.Result{})
   565  	db.AddQueryPattern(`SET @@session\.sql_log_bin = .*`, &sqltypes.Result{})
   566  
   567  	// Initialize our temp dirs
   568  	root := t.TempDir()
   569  
   570  	// Initialize BackupStorage
   571  	fbsRoot := path.Join(root, "fbs")
   572  	filebackupstorage.FileBackupStorageRoot = fbsRoot
   573  	backupstorage.BackupStorageImplementation = "file"
   574  
   575  	// Initialize the fake mysql root directories
   576  	sourceInnodbDataDir := path.Join(root, "source_innodb_data")
   577  	sourceInnodbLogDir := path.Join(root, "source_innodb_log")
   578  	sourceDataDir := path.Join(root, "source_data")
   579  	sourceDataDbDir := path.Join(sourceDataDir, "vt_db")
   580  	for _, s := range []string{sourceInnodbDataDir, sourceInnodbLogDir, sourceDataDbDir} {
   581  		require.NoError(t, os.MkdirAll(s, os.ModePerm))
   582  	}
   583  	require.NoError(t, os.WriteFile(path.Join(sourceInnodbDataDir, "innodb_data_1"), []byte("innodb data 1 contents"), os.ModePerm))
   584  
   585  	needIt, err := needInnoDBRedoLogSubdir()
   586  	require.NoError(t, err)
   587  	if needIt {
   588  		newPath := path.Join(sourceInnodbLogDir, mysql.DynamicRedoLogSubdir)
   589  		require.NoError(t, os.Mkdir(newPath, os.ModePerm))
   590  		require.NoError(t, os.WriteFile(path.Join(newPath, "#ib_redo1"), []byte("innodb log 1 contents"), os.ModePerm))
   591  	} else {
   592  		require.NoError(t, os.WriteFile(path.Join(sourceInnodbLogDir, "innodb_log_1"), []byte("innodb log 1 contents"), os.ModePerm))
   593  	}
   594  
   595  	require.NoError(t, os.WriteFile(path.Join(sourceDataDbDir, "db.opt"), []byte("db opt file"), os.ModePerm))
   596  
   597  	// create a primary tablet, set its primary position
   598  	primary := NewFakeTablet(t, wr, "cell1", 0, topodatapb.TabletType_PRIMARY, db)
   599  	primary.FakeMysqlDaemon.ReadOnly = false
   600  	primary.FakeMysqlDaemon.Replicating = false
   601  	primary.FakeMysqlDaemon.CurrentPrimaryPosition = mysql.Position{
   602  		GTIDSet: mysql.MariadbGTIDSet{
   603  			2: mysql.MariadbGTID{
   604  				Domain:   2,
   605  				Server:   123,
   606  				Sequence: 457,
   607  			},
   608  		},
   609  	}
   610  
   611  	// start primary so that replica can fetch primary position from it
   612  	primary.StartActionLoop(t, wr)
   613  
   614  	// create a single tablet, set it up so we can do backups
   615  	// set its position same as that of primary so that backup doesn't wait for catchup
   616  	sourceTablet := NewFakeTablet(t, wr, "cell1", 1, topodatapb.TabletType_REPLICA, db)
   617  	sourceTablet.FakeMysqlDaemon.ReadOnly = true
   618  	sourceTablet.FakeMysqlDaemon.Replicating = true
   619  	sourceTablet.FakeMysqlDaemon.CurrentPrimaryPosition = mysql.Position{
   620  		GTIDSet: mysql.MariadbGTIDSet{
   621  			2: mysql.MariadbGTID{
   622  				Domain:   2,
   623  				Server:   123,
   624  				Sequence: 457,
   625  			},
   626  		},
   627  	}
   628  	sourceTablet.FakeMysqlDaemon.SetReplicationSourceInputs = []string{fmt.Sprintf("%s:%d", primary.Tablet.MysqlHostname, primary.Tablet.MysqlPort)}
   629  	sourceTablet.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{
   630  		// These 4 statements come from tablet startup
   631  		"STOP SLAVE",
   632  		"RESET SLAVE ALL",
   633  		"FAKE SET MASTER",
   634  		"START SLAVE",
   635  		// This first set of STOP and START commands come from
   636  		// the builtinBackupEngine implementation which stops the replication
   637  		// while taking the backup
   638  		"STOP SLAVE",
   639  		"START SLAVE",
   640  		// These commands come from SetReplicationSource RPC called
   641  		// to set the correct primary and semi-sync after Backup has concluded
   642  		"STOP SLAVE",
   643  		"RESET SLAVE ALL",
   644  		"FAKE SET MASTER",
   645  		"START SLAVE",
   646  	}
   647  	sourceTablet.StartActionLoop(t, wr)
   648  	defer sourceTablet.StopActionLoop(t)
   649  
   650  	sourceTablet.TM.Cnf = &mysqlctl.Mycnf{
   651  		DataDir:               sourceDataDir,
   652  		InnodbDataHomeDir:     sourceInnodbDataDir,
   653  		InnodbLogGroupHomeDir: sourceInnodbLogDir,
   654  	}
   655  
   656  	// run the backup
   657  	require.NoError(t, vp.Run([]string{"Backup", topoproto.TabletAliasString(sourceTablet.Tablet.Alias)}))
   658  
   659  	// create a destination tablet, set it up so we can do restores
   660  	destTablet := NewFakeTablet(t, wr, "cell1", 2, topodatapb.TabletType_REPLICA, db)
   661  	destTablet.FakeMysqlDaemon.ReadOnly = true
   662  	destTablet.FakeMysqlDaemon.Replicating = true
   663  	destTablet.FakeMysqlDaemon.CurrentPrimaryPosition = mysql.Position{
   664  		GTIDSet: mysql.MariadbGTIDSet{
   665  			2: mysql.MariadbGTID{
   666  				Domain:   2,
   667  				Server:   123,
   668  				Sequence: 457,
   669  			},
   670  		},
   671  	}
   672  	destTablet.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{
   673  		// These 4 statements come from tablet startup
   674  		"STOP SLAVE",
   675  		"RESET SLAVE ALL",
   676  		"FAKE SET MASTER",
   677  		"START SLAVE",
   678  		"STOP SLAVE",
   679  		"RESET SLAVE ALL",
   680  		"FAKE SET SLAVE POSITION",
   681  		"STOP SLAVE",
   682  		"RESET SLAVE ALL",
   683  		"FAKE SET MASTER",
   684  		"START SLAVE",
   685  	}
   686  	destTablet.FakeMysqlDaemon.FetchSuperQueryMap = map[string]*sqltypes.Result{
   687  		"SHOW DATABASES": {},
   688  	}
   689  	destTablet.FakeMysqlDaemon.SetReplicationPositionPos = sourceTablet.FakeMysqlDaemon.CurrentPrimaryPosition
   690  	destTablet.FakeMysqlDaemon.SetReplicationSourceInputs = append(destTablet.FakeMysqlDaemon.SetReplicationSourceInputs, topoproto.MysqlAddr(primary.Tablet))
   691  
   692  	destTablet.StartActionLoop(t, wr)
   693  	defer destTablet.StopActionLoop(t)
   694  
   695  	destTablet.TM.Cnf = &mysqlctl.Mycnf{
   696  		DataDir:               sourceDataDir,
   697  		InnodbDataHomeDir:     sourceInnodbDataDir,
   698  		InnodbLogGroupHomeDir: sourceInnodbLogDir,
   699  		BinLogPath:            path.Join(root, "bin-logs/filename_prefix"),
   700  		RelayLogPath:          path.Join(root, "relay-logs/filename_prefix"),
   701  		RelayLogIndexPath:     path.Join(root, "relay-log.index"),
   702  		RelayLogInfoPath:      path.Join(root, "relay-log.info"),
   703  	}
   704  
   705  	// stop primary so that it is unreachable
   706  	primary.StopActionLoop(t)
   707  
   708  	// set a short timeout so that we don't have to wait 30 seconds
   709  	topo.RemoteOperationTimeout = 2 * time.Second
   710  	// Restore should still succeed
   711  	require.NoError(t, destTablet.TM.RestoreData(ctx, logutil.NewConsoleLogger(), 0 /* waitForBackupInterval */, false /* deleteBeforeRestore */, time.Time{} /* restoreFromBackupTs */))
   712  	// verify the full status
   713  	require.NoError(t, destTablet.FakeMysqlDaemon.CheckSuperQueryList(), "destTablet.FakeMysqlDaemon.CheckSuperQueryList failed")
   714  	assert.True(t, destTablet.FakeMysqlDaemon.Replicating)
   715  	assert.True(t, destTablet.FakeMysqlDaemon.Running)
   716  }
   717  
   718  func TestDisableActiveReparents(t *testing.T) {
   719  	mysqlctl.DisableActiveReparents = true
   720  	delay := discovery.GetTabletPickerRetryDelay()
   721  	defer func() {
   722  		// When you mess with globals you must remember to reset them
   723  		mysqlctl.DisableActiveReparents = false
   724  		discovery.SetTabletPickerRetryDelay(delay)
   725  	}()
   726  	discovery.SetTabletPickerRetryDelay(5 * time.Millisecond)
   727  
   728  	// Initialize our environment
   729  	ctx := context.Background()
   730  	db := fakesqldb.New(t)
   731  	defer db.Close()
   732  	ts := memorytopo.NewServer("cell1", "cell2")
   733  	wr := wrangler.New(logutil.NewConsoleLogger(), ts, tmclient.NewTabletManagerClient())
   734  	vp := NewVtctlPipe(t, ts)
   735  	defer vp.Close()
   736  
   737  	// Set up mock query results.
   738  	db.AddQuery(mysqlctl.GenerateInitialBinlogEntry(), &sqltypes.Result{})
   739  	db.AddQuery("BEGIN", &sqltypes.Result{})
   740  	db.AddQuery("COMMIT", &sqltypes.Result{})
   741  	db.AddQueryPattern(`SET @@session\.sql_log_bin = .*`, &sqltypes.Result{})
   742  
   743  	// Initialize our temp dirs
   744  	root := t.TempDir()
   745  
   746  	// Initialize BackupStorage
   747  	fbsRoot := path.Join(root, "fbs")
   748  	filebackupstorage.FileBackupStorageRoot = fbsRoot
   749  	backupstorage.BackupStorageImplementation = "file"
   750  
   751  	// Initialize the fake mysql root directories
   752  	sourceInnodbDataDir := path.Join(root, "source_innodb_data")
   753  	sourceInnodbLogDir := path.Join(root, "source_innodb_log")
   754  	sourceDataDir := path.Join(root, "source_data")
   755  	sourceDataDbDir := path.Join(sourceDataDir, "vt_db")
   756  	for _, s := range []string{sourceInnodbDataDir, sourceInnodbLogDir, sourceDataDbDir} {
   757  		require.NoError(t, os.MkdirAll(s, os.ModePerm))
   758  	}
   759  	require.NoError(t, os.WriteFile(path.Join(sourceInnodbDataDir, "innodb_data_1"), []byte("innodb data 1 contents"), os.ModePerm))
   760  
   761  	needIt, err := needInnoDBRedoLogSubdir()
   762  	require.NoError(t, err)
   763  	if needIt {
   764  		newPath := path.Join(sourceInnodbLogDir, mysql.DynamicRedoLogSubdir)
   765  		require.NoError(t, os.Mkdir(newPath, os.ModePerm))
   766  		require.NoError(t, os.WriteFile(path.Join(newPath, "#ib_redo1"), []byte("innodb log 1 contents"), os.ModePerm))
   767  	} else {
   768  		require.NoError(t, os.WriteFile(path.Join(sourceInnodbLogDir, "innodb_log_1"), []byte("innodb log 1 contents"), os.ModePerm))
   769  	}
   770  
   771  	require.NoError(t, os.WriteFile(path.Join(sourceDataDbDir, "db.opt"), []byte("db opt file"), os.ModePerm))
   772  
   773  	// create a primary tablet, set its primary position
   774  	primary := NewFakeTablet(t, wr, "cell1", 0, topodatapb.TabletType_PRIMARY, db)
   775  	primary.FakeMysqlDaemon.ReadOnly = false
   776  	primary.FakeMysqlDaemon.Replicating = false
   777  	primary.FakeMysqlDaemon.CurrentPrimaryPosition = mysql.Position{
   778  		GTIDSet: mysql.MariadbGTIDSet{
   779  			2: mysql.MariadbGTID{
   780  				Domain:   2,
   781  				Server:   123,
   782  				Sequence: 457,
   783  			},
   784  		},
   785  	}
   786  
   787  	// start primary so that replica can fetch primary position from it
   788  	primary.StartActionLoop(t, wr)
   789  	defer primary.StopActionLoop(t)
   790  
   791  	// create a single tablet, set it up so we can do backups
   792  	// set its position same as that of primary so that backup doesn't wait for catchup
   793  	sourceTablet := NewFakeTablet(t, wr, "cell1", 1, topodatapb.TabletType_REPLICA, db)
   794  	sourceTablet.FakeMysqlDaemon.ReadOnly = true
   795  	sourceTablet.FakeMysqlDaemon.Replicating = true
   796  	sourceTablet.FakeMysqlDaemon.CurrentPrimaryPosition = mysql.Position{
   797  		GTIDSet: mysql.MariadbGTIDSet{
   798  			2: mysql.MariadbGTID{
   799  				Domain:   2,
   800  				Server:   123,
   801  				Sequence: 457,
   802  			},
   803  		},
   804  	}
   805  	sourceTablet.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{
   806  		"STOP SLAVE",
   807  	}
   808  	sourceTablet.StartActionLoop(t, wr)
   809  	defer sourceTablet.StopActionLoop(t)
   810  
   811  	sourceTablet.TM.Cnf = &mysqlctl.Mycnf{
   812  		DataDir:               sourceDataDir,
   813  		InnodbDataHomeDir:     sourceInnodbDataDir,
   814  		InnodbLogGroupHomeDir: sourceInnodbLogDir,
   815  	}
   816  
   817  	// run the backup
   818  	require.NoError(t, vp.Run([]string{"Backup", topoproto.TabletAliasString(sourceTablet.Tablet.Alias)}))
   819  
   820  	// verify the full status
   821  	require.NoError(t, sourceTablet.FakeMysqlDaemon.CheckSuperQueryList())
   822  	assert.False(t, sourceTablet.FakeMysqlDaemon.Replicating)
   823  	assert.True(t, sourceTablet.FakeMysqlDaemon.Running)
   824  
   825  	// create a destination tablet, set it up so we can do restores
   826  	destTablet := NewFakeTablet(t, wr, "cell1", 2, topodatapb.TabletType_REPLICA, db)
   827  	destTablet.FakeMysqlDaemon.ReadOnly = true
   828  	destTablet.FakeMysqlDaemon.Replicating = true
   829  	destTablet.FakeMysqlDaemon.CurrentPrimaryPosition = mysql.Position{
   830  		GTIDSet: mysql.MariadbGTIDSet{
   831  			2: mysql.MariadbGTID{
   832  				Domain:   2,
   833  				Server:   123,
   834  				Sequence: 457,
   835  			},
   836  		},
   837  	}
   838  	destTablet.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{
   839  		"STOP SLAVE",
   840  		"RESET SLAVE ALL",
   841  		"FAKE SET SLAVE POSITION",
   842  	}
   843  	destTablet.FakeMysqlDaemon.FetchSuperQueryMap = map[string]*sqltypes.Result{
   844  		"SHOW DATABASES": {},
   845  	}
   846  	destTablet.FakeMysqlDaemon.SetReplicationPositionPos = sourceTablet.FakeMysqlDaemon.CurrentPrimaryPosition
   847  	destTablet.FakeMysqlDaemon.SetReplicationSourceInputs = append(destTablet.FakeMysqlDaemon.SetReplicationSourceInputs, topoproto.MysqlAddr(primary.Tablet))
   848  
   849  	destTablet.StartActionLoop(t, wr)
   850  	defer destTablet.StopActionLoop(t)
   851  
   852  	destTablet.TM.Cnf = &mysqlctl.Mycnf{
   853  		DataDir:               sourceDataDir,
   854  		InnodbDataHomeDir:     sourceInnodbDataDir,
   855  		InnodbLogGroupHomeDir: sourceInnodbLogDir,
   856  		BinLogPath:            path.Join(root, "bin-logs/filename_prefix"),
   857  		RelayLogPath:          path.Join(root, "relay-logs/filename_prefix"),
   858  		RelayLogIndexPath:     path.Join(root, "relay-log.index"),
   859  		RelayLogInfoPath:      path.Join(root, "relay-log.info"),
   860  	}
   861  
   862  	require.NoError(t, destTablet.TM.RestoreData(ctx, logutil.NewConsoleLogger(), 0 /* waitForBackupInterval */, false /* deleteBeforeRestore */, time.Time{} /* restoreFromBackupTs */))
   863  	// verify the full status
   864  	require.NoError(t, destTablet.FakeMysqlDaemon.CheckSuperQueryList(), "destTablet.FakeMysqlDaemon.CheckSuperQueryList failed")
   865  	assert.False(t, destTablet.FakeMysqlDaemon.Replicating)
   866  	assert.True(t, destTablet.FakeMysqlDaemon.Running)
   867  }
   868  
   869  // needInnoDBRedoLogSubdir indicates whether we need to create a redo log subdirectory.
   870  // Starting with MySQL 8.0.30, the InnoDB redo logs are stored in a subdirectory of the
   871  // <innodb_log_group_home_dir> (<datadir>/. by default) called "#innodb_redo". See:
   872  //
   873  //	https://dev.mysql.com/doc/refman/8.0/en/innodb-redo-log.html#innodb-modifying-redo-log-capacity
   874  func needInnoDBRedoLogSubdir() (needIt bool, err error) {
   875  	mysqldVersionStr, err := mysqlctl.GetVersionString()
   876  	if err != nil {
   877  		return needIt, err
   878  	}
   879  	_, sv, err := mysqlctl.ParseVersionString(mysqldVersionStr)
   880  	if err != nil {
   881  		return needIt, err
   882  	}
   883  	versionStr := fmt.Sprintf("%d.%d.%d", sv.Major, sv.Minor, sv.Patch)
   884  	_, capableOf, _ := mysql.GetFlavor(versionStr, nil)
   885  	if capableOf == nil {
   886  		return needIt, fmt.Errorf("cannot determine database flavor details for version %s", versionStr)
   887  	}
   888  	return capableOf(mysql.DynamicRedoLogCapacityFlavorCapability)
   889  }