Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lx9FNLG2CqeBbVGGd9bFsP
76 lines
2.4 KiB
Go
76 lines
2.4 KiB
Go
package backup
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/v10networks/nmon/internal/config"
|
|
"github.com/v10networks/nmon/internal/vmsup"
|
|
)
|
|
|
|
// addMetricStores snapshots each store through its own API, archives the snapshot directory under
|
|
// "<name>/" and deletes the snapshot. A store without a local DataDir is skipped with a manifest note.
|
|
func addMetricStores(ctx context.Context, o *Options, aw *archiveWriter, m *Manifest) error {
|
|
for _, vs := range o.Stores {
|
|
if vs.DataDir == "" || vs.URL == "" {
|
|
m.Notes = append(m.Notes, fmt.Sprintf("store %s is external and was not backed up", vs.Name))
|
|
continue
|
|
}
|
|
sm, err := backupOneStore(ctx, o, aw, vs, m.Boundary)
|
|
if err != nil {
|
|
return fmt.Errorf("backup: %s store: %w", vs.Name, err)
|
|
}
|
|
m.Stores = append(m.Stores, sm)
|
|
logf(o.Log, "backup: %s store snapshot archived (%d series fingerprinted)\n", vs.Name, len(sm.Series))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func backupOneStore(ctx context.Context, o *Options, aw *archiveWriter, vs VMStore, boundary time.Time) (sm StoreManifest, err error) {
|
|
api := vmAPI{hc: o.HTTP, base: vs.URL}
|
|
name, err := api.createSnapshot(ctx)
|
|
if err != nil {
|
|
return sm, err
|
|
}
|
|
defer func() {
|
|
// The caller's context may be the reason we are failing; deleting the snapshot must still happen.
|
|
dctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), time.Minute)
|
|
defer cancel()
|
|
if derr := api.deleteSnapshot(dctx, name); derr != nil && err == nil {
|
|
err = derr
|
|
}
|
|
}()
|
|
dir := filepath.Join(vs.DataDir, "snapshots", name)
|
|
if _, err := os.Stat(dir); err != nil {
|
|
return sm, fmt.Errorf("snapshot directory: %w", err)
|
|
}
|
|
if err := aw.addTree(vs.Name, dir); err != nil {
|
|
return sm, err
|
|
}
|
|
series, err := api.sampleChecks(ctx, boundary)
|
|
if err != nil {
|
|
return sm, fmt.Errorf("fingerprint series: %w", err)
|
|
}
|
|
return StoreManifest{Name: vs.Name, Series: series}, nil
|
|
}
|
|
|
|
// StoresFromStatus turns the supervisor's Status() into the stores to back up. An external store has no
|
|
// local data directory, so it is listed without one and skipped.
|
|
func StoresFromStatus(dataDir string, status []vmsup.ChildStatus) []VMStore {
|
|
out := make([]VMStore, 0, len(status))
|
|
for _, c := range status {
|
|
vs := VMStore{Name: c.Store, URL: c.URL}
|
|
if !c.External {
|
|
vs.DataDir = config.HotDir(dataDir)
|
|
if c.Store == vmsup.StoreLong {
|
|
vs.DataDir = config.LongDir(dataDir)
|
|
}
|
|
}
|
|
out = append(out, vs)
|
|
}
|
|
return out
|
|
}
|