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 }