mirror of
https://github.com/kopia/kopia.git
synced 2026-01-16 02:17:52 -05:00
- upgrade to golangci-lint 2.6.1 - updates for gosec - updates for govet - updates for perfsprint - updates modernize Leaves out modernize:omitempty due to conflicts with tests
216 lines
4.8 KiB
Go
216 lines
4.8 KiB
Go
//go:build darwin || (linux && amd64)
|
|
|
|
// Package engine provides the framework for a snapshot repository testing engine
|
|
package engine
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/kopia/kopia/internal/clock"
|
|
"github.com/kopia/kopia/tests/robustness"
|
|
"github.com/kopia/kopia/tests/robustness/checker"
|
|
)
|
|
|
|
var (
|
|
// ErrInvalidArgs is returned if the constructor arguments are incorrect.
|
|
ErrInvalidArgs = fmt.Errorf("invalid arguments")
|
|
|
|
noSpaceOnDeviceMatchStr = "no space left on device"
|
|
)
|
|
|
|
// Args contain the parameters for the engine constructor.
|
|
type Args struct {
|
|
// Interfaces used by the engine.
|
|
MetaStore robustness.Persister
|
|
TestRepo robustness.Snapshotter
|
|
FileWriter robustness.FileWriter
|
|
|
|
// WorkingDir is a directory to use for temporary data.
|
|
WorkingDir string
|
|
|
|
// SyncRepositories should be set to true to reconcile differences.
|
|
SyncRepositories bool
|
|
}
|
|
|
|
// Validate checks the arguments for correctness.
|
|
func (a *Args) Validate() error {
|
|
if a.MetaStore == nil || a.TestRepo == nil || a.FileWriter == nil || a.WorkingDir == "" {
|
|
return ErrInvalidArgs
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// New creates an Engine.
|
|
func New(args *Args) (*Engine, error) {
|
|
if err := args.Validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var (
|
|
e = &Engine{
|
|
MetaStore: args.MetaStore,
|
|
TestRepo: args.TestRepo,
|
|
FileWriter: args.FileWriter,
|
|
baseDirPath: args.WorkingDir,
|
|
RunStats: Stats{
|
|
RunCounter: 1,
|
|
CreationTime: clock.Now(),
|
|
PerActionStats: make(map[ActionKey]*ActionStats),
|
|
},
|
|
}
|
|
err error
|
|
)
|
|
|
|
if err = e.setupLogging(); err != nil {
|
|
e.cleanComponents()
|
|
return nil, err
|
|
}
|
|
|
|
e.Checker, err = checker.NewChecker(e.TestRepo, e.MetaStore, e.baseDirPath)
|
|
if err != nil {
|
|
e.cleanComponents()
|
|
return nil, err
|
|
}
|
|
|
|
e.Checker.RecoveryMode = args.SyncRepositories
|
|
e.cleanupRoutines = append(e.cleanupRoutines, e.Checker.Cleanup)
|
|
|
|
return e, nil
|
|
}
|
|
|
|
// Engine is the outer level testing framework for robustness testing.
|
|
type Engine struct {
|
|
FileWriter robustness.FileWriter
|
|
TestRepo robustness.Snapshotter
|
|
MetaStore robustness.Persister
|
|
|
|
Checker *checker.Checker
|
|
cleanupRoutines []func()
|
|
baseDirPath string
|
|
|
|
RunStats Stats
|
|
CumulativeStats Stats
|
|
statsMux sync.RWMutex
|
|
|
|
EngineLog Log
|
|
logMux sync.RWMutex
|
|
}
|
|
|
|
// Shutdown makes a last snapshot then flushes the metadata and prints the final statistics.
|
|
func (e *Engine) Shutdown(ctx context.Context) error {
|
|
// Perform a snapshot action to capture the state of the data directory
|
|
// at the end of the run
|
|
lastWriteEntry := e.EngineLog.FindLastThisRun(WriteRandomFilesActionKey)
|
|
lastSnapEntry := e.EngineLog.FindLastThisRun(SnapshotDirActionKey)
|
|
|
|
if lastWriteEntry != nil {
|
|
if lastSnapEntry == nil || lastSnapEntry.Idx < lastWriteEntry.Idx {
|
|
// Only force a final snapshot if the data tree has been modified since the last snapshot
|
|
e.ExecAction(ctx, SnapshotDirActionKey, make(map[string]string)) //nolint:errcheck
|
|
}
|
|
}
|
|
|
|
var cleanupSummaryBuilder strings.Builder
|
|
|
|
cleanupSummaryBuilder.WriteString("\n================\n")
|
|
cleanupSummaryBuilder.WriteString("Cleanup Summary:\n\n")
|
|
cleanupSummaryBuilder.WriteString(e.Stats())
|
|
cleanupSummaryBuilder.WriteString("\n\n")
|
|
cleanupSummaryBuilder.WriteString(e.EngineLog.StringThisRun())
|
|
cleanupSummaryBuilder.WriteString("\n")
|
|
|
|
log.Print(cleanupSummaryBuilder.String())
|
|
|
|
e.RunStats.RunTime = clock.Now().Sub(e.RunStats.CreationTime)
|
|
e.CumulativeStats.RunTime += e.RunStats.RunTime
|
|
|
|
defer e.cleanComponents()
|
|
|
|
if e.MetaStore != nil {
|
|
err := e.saveLog(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = e.saveStats(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = e.saveSnapIDIndex(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return e.MetaStore.FlushMetadata()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (e *Engine) setupLogging() error {
|
|
dirPath := e.MetaStore.GetPersistDir()
|
|
|
|
newLogPath := filepath.Join(dirPath, e.formatLogName())
|
|
|
|
f, err := os.Create(newLogPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Write to both stderr and persistent log file
|
|
wrt := io.MultiWriter(os.Stderr, f)
|
|
log.SetOutput(wrt)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (e *Engine) formatLogName() string {
|
|
st := e.RunStats.CreationTime
|
|
return fmt.Sprintf("Log_%s", st.Format("2006_01_02_15_04_05"))
|
|
}
|
|
|
|
// cleanComponents cleans up each component part of the test engine.
|
|
func (e *Engine) cleanComponents() {
|
|
for _, f := range e.cleanupRoutines {
|
|
if f != nil {
|
|
f()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Init initializes the Engine and performs a consistency check.
|
|
func (e *Engine) Init(ctx context.Context) error {
|
|
err := e.MetaStore.LoadMetadata()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = e.loadStats(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
e.CumulativeStats.RunCounter++
|
|
|
|
err = e.loadLog(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = e.loadSnapIDIndex(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return e.Checker.VerifySnapshotMetadata(ctx)
|
|
}
|