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
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.
174 lines
5.3 KiB
Go
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")
|
|
}
|
|
}
|