Say an apply's report even when the apply ends the link
A host that delivered its own successor stood aside before the report of that apply was published, so it failed with 'context canceled' and the release plan waited for a report that never came (novox/hq issue 264). Reports are now published on a context the stand-aside does not cancel, and the last apply's report is kept until the broker takes it and said again on the next link, so a crash between apply and report is covered too.
This commit is contained in:
+44
-2
@@ -1068,7 +1068,7 @@ func runLink(ctx context.Context, opts options) error {
|
||||
// Asked with the version this host is RUNNING, read from where it sits — not the link-time
|
||||
// stamp, which every delivered host carries as "development build". Asked with the stamp,
|
||||
// a delivered host never matched the newest delivered version, so it stood aside on every
|
||||
// push for ever, and standing aside cancels the report, so the mesh never heard from it
|
||||
// push for ever, and standing aside then cancelled the report, so the mesh never heard from it
|
||||
// again (novox/hq 04-ISSUES/163).
|
||||
switch next, waiting, err := upgrade.Successor(upgrade.VersionsDir(""), runningVersion()); {
|
||||
case err != nil:
|
||||
@@ -1117,7 +1117,7 @@ func runLink(ctx context.Context, opts options) error {
|
||||
Password: mine.Membership.Password,
|
||||
Transport: mine.Membership.Transport,
|
||||
Signer: mine.Membership.Signer,
|
||||
}, applier, say, opts.timeout, rousedBySignal(ctx), outbox)
|
||||
}, applier, say, opts.timeout, rousedBySignal(ctx), outbox, unsaidBeside(opts.state))
|
||||
// **Cleanly**, or the launcher counts standing aside as a crash and rolls the new host back
|
||||
// before it has run once. The context this returns on was cancelled deliberately, so its error
|
||||
// is not a fault to report.
|
||||
@@ -1127,6 +1127,48 @@ func runLink(ctx context.Context, opts options) error {
|
||||
return held
|
||||
}
|
||||
|
||||
// unsaidFile keeps the last apply's report beside the node's state until the mesh has taken it
|
||||
// (novox/hq issue 264), so a report lost when this host stood aside for its successor — or died
|
||||
// between applying and publishing — is said by whichever host runs next, once it is linked.
|
||||
type unsaidFile struct{ path string }
|
||||
|
||||
func unsaidBeside(statePath string) unsaidFile { return unsaidFile{path: store.UnsaidPath(statePath)} }
|
||||
|
||||
func (u unsaidFile) Keep(r link.Report) error {
|
||||
// A report naming no declaration is one the mesh cannot match to what it sent, and keeping it
|
||||
// would only re-say an older apply after a newer one was refused.
|
||||
if r.Declared == "" {
|
||||
return store.ClearUnsaid(u.path)
|
||||
}
|
||||
body, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return store.SaveUnsaid(u.path, body)
|
||||
}
|
||||
|
||||
func (u unsaidFile) Said(declared string) error {
|
||||
kept, ok, err := u.Pending()
|
||||
if err != nil || !ok || kept.Declared != declared {
|
||||
return err
|
||||
}
|
||||
return store.ClearUnsaid(u.path)
|
||||
}
|
||||
|
||||
func (u unsaidFile) Pending() (link.Report, bool, error) {
|
||||
raw, err := store.ReadUnsaid(u.path)
|
||||
if err != nil || raw == nil {
|
||||
return link.Report{}, false, err
|
||||
}
|
||||
var r link.Report
|
||||
if err := json.Unmarshal(raw, &r); err != nil {
|
||||
// Unreadable is forgotten rather than kept for ever: it can never be said.
|
||||
_ = store.ClearUnsaid(u.path)
|
||||
return link.Report{}, false, fmt.Errorf("the kept report at %s is unreadable, and is dropped: %w", u.path, err)
|
||||
}
|
||||
return r, r.Declared != "", nil
|
||||
}
|
||||
|
||||
// adoptionWatch remembers what the node last said about what it holds and its firewall, so a
|
||||
// reconcile speaks unasked only when that changed.
|
||||
type adoptionWatch struct {
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/novox/mesh-host/internal/link"
|
||||
"github.com/novox/mesh-host/internal/store"
|
||||
)
|
||||
|
||||
// The last apply's report, kept beside the state until the mesh takes it (novox/hq issue 264).
|
||||
|
||||
// What a host that died between applying and reporting kept is read back by the next one exactly —
|
||||
// the digest and the outcome, never a new account of the machine.
|
||||
func TestTheKeptReportSurvivesTheHost(t *testing.T) {
|
||||
state := filepath.Join(t.TempDir(), "state.json")
|
||||
made := link.Report{Declared: "sha256:d1", Applied: []string{"a"}, Failed: map[string]string{"b": "no room"},
|
||||
Host: "v2"}
|
||||
if err := unsaidBeside(state).Keep(made); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
kept, ok, err := unsaidBeside(state).Pending()
|
||||
if err != nil || !ok {
|
||||
t.Fatalf("nothing kept: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(kept, made) {
|
||||
t.Fatalf("kept %+v, made %+v", kept, made)
|
||||
}
|
||||
|
||||
// Taken about another declaration, it stays; taken about this one, it goes.
|
||||
if err := unsaidBeside(state).Said("sha256:other"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, ok, _ := unsaidBeside(state).Pending(); !ok {
|
||||
t.Fatal("a report about another declaration cleared this one")
|
||||
}
|
||||
if err := unsaidBeside(state).Said("sha256:d1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := os.Stat(store.UnsaidPath(state)); !os.IsNotExist(err) {
|
||||
t.Fatalf("the report was taken and is still kept: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// A node that never applied anything has nothing to say again; and an apply refused before its
|
||||
// declaration was read clears an older report, which would otherwise be re-said after it.
|
||||
func TestNothingKeptWhenNothingWasApplied(t *testing.T) {
|
||||
state := filepath.Join(t.TempDir(), "state.json")
|
||||
if _, ok, err := unsaidBeside(state).Pending(); ok || err != nil {
|
||||
t.Fatalf("a node that applied nothing has a report to say: %v %v", ok, err)
|
||||
}
|
||||
if err := unsaidBeside(state).Keep(link.Report{Declared: "sha256:d1"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := unsaidBeside(state).Keep(link.Report{Refused: "forged"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, ok, _ := unsaidBeside(state).Pending(); ok {
|
||||
t.Fatal("an older report is kept after a later declaration was refused")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user