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

     1  // Package mysqlctl_test is the blackbox tests for package mysqlctl.
     2  // Tests that need to use fakemysqldaemon must be written as blackbox tests;
     3  // since fakemysqldaemon imports mysqlctl, importing fakemysqldaemon in
     4  // a `package mysqlctl` test would cause a circular import.
     5  package mysqlctl_test
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"os"
    11  	"path"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/stretchr/testify/assert"
    16  	"github.com/stretchr/testify/require"
    17  
    18  	"vitess.io/vitess/go/mysql"
    19  	"vitess.io/vitess/go/mysql/fakesqldb"
    20  	"vitess.io/vitess/go/vt/logutil"
    21  	"vitess.io/vitess/go/vt/mysqlctl"
    22  	"vitess.io/vitess/go/vt/mysqlctl/fakemysqldaemon"
    23  	"vitess.io/vitess/go/vt/mysqlctl/filebackupstorage"
    24  	"vitess.io/vitess/go/vt/proto/topodata"
    25  	"vitess.io/vitess/go/vt/proto/vttime"
    26  	"vitess.io/vitess/go/vt/topo"
    27  	"vitess.io/vitess/go/vt/topo/memorytopo"
    28  	"vitess.io/vitess/go/vt/vttablet/faketmclient"
    29  	"vitess.io/vitess/go/vt/vttablet/tmclient"
    30  )
    31  
    32  func setBuiltinBackupMysqldDeadline(t time.Duration) time.Duration {
    33  	old := mysqlctl.BuiltinBackupMysqldTimeout
    34  	mysqlctl.BuiltinBackupMysqldTimeout = t
    35  
    36  	return old
    37  }
    38  
    39  func createBackupDir(root string, dirs ...string) error {
    40  	for _, dir := range dirs {
    41  		if err := os.MkdirAll(path.Join(root, dir), 0755); err != nil {
    42  			return err
    43  		}
    44  	}
    45  
    46  	return nil
    47  }
    48  
    49  func TestExecuteBackup(t *testing.T) {
    50  	// Set up local backup directory
    51  	backupRoot := "testdata/builtinbackup_test"
    52  	filebackupstorage.FileBackupStorageRoot = backupRoot
    53  	require.NoError(t, createBackupDir(backupRoot, "innodb", "log", "datadir"))
    54  	defer os.RemoveAll(backupRoot)
    55  
    56  	ctx := context.Background()
    57  
    58  	needIt, err := needInnoDBRedoLogSubdir()
    59  	require.NoError(t, err)
    60  	if needIt {
    61  		fpath := path.Join("log", mysql.DynamicRedoLogSubdir)
    62  		if err := createBackupDir(backupRoot, fpath); err != nil {
    63  			t.Fatalf("failed to create directory %s: %v", fpath, err)
    64  		}
    65  	}
    66  
    67  	// Set up topo
    68  	keyspace, shard := "mykeyspace", "-80"
    69  	ts := memorytopo.NewServer("cell1")
    70  	defer ts.Close()
    71  
    72  	require.NoError(t, ts.CreateKeyspace(ctx, keyspace, &topodata.Keyspace{}))
    73  	require.NoError(t, ts.CreateShard(ctx, keyspace, shard))
    74  
    75  	tablet := topo.NewTablet(100, "cell1", "mykeyspace-00-80-0100")
    76  	tablet.Keyspace = keyspace
    77  	tablet.Shard = shard
    78  
    79  	require.NoError(t, ts.CreateTablet(ctx, tablet))
    80  
    81  	_, err = ts.UpdateShardFields(ctx, keyspace, shard, func(si *topo.ShardInfo) error {
    82  		si.PrimaryAlias = &topodata.TabletAlias{Uid: 100, Cell: "cell1"}
    83  
    84  		now := time.Now()
    85  		si.PrimaryTermStartTime = &vttime.Time{Seconds: int64(now.Second()), Nanoseconds: int32(now.Nanosecond())}
    86  
    87  		return nil
    88  	})
    89  	require.NoError(t, err)
    90  
    91  	// Set up tm client
    92  	// Note that using faketmclient.NewFakeTabletManagerClient will cause infinite recursion :shrug:
    93  	tmclient.RegisterTabletManagerClientFactory("grpc",
    94  		func() tmclient.TabletManagerClient { return &faketmclient.FakeTabletManagerClient{} },
    95  	)
    96  
    97  	be := &mysqlctl.BuiltinBackupEngine{}
    98  
    99  	// Configure a tight deadline to force a timeout
   100  	oldDeadline := setBuiltinBackupMysqldDeadline(time.Second)
   101  	defer setBuiltinBackupMysqldDeadline(oldDeadline)
   102  
   103  	bh := filebackupstorage.FileBackupHandle{}
   104  
   105  	// Spin up a fake daemon to be used in backups. It needs to be allowed to receive:
   106  	//  "STOP SLAVE", "START SLAVE", in that order.
   107  	mysqld := fakemysqldaemon.NewFakeMysqlDaemon(fakesqldb.New(t))
   108  	mysqld.ExpectedExecuteSuperQueryList = []string{"STOP SLAVE", "START SLAVE"}
   109  	// mysqld.ShutdownTime = time.Minute
   110  
   111  	ok, err := be.ExecuteBackup(ctx, mysqlctl.BackupParams{
   112  		Logger: logutil.NewConsoleLogger(),
   113  		Mysqld: mysqld,
   114  		Cnf: &mysqlctl.Mycnf{
   115  			InnodbDataHomeDir:     path.Join(backupRoot, "innodb"),
   116  			InnodbLogGroupHomeDir: path.Join(backupRoot, "log"),
   117  			DataDir:               path.Join(backupRoot, "datadir"),
   118  		},
   119  		HookExtraEnv: map[string]string{},
   120  		TopoServer:   ts,
   121  		Keyspace:     keyspace,
   122  		Shard:        shard,
   123  	}, &bh)
   124  
   125  	require.NoError(t, err)
   126  	assert.True(t, ok)
   127  
   128  	mysqld.ExpectedExecuteSuperQueryCurrent = 0 // resest the index of what queries we've run
   129  	mysqld.ShutdownTime = time.Minute           // reminder that shutdownDeadline is 1s
   130  
   131  	ok, err = be.ExecuteBackup(ctx, mysqlctl.BackupParams{
   132  		Logger: logutil.NewConsoleLogger(),
   133  		Mysqld: mysqld,
   134  		Cnf: &mysqlctl.Mycnf{
   135  			InnodbDataHomeDir:     path.Join(backupRoot, "innodb"),
   136  			InnodbLogGroupHomeDir: path.Join(backupRoot, "log"),
   137  			DataDir:               path.Join(backupRoot, "datadir"),
   138  		},
   139  		HookExtraEnv: map[string]string{},
   140  		TopoServer:   ts,
   141  		Keyspace:     keyspace,
   142  		Shard:        shard,
   143  	}, &bh)
   144  
   145  	assert.Error(t, err)
   146  	assert.False(t, ok)
   147  }
   148  
   149  // needInnoDBRedoLogSubdir indicates whether we need to create a redo log subdirectory.
   150  // Starting with MySQL 8.0.30, the InnoDB redo logs are stored in a subdirectory of the
   151  // <innodb_log_group_home_dir> (<datadir>/. by default) called "#innodb_redo". See:
   152  //
   153  //	https://dev.mysql.com/doc/refman/8.0/en/innodb-redo-log.html#innodb-modifying-redo-log-capacity
   154  func needInnoDBRedoLogSubdir() (needIt bool, err error) {
   155  	mysqldVersionStr, err := mysqlctl.GetVersionString()
   156  	if err != nil {
   157  		return needIt, err
   158  	}
   159  	_, sv, err := mysqlctl.ParseVersionString(mysqldVersionStr)
   160  	if err != nil {
   161  		return needIt, err
   162  	}
   163  	versionStr := fmt.Sprintf("%d.%d.%d", sv.Major, sv.Minor, sv.Patch)
   164  	_, capableOf, _ := mysql.GetFlavor(versionStr, nil)
   165  	if capableOf == nil {
   166  		return needIt, fmt.Errorf("cannot determine database flavor details for version %s", versionStr)
   167  	}
   168  	return capableOf(mysql.DynamicRedoLogCapacityFlavorCapability)
   169  }