351 lines
9.7 KiB
Go
351 lines
9.7 KiB
Go
package testcase
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"plp-test/internal/config"
|
|
"plp-test/internal/model"
|
|
"plp-test/internal/utils"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// PowerLossTest 断电测试
|
|
type PowerLossTest struct {
|
|
*BaseTestCase
|
|
testDir string
|
|
blockSize int
|
|
totalBlocks int
|
|
writtenBlocks int
|
|
verifiedBlocks int
|
|
corruptedBlocks int
|
|
blocks []*model.TestBlock
|
|
recoveryTimeMs float64
|
|
powerCutInfo *model.PowerCutInfo
|
|
}
|
|
|
|
// NewPowerLossTest 创建断电测试
|
|
func NewPowerLossTest(cfg *config.Config, logger *logrus.Logger) *PowerLossTest {
|
|
baseTest := NewBaseTestCase(
|
|
"power_loss",
|
|
"测试在断电情况下Open-CAS的数据完整性保护能力",
|
|
cfg,
|
|
logger,
|
|
)
|
|
|
|
return &PowerLossTest{
|
|
BaseTestCase: baseTest,
|
|
blockSize: utils.MBToBytes(float64(cfg.Test.BlockSize)),
|
|
totalBlocks: utils.MBToBytes(float64(cfg.Test.DataSizeMB)) / utils.MBToBytes(float64(cfg.Test.BlockSize)),
|
|
}
|
|
}
|
|
|
|
// Setup 设置测试环境
|
|
func (t *PowerLossTest) Setup(ctx context.Context) error {
|
|
if err := t.BaseTestCase.Setup(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
t.setMessage("创建Open-CAS缓存实例")
|
|
id := t.config.Server.CacheInstanceID
|
|
nvme := t.config.Server.DevicesNVMe
|
|
hdd := t.config.Server.DevicesHDD
|
|
|
|
// 创建缓存实例 - 使用Write-Back模式以测试断电恢复
|
|
err := t.casManager.CreateCacheInstance(id, nvme, hdd, "wb")
|
|
if err != nil {
|
|
t.setStatus(StatusFailed)
|
|
return fmt.Errorf("创建缓存实例失败: %v", err)
|
|
}
|
|
|
|
// 获取缓存设备路径
|
|
cacheDevice := fmt.Sprintf("/dev/cas%s-1", id)
|
|
t.setMessage(fmt.Sprintf("格式化缓存设备 %s", cacheDevice))
|
|
|
|
// 格式化缓存设备
|
|
err = t.casManager.FormatDevice(cacheDevice, "ext4")
|
|
if err != nil {
|
|
t.setStatus(StatusFailed)
|
|
return fmt.Errorf("格式化缓存设备失败: %v", err)
|
|
}
|
|
|
|
// 挂载缓存设备
|
|
mountPoint := t.config.Server.MountPoint
|
|
t.setMessage(fmt.Sprintf("挂载缓存设备到 %s", mountPoint))
|
|
err = t.casManager.MountDevice(cacheDevice, mountPoint)
|
|
if err != nil {
|
|
t.setStatus(StatusFailed)
|
|
return fmt.Errorf("挂载缓存设备失败: %v", err)
|
|
}
|
|
|
|
// 创建测试目录
|
|
t.testDir = filepath.Join(mountPoint, "power_loss_test")
|
|
t.setMessage(fmt.Sprintf("创建测试目录 %s", t.testDir))
|
|
err = utils.CreateDirIfNotExist(t.testDir)
|
|
if err != nil {
|
|
t.setStatus(StatusFailed)
|
|
return fmt.Errorf("创建测试目录失败: %v", err)
|
|
}
|
|
|
|
// 初始化测试数据
|
|
t.blocks = make([]*model.TestBlock, 0, t.totalBlocks)
|
|
t.powerCutInfo = &model.PowerCutInfo{}
|
|
|
|
t.setProgress(10)
|
|
return nil
|
|
}
|
|
|
|
// Run 运行测试
|
|
func (t *PowerLossTest) Run(ctx context.Context) (*model.TestResult, error) {
|
|
t.setMessage("开始断电测试")
|
|
startTime := time.Now()
|
|
var totalBytesWritten int
|
|
|
|
// 写入阶段 - 只写入一部分数据,断电前
|
|
t.setMessage("写入数据 (断电前)")
|
|
blocksBeforePowerCut := t.totalBlocks / 2 // 写入一半的数据块
|
|
|
|
for i := 0; i < blocksBeforePowerCut; i++ {
|
|
select {
|
|
case <-ctx.Done():
|
|
t.setStatus(StatusAborted)
|
|
return nil, ctx.Err()
|
|
default:
|
|
// 生成随机数据
|
|
data, err := utils.GenerateRandomData(t.blockSize)
|
|
if err != nil {
|
|
t.setStatus(StatusFailed)
|
|
return nil, fmt.Errorf("生成随机数据失败: %v", err)
|
|
}
|
|
|
|
// 创建测试数据块
|
|
block := model.NewTestBlock(data, i)
|
|
t.blocks = append(t.blocks, block)
|
|
|
|
// 写入文件
|
|
filePath := filepath.Join(t.testDir, fmt.Sprintf("block_%d.dat", i))
|
|
err = os.WriteFile(filePath, data, 0644)
|
|
if err != nil {
|
|
t.setStatus(StatusFailed)
|
|
return nil, fmt.Errorf("写入文件 %s 失败: %v", filePath, err)
|
|
}
|
|
|
|
t.writtenBlocks++
|
|
totalBytesWritten += len(data)
|
|
|
|
// 更新进度
|
|
progress := float64(i+1) / float64(t.totalBlocks) * 30 // 第一阶段占30%
|
|
t.setProgress(progress)
|
|
}
|
|
}
|
|
|
|
// 记录写入数据的时间点
|
|
t.powerCutInfo.BlocksWritten = t.writtenBlocks
|
|
|
|
// 执行同步以确保部分数据已经写入到磁盘,但部分仍在缓存中
|
|
t.setMessage("执行sync同步部分数据到磁盘")
|
|
_, err := utils.ExecuteCommand("sync")
|
|
if err != nil {
|
|
t.logger.Warnf("执行sync命令失败: %v", err)
|
|
}
|
|
|
|
// 再写入一些数据但不同步,保证有缓存中的数据
|
|
t.setMessage("写入额外数据到缓存中 (这些数据可能会在断电后丢失)")
|
|
additionalBlocks := t.totalBlocks / 4 // 额外写入1/4的数据块
|
|
|
|
for i := blocksBeforePowerCut; i < blocksBeforePowerCut+additionalBlocks; i++ {
|
|
select {
|
|
case <-ctx.Done():
|
|
t.setStatus(StatusAborted)
|
|
return nil, ctx.Err()
|
|
default:
|
|
// 生成随机数据
|
|
data, err := utils.GenerateRandomData(t.blockSize)
|
|
if err != nil {
|
|
t.setStatus(StatusFailed)
|
|
return nil, fmt.Errorf("生成随机数据失败: %v", err)
|
|
}
|
|
|
|
// 创建测试数据块
|
|
block := model.NewTestBlock(data, i)
|
|
t.blocks = append(t.blocks, block)
|
|
|
|
// 写入文件
|
|
filePath := filepath.Join(t.testDir, fmt.Sprintf("block_%d.dat", i))
|
|
err = os.WriteFile(filePath, data, 0644)
|
|
if err != nil {
|
|
t.setStatus(StatusFailed)
|
|
return nil, fmt.Errorf("写入文件 %s 失败: %v", filePath, err)
|
|
}
|
|
|
|
t.writtenBlocks++
|
|
totalBytesWritten += len(data)
|
|
|
|
// 更新进度
|
|
progress := 30 + float64(i-blocksBeforePowerCut+1)/float64(additionalBlocks)*10 // 额外写入占10%
|
|
t.setProgress(progress)
|
|
}
|
|
}
|
|
|
|
// 模拟断电
|
|
t.setMessage("模拟断电...")
|
|
t.powerCutInfo.Timestamp = time.Now()
|
|
err = t.casManager.SimulatePowerCut(t.config.Server.CacheInstanceID)
|
|
if err != nil {
|
|
t.setStatus(StatusFailed)
|
|
return nil, fmt.Errorf("模拟断电失败: %v", err)
|
|
}
|
|
|
|
// 模拟等待一段时间,就像真正断电后的重启
|
|
t.setMessage("模拟系统重启中...")
|
|
time.Sleep(2 * time.Second)
|
|
|
|
// 恢复阶段
|
|
t.setMessage("恢复阶段")
|
|
recoveryStart := time.Now()
|
|
|
|
// 重新创建缓存实例
|
|
id := t.config.Server.CacheInstanceID
|
|
nvme := t.config.Server.DevicesNVMe
|
|
hdd := t.config.Server.DevicesHDD
|
|
|
|
// 尝试修复/加载现有缓存
|
|
t.setMessage("尝试加载和修复缓存")
|
|
err = t.casManager.RepairCache(id, nvme)
|
|
if err != nil {
|
|
// 如果修复失败,尝试重新创建
|
|
t.logger.Warnf("修复缓存失败,尝试重新创建: %v", err)
|
|
err = t.casManager.CreateCacheInstance(id, nvme, hdd, "wb")
|
|
if err != nil {
|
|
t.setStatus(StatusFailed)
|
|
return nil, fmt.Errorf("断电后重新创建缓存实例失败: %v", err)
|
|
}
|
|
}
|
|
|
|
// 获取缓存设备路径
|
|
cacheDevice := fmt.Sprintf("/dev/cas%s-1", id)
|
|
|
|
// 重新挂载缓存设备
|
|
mountPoint := t.config.Server.MountPoint
|
|
t.setMessage(fmt.Sprintf("重新挂载缓存设备到 %s", mountPoint))
|
|
err = t.casManager.MountDevice(cacheDevice, mountPoint)
|
|
if err != nil {
|
|
t.setStatus(StatusFailed)
|
|
return nil, fmt.Errorf("断电后重新挂载缓存设备失败: %v", err)
|
|
}
|
|
|
|
// 记录恢复时间
|
|
t.recoveryTimeMs = float64(time.Since(recoveryStart).Milliseconds())
|
|
t.setProgress(50)
|
|
|
|
// 验证阶段 - 检查断电前写入的数据是否完整
|
|
t.setMessage("验证阶段 - 检查数据完整性")
|
|
t.corruptedBlocks = 0
|
|
|
|
for i, block := range t.blocks {
|
|
// 检查文件是否存在
|
|
filePath := filepath.Join(t.testDir, fmt.Sprintf("block_%d.dat", i))
|
|
if !utils.FileExists(filePath) {
|
|
t.logger.Warnf("文件 %s 在断电后丢失", filePath)
|
|
t.corruptedBlocks++
|
|
continue
|
|
}
|
|
|
|
// 读取文件数据
|
|
data, err := os.ReadFile(filePath)
|
|
if err != nil {
|
|
t.logger.Warnf("读取文件 %s 失败: %v", filePath, err)
|
|
t.corruptedBlocks++
|
|
continue
|
|
}
|
|
|
|
// 验证数据完整性
|
|
if string(data) != string(block.Data) {
|
|
t.logger.Warnf("文件 %s 数据损坏", filePath)
|
|
t.corruptedBlocks++
|
|
continue
|
|
}
|
|
|
|
t.verifiedBlocks++
|
|
|
|
// 更新进度
|
|
progress := 50 + float64(i+1)/float64(len(t.blocks))*40 // 验证占40%
|
|
t.setProgress(progress)
|
|
}
|
|
|
|
// 写入断电后的额外数据
|
|
t.setMessage("断电后写入额外数据")
|
|
for i := t.writtenBlocks; i < t.totalBlocks; i++ {
|
|
select {
|
|
case <-ctx.Done():
|
|
t.setStatus(StatusAborted)
|
|
return nil, ctx.Err()
|
|
default:
|
|
// 生成随机数据
|
|
data, err := utils.GenerateRandomData(t.blockSize)
|
|
if err != nil {
|
|
t.setStatus(StatusFailed)
|
|
return nil, fmt.Errorf("生成随机数据失败: %v", err)
|
|
}
|
|
|
|
// 写入文件
|
|
filePath := filepath.Join(t.testDir, fmt.Sprintf("block_%d.dat", i))
|
|
err = os.WriteFile(filePath, data, 0644)
|
|
if err != nil {
|
|
t.setStatus(StatusFailed)
|
|
return nil, fmt.Errorf("断电后写入文件 %s 失败: %v", filePath, err)
|
|
}
|
|
|
|
// 更新进度
|
|
progress := 90 + float64(i-t.writtenBlocks+1)/float64(t.totalBlocks-t.writtenBlocks)*10 // 最后10%
|
|
t.setProgress(progress)
|
|
}
|
|
}
|
|
|
|
// 记录断电信息
|
|
t.powerCutInfo.RecoverySuccess = t.corruptedBlocks == 0
|
|
t.powerCutInfo.DataLossMB = utils.BytesToMB(t.corruptedBlocks * t.blockSize)
|
|
|
|
t.setProgress(100)
|
|
t.setStatus(StatusCompleted)
|
|
t.setMessage("断电测试完成")
|
|
|
|
// 构造测试结果
|
|
result := t.getTestResult()
|
|
result.BlocksWritten = t.writtenBlocks
|
|
result.BlocksVerified = t.verifiedBlocks
|
|
result.DataWrittenMB = utils.BytesToMB(totalBytesWritten)
|
|
result.WriteSpeedMBs = utils.BytesToMB(totalBytesWritten) / time.Since(startTime).Seconds()
|
|
result.Metrics = model.TestMetrics{
|
|
DataIntegrityLoss: t.corruptedBlocks,
|
|
RecoveryTimeMs: t.recoveryTimeMs,
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// Cleanup 清理测试环境
|
|
func (t *PowerLossTest) Cleanup(ctx context.Context) error {
|
|
if err := t.BaseTestCase.Cleanup(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
t.setMessage("卸载缓存设备")
|
|
err := t.casManager.UnmountDevice(t.config.Server.MountPoint)
|
|
if err != nil {
|
|
t.logger.Warnf("卸载缓存设备失败: %v", err)
|
|
}
|
|
|
|
t.setMessage("停止缓存实例")
|
|
err = t.casManager.StopCacheInstance(t.config.Server.CacheInstanceID)
|
|
if err != nil {
|
|
t.logger.Warnf("停止缓存实例失败: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|