mesh/delivery merged: source.main moved to novox/mesh-host@1195cf45971e
mesh/check pass 99285cb080db on 1195cf45971e | unit 303 run, 1080 kept, 5 skipped | integration 306 run, 1082 kept, 4 skipped | over bound: unit (slow…
The bus node's engine writes the bus user list (ADR 0547 bridge); each write makes the bus reload its accounts, and for about three seconds every JetStream request meets no responders. An apply that failed then was kept failed until a key changed (mesh-vault on novox, 2026-10-25 23:04:24), holding every module that needs the secret seat. A transient bus failure is now retried with a backoff (2 s, 5 s, 15 s, 60 s, then every 5 min), the have saying retrying meanwhile; after the engine writes the user list, the transiently failed modules are tried again once the reload is over. A have write that met it is written again the same way. shorten keeps the head and the tail, the cause.
117 lines
4.8 KiB
Go
117 lines
4.8 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/declaration"
|
|
"github.com/nats-io/nats.go"
|
|
"github.com/novox/mesh-host/internal/link"
|
|
)
|
|
|
|
// aFilesLink is a link that serves objects by name, or fails every read with err.
|
|
type aFilesLink struct {
|
|
objects map[string][]byte
|
|
err error
|
|
}
|
|
|
|
func (a aFilesLink) FileObject(_ context.Context, name string, _ int64) ([]byte, error) {
|
|
if a.err != nil {
|
|
return nil, a.err
|
|
}
|
|
if b, ok := a.objects[name]; ok {
|
|
return b, nil
|
|
}
|
|
return nil, os.ErrNotExist
|
|
}
|
|
|
|
func referenceDeclaration(path string, body []byte) []byte {
|
|
return []byte(`{"declaration":1,"resources":[{"id":"m.big","type":"file","path":"` + path + `","digest":"` +
|
|
declaration.DigestOf(body) + `","size":` + strconv.Itoa(len(body)) + `}]}`)
|
|
}
|
|
|
|
// 1695/AC1 and AC2, through the whole apply: a reference is fetched, verified and written; a differing object
|
|
// refuses the declaration whole and writes nothing.
|
|
func TestADeclarationWithAFileByDigestAppliesFromTheObject(t *testing.T) {
|
|
unansweringPacman(t)
|
|
body := []byte(strings.Repeat("a line of a big file\n", 4000))
|
|
name, _ := declaration.ObjectName(declaration.DigestOf(body))
|
|
dir := t.TempDir()
|
|
target := filepath.Join(dir, "big")
|
|
opts := options{state: filepath.Join(dir, "state.json"), out: &strings.Builder{}}
|
|
raw := referenceDeclaration(target, body)
|
|
|
|
bad := append([]byte(nil), body...)
|
|
bad[10] ^= 1
|
|
ctx := link.WithFiles(context.Background(), aFilesLink{objects: map[string][]byte{name: bad}})
|
|
report := applyAndKeep(ctx, opts, raw, nil, nil, nil)
|
|
if report.Refused == "" || !strings.Contains(report.Refused, "m.big") {
|
|
t.Errorf("1695/AC2: a differing object did not refuse the declaration: %+v", report)
|
|
}
|
|
if _, err := os.Stat(target); err == nil {
|
|
t.Error("1695/AC2: a file was written from an object that differs")
|
|
}
|
|
if failedWith(report.Refused).Transient {
|
|
t.Errorf("an object that differs is a real failure, and was taken as the bus not answering: %s", report.Refused)
|
|
}
|
|
|
|
// The bus answers no JetStream request while it reloads its accounts (2026-10-25 23:04:24, mesh-vault on the bus
|
|
// node): the failure is transient, so the agent tries it again, and the detail kept is its cause, the tail.
|
|
noResponders := fmt.Errorf("fetching the object 2bd98ebd of this node's own object bucket: reading the chunks of "+
|
|
"object %s: %w", strings.Repeat("x", 300), nats.ErrNoResponders)
|
|
ctx = link.WithFiles(context.Background(), aFilesLink{err: noResponders})
|
|
report = applyAndKeep(ctx, opts, raw, nil, nil, nil)
|
|
failed := failedWith("a file of this composition could not be had by its digest: file mesh-vault.kept: " + report.Refused)
|
|
if !failed.Transient || !strings.HasSuffix(failed.Detail, nats.ErrNoResponders.Error()) || len(failed.Detail) > 310 ||
|
|
!strings.HasPrefix(failed.Detail, "a file of this composition") {
|
|
t.Errorf("a fetch that met no responders: transient %v, detail %q (%d bytes); want transient, its head and its cause",
|
|
failed.Transient, failed.Detail, len(failed.Detail))
|
|
}
|
|
|
|
ctx = link.WithFiles(context.Background(), aFilesLink{objects: map[string][]byte{name: body}})
|
|
report = applyAndKeep(ctx, opts, raw, nil, nil, nil)
|
|
if report.Refused != "" {
|
|
t.Fatalf("1695/AC1: refused: %s", report.Refused)
|
|
}
|
|
got, err := os.ReadFile(target)
|
|
if err != nil || string(got) != string(body) {
|
|
t.Errorf("1695/AC1: the file holds %d bytes (%v), want %d", len(got), err, len(body))
|
|
}
|
|
}
|
|
|
|
// 1695/AC8: the hand-run `apply <file>` and `reconcile` have no bus: a reference whose file here verifies is applied
|
|
// as it is, any other refuses and writes nothing — never an empty file.
|
|
func TestTheHandApplyNeverWritesAReferenceEmpty(t *testing.T) {
|
|
unansweringPacman(t)
|
|
body := []byte(strings.Repeat("a line of a big file\n", 4000))
|
|
dir := t.TempDir()
|
|
target := filepath.Join(dir, "big")
|
|
opts := options{state: filepath.Join(dir, "state.json"), out: &strings.Builder{}}
|
|
raw := referenceDeclaration(target, body)
|
|
d, err := declaration.ParseFileTrusted(raw)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := runApply(context.Background(), opts, d, raw, fromFile); err == nil || !strings.Contains(err.Error(), "digest") {
|
|
t.Errorf("1695/AC8: a reference with no file and no bus was not refused: %v", err)
|
|
}
|
|
if _, err := os.Stat(target); err == nil {
|
|
t.Error("1695/AC8: a file was written for a reference that could not be had")
|
|
}
|
|
if err := os.WriteFile(target, body, 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
d, _ = declaration.ParseFileTrusted(raw)
|
|
if err := runApply(context.Background(), opts, d, raw, fromFile); err != nil && !strings.Contains(err.Error(), "applied only in part") {
|
|
t.Fatalf("1695/AC8: %v", err)
|
|
}
|
|
if got, _ := os.ReadFile(target); string(got) != string(body) {
|
|
t.Errorf("1695/AC8: the file holds %d bytes, want %d", len(got), len(body))
|
|
}
|
|
}
|