mirror of
https://github.com/kopia/kopia.git
synced 2026-05-15 10:17:16 -04:00
Protect filesystem subtrees from concurrent manipulation during critical sections if engine actions are called asynchronously. This change provides coordination between the `Snapshotter` and the `FileWriter`. For example, the `FileWriter` should be blocked from perturbing the same directory tree if a Gather-Snapshot is taking place along that tree simultaneously. This will ensure the fingerprint data accumulated during the `Gather` phase will correspond unambiguously to the data included in the snapshot. Extend build flags to kopia snapshotter This package now imports fswalker which can only be built for darwin,amd64 or linux,amd64
211 lines
4.7 KiB
Go
211 lines
4.7 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
|
|
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 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,
|
|
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
|
|
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(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(SnapshotDirActionKey, 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()
|
|
}
|