mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-03 07:50:25 +08:00
* fix: 保存 LastResult 时截断字符串过长部分,以避免数据库报错 * refactor: 新增 entity.DbTaskBase 和 persistence.dbTaskBase, 用于实现数据库备份和恢复任务处理相关部分 * fix: aeskey变更后,解密密码出现数组越界访问错误 * fix: 时间属性为零值时,保存到 mysql 数据库报错 * refactor db.infrastructure.service.scheduler * feat: 实现立即备份功能 * refactor db.infrastructure.service.db_instance * refactor: 从数据库中获取数据库备份目录、mysql文件路径等配置信息 * fix: 数据库备份和恢复问题 * fix: 修改 .gitignore 文件,忽略数据库备份目录和数据库程序目录
277 lines
8.5 KiB
Go
277 lines
8.5 KiB
Go
//go:build e2e
|
|
|
|
package dbm
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"github.com/stretchr/testify/suite"
|
|
"mayfly-go/internal/db/config"
|
|
"mayfly-go/internal/db/domain/entity"
|
|
"mayfly-go/internal/db/domain/repository"
|
|
"mayfly-go/internal/db/infrastructure/persistence"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
instanceIdTest = 0
|
|
backupIdTest = 0
|
|
dbNameBackupTest = "test-backup-01"
|
|
tableNameBackupTest = "test-backup"
|
|
tableNameRestorePITTest = "test-restore-pit"
|
|
tableNameNoBackupTest = "test-not-backup"
|
|
)
|
|
|
|
type DbInstanceSuite struct {
|
|
suite.Suite
|
|
repositories *repository.Repositories
|
|
instanceSvc *DbProgramMysql
|
|
dbConn *DbConn
|
|
}
|
|
|
|
func (s *DbInstanceSuite) SetupSuite() {
|
|
if err := chdir("mayfly-go", "server"); err != nil {
|
|
panic(err)
|
|
}
|
|
dbInfo := DbInfo{
|
|
Type: DbTypeMysql,
|
|
Host: "localhost",
|
|
Port: 3306,
|
|
Username: "test",
|
|
Password: "test",
|
|
}
|
|
dbConn, err := dbInfo.Conn()
|
|
s.Require().NoError(err)
|
|
s.dbConn = dbConn
|
|
s.repositories = &repository.Repositories{
|
|
Instance: persistence.GetInstanceRepo(),
|
|
Backup: persistence.NewDbBackupRepo(),
|
|
BackupHistory: persistence.NewDbBackupHistoryRepo(),
|
|
Restore: persistence.NewDbRestoreRepo(),
|
|
RestoreHistory: persistence.NewDbRestoreHistoryRepo(),
|
|
Binlog: persistence.NewDbBinlogRepo(),
|
|
BinlogHistory: persistence.NewDbBinlogHistoryRepo(),
|
|
}
|
|
s.instanceSvc = NewDbProgramMysql(s.dbConn)
|
|
var extName string
|
|
if runtime.GOOS == "windows" {
|
|
extName = ".exe"
|
|
}
|
|
path := "db/mysql/bin"
|
|
s.instanceSvc.mysqlBin = &config.MysqlBin{
|
|
Path: filepath.Join(path),
|
|
MysqlPath: filepath.Join(path, "mysql"+extName),
|
|
MysqldumpPath: filepath.Join(path, "mysqldump"+extName),
|
|
MysqlbinlogPath: filepath.Join(path, "mysqlbinlog"+extName),
|
|
}
|
|
s.instanceSvc.backupPath = "db/backup"
|
|
}
|
|
|
|
func (s *DbInstanceSuite) TearDownSuite() {
|
|
if s.dbConn != nil {
|
|
s.dbConn.Close()
|
|
s.dbConn = nil
|
|
}
|
|
}
|
|
|
|
func (s *DbInstanceSuite) SetupTest() {
|
|
sql := strings.Builder{}
|
|
require := s.Require()
|
|
sql.WriteString(fmt.Sprintf("drop database if exists `%s`;", dbNameBackupTest))
|
|
sql.WriteString(fmt.Sprintf("create database `%s`;", dbNameBackupTest))
|
|
require.NoError(s.instanceSvc.execute("", sql.String()))
|
|
}
|
|
|
|
func (s *DbInstanceSuite) TearDownTest() {
|
|
require := s.Require()
|
|
sql := fmt.Sprintf("drop database if exists `%s`", dbNameBackupTest)
|
|
require.NoError(s.instanceSvc.execute("", sql))
|
|
|
|
_ = os.RemoveAll(s.instanceSvc.getDbInstanceBackupRoot(instanceIdTest))
|
|
}
|
|
|
|
func (s *DbInstanceSuite) TestBackup() {
|
|
task := &entity.DbBackupHistory{
|
|
DbName: dbNameBackupTest,
|
|
Uuid: dbNameBackupTest,
|
|
}
|
|
task.Id = backupIdTest
|
|
s.testBackup(task)
|
|
}
|
|
|
|
func (s *DbInstanceSuite) testBackup(backupHistory *entity.DbBackupHistory) {
|
|
require := s.Require()
|
|
binlogInfo, err := s.instanceSvc.Backup(context.Background(), backupHistory)
|
|
require.NoError(err)
|
|
|
|
fileName := filepath.Join(s.instanceSvc.getDbBackupDir(s.dbConn.Info.InstanceId, backupHistory.Id), dbNameBackupTest+".sql")
|
|
_, err = os.Stat(fileName)
|
|
require.NoError(err)
|
|
|
|
backupHistory.BinlogFileName = binlogInfo.FileName
|
|
backupHistory.BinlogSequence = binlogInfo.Sequence
|
|
backupHistory.BinlogPosition = binlogInfo.Position
|
|
}
|
|
|
|
func TestDbInstance(t *testing.T) {
|
|
suite.Run(t, &DbInstanceSuite{})
|
|
}
|
|
|
|
func (s *DbInstanceSuite) TestRestoreDatabase() {
|
|
backupHistory := &entity.DbBackupHistory{
|
|
DbName: dbNameBackupTest,
|
|
Uuid: dbNameBackupTest,
|
|
}
|
|
|
|
s.createTable(dbNameBackupTest, tableNameBackupTest, "")
|
|
s.selectTable(dbNameBackupTest, tableNameBackupTest, "")
|
|
s.testBackup(backupHistory)
|
|
s.createTable(dbNameBackupTest, tableNameNoBackupTest, "")
|
|
s.selectTable(dbNameBackupTest, tableNameNoBackupTest, "")
|
|
s.testRestore(backupHistory)
|
|
s.selectTable(dbNameBackupTest, tableNameBackupTest, "")
|
|
s.selectTable(dbNameBackupTest, tableNameNoBackupTest, "运行 mysql 程序失败")
|
|
}
|
|
|
|
func (s *DbInstanceSuite) TestRestorePontInTime() {
|
|
backupHistory := &entity.DbBackupHistory{
|
|
DbName: dbNameBackupTest,
|
|
Uuid: dbNameBackupTest,
|
|
}
|
|
|
|
s.createTable(dbNameBackupTest, tableNameBackupTest, "")
|
|
s.selectTable(dbNameBackupTest, tableNameBackupTest, "")
|
|
s.testBackup(backupHistory)
|
|
|
|
s.createTable(dbNameBackupTest, tableNameRestorePITTest, "")
|
|
s.selectTable(dbNameBackupTest, tableNameRestorePITTest, "")
|
|
time.Sleep(time.Second)
|
|
targetTime := time.Now()
|
|
|
|
s.dropTable(dbNameBackupTest, tableNameBackupTest, "")
|
|
s.selectTable(dbNameBackupTest, tableNameBackupTest, "运行 mysql 程序失败")
|
|
s.createTable(dbNameBackupTest, tableNameNoBackupTest, "")
|
|
s.selectTable(dbNameBackupTest, tableNameNoBackupTest, "")
|
|
|
|
s.testRestore(backupHistory)
|
|
s.selectTable(dbNameBackupTest, tableNameBackupTest, "")
|
|
s.selectTable(dbNameBackupTest, tableNameRestorePITTest, "运行 mysql 程序失败")
|
|
s.selectTable(dbNameBackupTest, tableNameNoBackupTest, "运行 mysql 程序失败")
|
|
|
|
s.testReplayBinlog(backupHistory, targetTime)
|
|
s.selectTable(dbNameBackupTest, tableNameBackupTest, "")
|
|
s.selectTable(dbNameBackupTest, tableNameRestorePITTest, "")
|
|
s.selectTable(dbNameBackupTest, tableNameNoBackupTest, "运行 mysql 程序失败")
|
|
}
|
|
|
|
func (s *DbInstanceSuite) testReplayBinlog(backupHistory *entity.DbBackupHistory, targetTime time.Time) {
|
|
require := s.Require()
|
|
binlogFilesOnServerSorted, err := s.instanceSvc.GetSortedBinlogFilesOnServer(context.Background())
|
|
require.NoError(err)
|
|
require.True(len(binlogFilesOnServerSorted) > 0, "binlog 文件不存在")
|
|
for i, bf := range binlogFilesOnServerSorted {
|
|
if bf.Name == backupHistory.BinlogFileName {
|
|
binlogFilesOnServerSorted = binlogFilesOnServerSorted[i:]
|
|
break
|
|
}
|
|
require.Less(i, len(binlogFilesOnServerSorted), "binlog 文件没找到")
|
|
}
|
|
err = s.instanceSvc.downloadBinlogFilesOnServer(context.Background(), binlogFilesOnServerSorted, true)
|
|
require.NoError(err)
|
|
|
|
binlogFileLast := binlogFilesOnServerSorted[len(binlogFilesOnServerSorted)-1]
|
|
position, err := s.instanceSvc.GetBinlogEventPositionAtOrAfterTime(context.Background(), binlogFileLast.Name, targetTime)
|
|
require.NoError(err)
|
|
binlogHistories := make([]*entity.DbBinlogHistory, 0, 2)
|
|
binlogHistoryBackup := &entity.DbBinlogHistory{
|
|
FileName: backupHistory.BinlogFileName,
|
|
Sequence: backupHistory.BinlogSequence,
|
|
}
|
|
binlogHistories = append(binlogHistories, binlogHistoryBackup)
|
|
if binlogHistoryBackup.Sequence != binlogFileLast.Sequence {
|
|
require.Equal(binlogFilesOnServerSorted[0].Sequence, binlogHistoryBackup.Sequence)
|
|
binlogHistoryLast := &entity.DbBinlogHistory{
|
|
FileName: binlogFileLast.Name,
|
|
Sequence: binlogFileLast.Sequence,
|
|
}
|
|
binlogHistories = append(binlogHistories, binlogHistoryLast)
|
|
}
|
|
|
|
restoreInfo := &RestoreInfo{
|
|
BackupHistory: backupHistory,
|
|
BinlogHistories: binlogHistories,
|
|
StartPosition: backupHistory.BinlogPosition,
|
|
TargetPosition: position,
|
|
TargetTime: targetTime,
|
|
}
|
|
err = s.instanceSvc.ReplayBinlog(context.Background(), dbNameBackupTest, dbNameBackupTest, restoreInfo)
|
|
require.NoError(err)
|
|
}
|
|
|
|
func (s *DbInstanceSuite) testRestore(backupHistory *entity.DbBackupHistory) {
|
|
require := s.Require()
|
|
err := s.instanceSvc.RestoreBackupHistory(context.Background(), backupHistory.DbName, backupHistory.DbBackupId, backupHistory.Uuid)
|
|
require.NoError(err)
|
|
}
|
|
|
|
func (s *DbInstanceSuite) selectTable(database, tableName, wantErr string) {
|
|
require := s.Require()
|
|
sql := fmt.Sprintf("select * from`%s`;", tableName)
|
|
err := s.instanceSvc.execute(database, sql)
|
|
if len(wantErr) > 0 {
|
|
require.ErrorContains(err, wantErr)
|
|
return
|
|
}
|
|
require.NoError(err)
|
|
}
|
|
|
|
func (s *DbInstanceSuite) createTable(database, tableName, wantErr string) {
|
|
require := s.Require()
|
|
sql := fmt.Sprintf("create table `%s`(id int);", tableName)
|
|
err := s.instanceSvc.execute(database, sql)
|
|
if len(wantErr) > 0 {
|
|
require.ErrorContains(err, wantErr)
|
|
return
|
|
}
|
|
require.NoError(err)
|
|
}
|
|
|
|
func (s *DbInstanceSuite) dropTable(database, tableName, wantErr string) {
|
|
require := s.Require()
|
|
sql := fmt.Sprintf("drop table `%s`;", tableName)
|
|
err := s.instanceSvc.execute(database, sql)
|
|
if len(wantErr) > 0 {
|
|
require.ErrorContains(err, wantErr)
|
|
return
|
|
}
|
|
require.NoError(err)
|
|
}
|
|
|
|
func chdir(projectName string, subdir ...string) error {
|
|
subdir = append([]string{"/", projectName}, subdir...)
|
|
suffix := filepath.Join(subdir...)
|
|
wd, err := os.Getwd()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for {
|
|
if strings.HasSuffix(wd, suffix) {
|
|
if err := os.Chdir(wd); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
upper := filepath.Join(wd, "..")
|
|
if upper == wd {
|
|
return errors.New(fmt.Sprintf("not found directory: %s", suffix[1:]))
|
|
}
|
|
wd = upper
|
|
}
|
|
}
|