Files
Felis/internal/backup/archiver.go
T

99 lines
4.1 KiB
Go

// Package backup implements the WorldArchiver abstraction (spec §19). The
// reaper and the restore endpoint speak only to the interface and never learn
// whether the backend is a tar file, a VolumeSnapshot, or a Longhorn backup —
// ArchiveRef is deliberately opaque.
package backup
import (
"context"
"errors"
"time"
)
// ArchiveRef is an opaque handle to a stored world archive. Depending on the
// backend it may be a tar path, a VolumeSnapshot name, or a Longhorn backup URL.
type ArchiveRef string
// WorldArchiver archives, restores, and deletes a server's world. The signature
// is intentionally "archive a world" rather than "write bytes": snapshot
// backends (VolumeSnapshot/Longhorn) cannot produce an io.Reader — they create
// K8s objects referencing the source PVC (spec §19).
type WorldArchiver interface {
// Archive captures the world living on pvc for server. It returns only once
// the archive is complete and durable: a failed or interrupted Archive leaves
// nothing that could be mistaken for a finished archive.
Archive(ctx context.Context, server, pvc string) (Archived, error)
// Restore writes a previously archived world into targetPVC.
Restore(ctx context.Context, ref ArchiveRef, targetPVC string) error
// Delete removes the archive identified by ref.
Delete(ctx context.Context, ref ArchiveRef) error
}
// Archived is what one Archive call stored.
type Archived struct {
Ref ArchiveRef
Size int64 // stored bytes
// SHA256 is the hex digest of the stored archive, "" when the backend keeps
// none. world_backups.sha256 records it, so a later Verify tells an archive
// that rotted on disk from a good one.
SHA256 string
// Skipped lists the world entries the archive leaves out (symbolic links,
// devices, sockets, named pipes), relative to the world root.
Skipped []string
}
// ErrCorrupt marks an archive that cannot be read back in full, or whose bytes
// no longer match the checksum recorded for it. It is the only Verify error that
// condemns the archive: any other (a mount that is not there, a cancelled
// context) says nothing about it.
var ErrCorrupt = errors.New("backup: archive corrupt")
// Verifier is implemented by backends that can read a stored archive back end to
// end. Verify returns the archive's SHA256 as read; want, when not "", is the
// digest it must match (an archive recorded before checksums were kept has
// none, and its read-back establishes one).
type Verifier interface {
Verify(ctx context.Context, ref ArchiveRef, want string) (sha256 string, err error)
}
// Sweeper is implemented by backends that can find what interrupted archives
// leave behind.
type Sweeper interface {
// Sweep removes every unfinished archive last written before partialBefore.
// A finished archive that live does not claim (its record was never inserted)
// is removed once it was last written before orphanBefore, and reported and
// kept until then.
Sweep(ctx context.Context, live func(ArchiveRef) bool, partialBefore, orphanBefore time.Time) (Swept, error)
}
// Swept is what one Sweep found.
type Swept struct {
Removed []string // paths removed
// Orphans are finished archives no record claims that are still young enough
// to keep. A backup whose record insert failed looks like this, and so do the
// archives of a store brought back before the database that records them:
// removing them early could throw away the only copy of a world.
Orphans []string
OrphanBytes int64
}
// PVCResolver maps a PVC name to the local filesystem path where it is mounted.
// In production the reaper Job mounts the source/backup PVCs and supplies a
// resolver over those mount points; tests supply temp dirs.
type PVCResolver func(pvc string) (string, error)
// StaticResolver resolves PVC names from a fixed map, erroring on unknown names.
func StaticResolver(paths map[string]string) PVCResolver {
return func(pvc string) (string, error) {
if p, ok := paths[pvc]; ok {
return p, nil
}
return "", &UnknownPVCError{PVC: pvc}
}
}
// UnknownPVCError is returned when a resolver cannot map a PVC name.
type UnknownPVCError struct{ PVC string }
func (e *UnknownPVCError) Error() string { return "backup: unknown pvc " + e.PVC }