Main reverted G's half merge (#189, reverted by8afc3ebin #191) and then took the reader release's S1-S5, which copied G's code. G's form holds where they differ (process_bus.go, seats.go, state.go and their tests); the release's kept names stay so main's callers keep building: SeatViewPrefix beside SeatReadPrefix, and QueueInvoke's empty strings on not-ok (with its test TestQueueInvoke). The tree differs from G's headf30dee54only by those and the release's two tests (objects cap option, seat states and stream kinds). Builds: design 47 G (the milestone's sdk part, as atf30dee54) Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
87 lines
2.9 KiB
Go
87 lines
2.9 KiB
Go
// Package busarchive is the bus's snapshot archive: its manifest, reading one back and holding it to
|
|
// its checksums. The snapshot program (cmd/mesh-nats-snapshot) writes and restores with it and the bus's
|
|
// tool nats_snapshot_verify (cmd/nats-tools) checks with it, so the two never differ on what a good
|
|
// snapshot is.
|
|
package busarchive
|
|
|
|
import (
|
|
"archive/tar"
|
|
"io"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/klauspost/compress/s2"
|
|
)
|
|
|
|
// Manifest is the first entry of an archive: what was taken, when, how long it took, and the checksum
|
|
// of every file beside it. It is what `verify` and `restore` hold the archive to.
|
|
type Manifest struct {
|
|
Format int `json:"format"`
|
|
Server string `json:"server_version"`
|
|
Started time.Time `json:"started"`
|
|
Finished time.Time `json:"finished"`
|
|
Seconds float64 `json:"seconds"`
|
|
// Messages and StreamBytes are the streams' own totals at their snapshots; ArchiveBytes what the
|
|
// archives take, compressed.
|
|
Messages uint64 `json:"messages"`
|
|
StreamBytes int64 `json:"stream_bytes"`
|
|
ArchiveBytes int64 `json:"archive_bytes"`
|
|
Streams []StreamEntry `json:"streams"`
|
|
// Skipped are streams that hold nothing across a restart (memory storage), said rather than
|
|
// silently left out.
|
|
Skipped []Skipped `json:"skipped,omitempty"`
|
|
}
|
|
|
|
// StreamEntry is one stream as it was snapshotted.
|
|
type StreamEntry struct {
|
|
Name string `json:"name"`
|
|
Subjects []string `json:"subjects,omitempty"`
|
|
Retention string `json:"retention,omitempty"`
|
|
Messages uint64 `json:"messages"`
|
|
Bytes uint64 `json:"bytes"`
|
|
FirstSeq uint64 `json:"first_seq"`
|
|
LastSeq uint64 `json:"last_seq"`
|
|
Deleted int `json:"deleted,omitempty"`
|
|
Consumers int `json:"consumers"`
|
|
// Meta is the server's own description of the stream (its configuration and state), in the
|
|
// shape the restore API and the nats CLI take back: backup.json.
|
|
Meta string `json:"meta"`
|
|
MetaSHA256 string `json:"meta_sha256"`
|
|
// Archive is the server's snapshot of the stream, as the server sent it: stream.tar.s2.
|
|
Archive string `json:"archive"`
|
|
ArchiveBytes int64 `json:"archive_bytes"`
|
|
ArchiveSHA256 string `json:"archive_sha256"`
|
|
Seconds float64 `json:"seconds"`
|
|
}
|
|
|
|
// Skipped is a stream not snapshotted, and why.
|
|
type Skipped struct {
|
|
Name string `json:"name"`
|
|
Why string `json:"why"`
|
|
}
|
|
|
|
// ManifestName is the archive's first entry.
|
|
const ManifestName = "manifest.json"
|
|
|
|
// ReadsThrough is whether a stream's archive decompresses and lists to its end.
|
|
func ReadsThrough(r io.Reader) error {
|
|
tr := tar.NewReader(s2.NewReader(r))
|
|
for {
|
|
_, err := tr.Next()
|
|
if err == io.EOF {
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, err := io.Copy(io.Discard, tr); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
// ValidName is a stream name that is one safe path component.
|
|
func ValidName(name string) bool {
|
|
return name != "" && name != "." && name != ".." && !strings.ContainsAny(name, "/\\\x00 *>")
|
|
}
|