mirror of
https://github.com/kopia/kopia.git
synced 2026-05-16 10:44:40 -04:00
Add metadata R/W locking for asynchronous accesses to robustness engine metadata. Remove the index from the Store interface and maintain it only in Checker, where it's used.
214 lines
4.8 KiB
Go
214 lines
4.8 KiB
Go
// +build darwin,amd64 linux,amd64
|
|
|
|
// Package engine provides the framework for a snapshot repository testing engine
|
|
package engine
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/kopia/kopia/internal/clock"
|
|
"github.com/kopia/kopia/tests/robustness"
|
|
"github.com/kopia/kopia/tests/robustness/checker"
|
|
)
|
|
|
|
var (
|
|
// ErrorInvalidArgs is returned if the constructor arguments are incorrect.
|
|
ErrorInvalidArgs = 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
|
|
Validator robustness.Comparer
|
|
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.Validator == nil || a.FileWriter == nil || a.WorkingDir == "" {
|
|
return ErrorInvalidArgs
|
|
}
|
|
|
|
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,
|
|
Validator: args.Validator,
|
|
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.Validator, 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
|
|
Validator robustness.Comparer
|
|
|
|
Checker *checker.Checker
|
|
cleanupRoutines []func()
|
|
baseDirPath string
|
|
|
|
RunStats Stats
|
|
CumulativeStats Stats
|
|
EngineLog Log
|
|
}
|
|
|
|
// Shutdown makes a last snapshot then flushes the metadata and prints the final statistics.
|
|
func (e *Engine) Shutdown() 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(SnapshotRootDirActionKey)
|
|
|
|
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(SnapshotRootDirActionKey, make(map[string]string)) //nolint:errcheck
|
|
}
|
|
}
|
|
|
|
cleanupSummaryBuilder := new(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.Since(e.RunStats.CreationTime)
|
|
e.CumulativeStats.RunTime += e.RunStats.RunTime
|
|
|
|
defer e.cleanComponents()
|
|
|
|
if e.MetaStore != nil {
|
|
err := e.saveLog()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = e.saveStats()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = e.saveSnapIDIndex()
|
|
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()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
e.CumulativeStats.RunCounter++
|
|
|
|
err = e.loadLog()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = e.loadSnapIDIndex()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return e.Checker.VerifySnapshotMetadata()
|
|
}
|