Files
mesh-catalog/modules/nats/internal/busarchive/verifyfile_test.go
T
jschoubben 98ca97fc5b
mesh/check failed: ace: novox/mesh-catalog#442: checks/particulars/go.mod cannot be read to check its mesh-sdk pin: Gitea API /repos/novox/mesh-catalo…
mesh/merge-gate head 98ca97fc5b3f merged into trunk dcf6e86d0236 — pass: builds nats, sent nowhere; merging this needs a person's bus upgrade; 1 wait(s) fo…
mesh/repo-check head 98ca97fc5b3f merged into trunk dcf6e86d0236 — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery delivered
Verify the bus's snapshot with a tool, sharing the snapshot program's archive code (issue 1842)
The upgrade and restore steps told a person to docker exec the snapshot program; no mesh tool
did it. nats_snapshot_verify reads the newest bus.tar of the snapshots directory only.
2026-10-22 14:24:25 +02:00

174 lines
5.3 KiB
Go

package busarchive
import (
"archive/tar"
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/klauspost/compress/s2"
)
func sum(b []byte) string { h := sha256.Sum256(b); return hex.EncodeToString(h[:]) }
// streamArchive is a stream's archive as the server sends it: a tar of one file, s2-compressed.
func streamArchive(t *testing.T) []byte {
t.Helper()
var raw bytes.Buffer
tw := tar.NewWriter(&raw)
body := []byte(strings.Repeat("a message of the stream\n", 50))
if err := tw.WriteHeader(&tar.Header{Name: "msgs.blk", Mode: 0o600, Size: int64(len(body))}); err != nil {
t.Fatal(err)
}
tw.Write(body)
tw.Close()
var out bytes.Buffer
w := s2.NewWriter(&out)
w.Write(raw.Bytes())
w.Close()
return out.Bytes()
}
// snapshotTar builds a snapshot of two streams; the mutations change what the manifest says or what is
// stored, so each fault is made in the one place it is found.
func snapshotTar(t *testing.T, mutate func(m *Manifest, files map[string][]byte)) []byte {
t.Helper()
files := map[string][]byte{}
m := Manifest{Format: 1, Server: "2.11.17", Finished: time.Date(2026, 10, 22, 3, 0, 0, 0, time.UTC)}
for i, name := range []string{"EVENTS", "KV_conditions"} {
meta := []byte(`{"config":{"name":"` + name + `"}}`)
arc := streamArchive(t)
e := StreamEntry{Name: name, LastSeq: uint64(10 * (i + 1)),
Meta: "streams/" + name + "/backup.json", MetaSHA256: sum(meta),
Archive: "streams/" + name + "/stream.tar.s2", ArchiveBytes: int64(len(arc)), ArchiveSHA256: sum(arc)}
m.Streams = append(m.Streams, e)
files[e.Meta], files[e.Archive] = meta, arc
}
if mutate != nil {
mutate(&m, files)
}
var out bytes.Buffer
tw := tar.NewWriter(&out)
put := func(name string, b []byte) {
if err := tw.WriteHeader(&tar.Header{Name: name, Mode: 0o600, Size: int64(len(b))}); err != nil {
t.Fatal(err)
}
tw.Write(b)
}
raw, _ := json.Marshal(m)
put(ManifestName, raw)
for _, s := range m.Streams {
put(s.Meta, files[s.Meta])
put(s.Archive, files[s.Archive])
}
tw.Close()
return out.Bytes()
}
func put(t *testing.T, dir, name string, b []byte, at time.Time) {
t.Helper()
p := filepath.Join(dir, name)
if err := os.WriteFile(p, b, 0o600); err != nil {
t.Fatal(err)
}
os.Chtimes(p, at, at)
}
func TestAGoodSnapshotPassesAndSaysItsStreams(t *testing.T) {
dir := t.TempDir()
good := snapshotTar(t, nil)
put(t, dir, "bus.tar", good, time.Now())
r, err := VerifyFile(dir, "")
if err != nil {
t.Fatal(err)
}
if !r.Passed || r.Fault != "" || r.File != "bus.tar" || r.Size != int64(len(good)) {
t.Fatalf("%+v", r)
}
if r.Taken == nil || !r.Taken.Equal(time.Date(2026, 10, 22, 3, 0, 0, 0, time.UTC)) {
t.Errorf("taken %v", r.Taken)
}
if len(r.Streams) != 2 || r.Streams[0] != (StreamReport{"EVENTS", 10}) || r.Streams[1] != (StreamReport{"KV_conditions", 20}) {
t.Errorf("streams %+v", r.Streams)
}
}
func TestACorruptedChecksumFailsNamingTheFile(t *testing.T) {
dir := t.TempDir()
put(t, dir, "bus.tar", snapshotTar(t, func(m *Manifest, files map[string][]byte) {
m.Streams[1].ArchiveSHA256 = sum([]byte("something else"))
}), time.Now())
r, err := VerifyFile(dir, "bus.tar")
if err != nil {
t.Fatal(err)
}
if r.Passed || !strings.Contains(r.Fault, "streams/KV_conditions/stream.tar.s2") || !strings.Contains(r.Fault, "checksum") {
t.Fatalf("%+v", r)
}
if len(r.Streams) != 2 {
t.Errorf("a failed snapshot still says its streams: %+v", r.Streams)
}
}
func TestATruncatedSnapshotFails(t *testing.T) {
dir := t.TempDir()
good := snapshotTar(t, nil)
put(t, dir, "bus.tar", good[:len(good)*2/3], time.Now())
r, err := VerifyFile(dir, "")
if err != nil {
t.Fatal(err)
}
if r.Passed || r.Fault == "" {
t.Fatalf("a truncated snapshot passed: %+v", r)
}
}
func TestAnArchiveThatDoesNotReadToItsEndFailsEvenWithMatchingChecksums(t *testing.T) {
dir := t.TempDir()
put(t, dir, "bus.tar", snapshotTar(t, func(m *Manifest, files map[string][]byte) {
bad := []byte("not an s2 stream at all")
s := &m.Streams[0]
files[s.Archive] = bad
s.ArchiveSHA256, s.ArchiveBytes = sum(bad), int64(len(bad))
}), time.Now())
r, _ := VerifyFile(dir, "")
if r.Passed || !strings.Contains(r.Fault, "does not read") {
t.Fatalf("%+v", r)
}
}
func TestTheNewestTarIsVerifiedAndOnlyInsideTheDirectory(t *testing.T) {
dir := t.TempDir()
now := time.Now()
put(t, dir, "old.tar", []byte("junk"), now.Add(-48*time.Hour))
put(t, dir, "bus.tar", snapshotTar(t, nil), now)
put(t, dir, "bus.tar.partial", []byte("half"), now.Add(time.Hour))
r, err := VerifyFile(dir, "")
if err != nil || r.File != "bus.tar" || !r.Passed {
t.Fatalf("%+v %v", r, err)
}
if r, _ := VerifyFile(dir, "old.tar"); r.Passed || r.File != "old.tar" {
t.Errorf("a named file is the one checked: %+v", r)
}
outside := t.TempDir()
put(t, outside, "x.tar", snapshotTar(t, nil), now)
for _, name := range []string{"../" + filepath.Base(outside) + "/x.tar", outside + "/x.tar", ".."} {
if _, err := VerifyFile(dir, name); err == nil {
t.Errorf("%q was read", name)
}
}
os.Symlink(filepath.Join(outside, "x.tar"), filepath.Join(dir, "link.tar"))
if _, err := VerifyFile(dir, "link.tar"); err == nil {
t.Error("a symbolic link out of the directory was followed")
}
if _, err := VerifyFile(t.TempDir(), ""); err == nil {
t.Error("an empty directory is not a snapshot")
}
}