Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lx9FNLG2CqeBbVGGd9bFsP
63 lines
2.0 KiB
Go
63 lines
2.0 KiB
Go
package backup
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/v10networks/nmon/internal/vmsup"
|
|
)
|
|
|
|
// openStaged opens a file still in the restore staging area.
|
|
func openStaged(dataDir, name string) (*os.File, error) {
|
|
return os.Open(filepath.Join(dataDir, stagingDir, name))
|
|
}
|
|
|
|
// VMVerifier verifies series by starting the restored stores for a moment under a vmsup.Supervisor and
|
|
// comparing each fingerprint with a live /api/v1/export. This is the strongest check available (the real
|
|
// store reads the real restored files) and needs the embedded store binary, so it runs wherever
|
|
// nmon itself runs.
|
|
type VMVerifier struct{ opts vmsup.Options }
|
|
|
|
// NewVMVerifier returns a VMVerifier; opts.DataDir is set per call. Retention defaults are the supervisor's.
|
|
func NewVMVerifier(opts vmsup.Options) *VMVerifier { return &VMVerifier{opts: opts} }
|
|
|
|
// VerifySeries implements SeriesVerifier.
|
|
func (v *VMVerifier) VerifySeries(ctx context.Context, dataDir string, boundary time.Time, stores []StoreManifest) (err error) {
|
|
opts := v.opts
|
|
opts.DataDir = dataDir
|
|
sup, err := vmsup.New(opts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
|
|
defer cancel()
|
|
if err := sup.Start(sctx); err != nil {
|
|
return fmt.Errorf("start restored stores: %w", err)
|
|
}
|
|
defer func() {
|
|
stopCtx, stop := context.WithTimeout(context.WithoutCancel(ctx), time.Minute)
|
|
defer stop()
|
|
err = errors.Join(err, sup.Stop(stopCtx))
|
|
}()
|
|
hc := &http.Client{Timeout: time.Minute}
|
|
for _, s := range stores {
|
|
api := vmAPI{hc: hc, base: sup.URL(s.Name)}
|
|
for _, want := range s.Series {
|
|
got, err := api.fingerprint(ctx, want.Selector, boundary)
|
|
if err != nil {
|
|
return fmt.Errorf("%s store: %w", s.Name, err)
|
|
}
|
|
if got != want {
|
|
return fmt.Errorf("%s store: series %s has %d samples (sha %.12s), backup had %d (sha %.12s)",
|
|
s.Name, want.Selector, got.Samples, got.SHA256, want.Samples, want.SHA256)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|