Files
mesh-controller/internal/link/stale_report_test.go
T
jschoubben 19e3eac746
mesh/merge-gate head 19e3eac74658 merged into trunk 89cfa9431d8f — pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every …
mesh/repo-check head 19e3eac74658 merged into trunk 89cfa9431d8f — pass: its merge-check.sh passed
mesh/delivery delivered
The controller no longer subscribes to the alive message: HostBeats, its handler, the heartbeat-gap duration and a fixed silent bound (issue 1183, AC3, AC4, AC5)
2026-10-19 02:16:21 +02:00

124 lines
4.9 KiB
Go

package link
import (
"context"
"errors"
"testing"
)
// Supersession as a check rather than a memory (design 25 §3).
//
// Holding a message in memory let the controller drop an older report when a newer one for the
// same node arrived. On the bus being built the message belongs to the server and comes back
// whatever happened meanwhile — so the older report is redelivered *after* the newer was applied,
// and acting on it would undo the newer.
//
// The answer was already in the message: a report carries the digest of the declaration it is
// about, so "is this the past?" is a question the message answers.
// sentAndHeard records reports and knows what was last sent, which is the pair the check needs.
type sentAndHeard struct {
sent string
heard []Report
err error
}
func (s *sentAndHeard) Heard(_ context.Context, r Report) (bool, error) {
if s.err != nil {
return false, s.err
}
s.heard = append(s.heard, r)
return true, nil
}
func (s *sentAndHeard) Outstanding(context.Context, string) (string, error) { return s.sent, nil }
// A report about a declaration the mesh has moved past is settled and not acted on. Settled rather
// than dropped, because there is nothing wrong with the message — it is simply the past, and
// redelivering it for ever is worse than letting it go.
func TestAReportAboutASupersededDeclarationIsNotActedOn(t *testing.T) {
store := &sentAndHeard{sent: "d2"}
s, in := serving()
s.listener = store
to := &settled{}
s.act(context.Background(), in.sends(t, to, KindReport, aReport("anchor", "d1")))
if len(store.heard) != 0 {
t.Fatalf("a report about a superseded declaration was acted on: %+v", store.heard)
}
if !to.acked {
t.Fatalf("a superseded report was not settled, so it comes back for ever: %+v", *to)
}
}
// The report about the declaration that *is* outstanding is acted on, and so is one from a node
// the mesh has no digest for — an older host that says nothing about which declaration it applied
// has nothing to be judged against, and refusing it would silence every node built before reports
// carried the digest.
func TestAReportAboutTheOutstandingDeclarationIsActedOn(t *testing.T) {
for _, c := range []struct{ what, sent, declared string }{
{"the one outstanding", "d2", "d2"},
{"a report that says nothing about which", "d2", ""},
{"a node nothing was ever sent", "", "d1"},
} {
store := &sentAndHeard{sent: c.sent}
s, in := serving()
s.listener = store
to := &settled{}
s.act(context.Background(), in.sends(t, to, KindReport, aReport("anchor", c.declared)))
if len(store.heard) != 1 || !to.acked {
t.Errorf("%s: was not acted on and acknowledged: heard %+v, settled %+v",
c.what, store.heard, *to)
}
}
}
// **Staleness is decided before the store is waited on**, not after: a redelivery that lost its
// race is not worth holding a slot in the window that a current message needs.
func TestASupersededReportIsNotHeldForTheStore(t *testing.T) {
store := &sentAndHeard{sent: "d2", err: errors.Join(ErrTryAgain, errors.New("starting up"))}
s, in := serving()
s.listener = store
to := &settled{}
s.act(context.Background(), in.sends(t, to, KindReport, aReport("anchor", "d1")))
if !to.acked || len(in.held) != 0 {
t.Fatalf("a superseded report waited for the store: %+v, %d held", *to, len(in.held))
}
}
// **Half of a report is not about a declaration, and that half is never stale.**
//
// What the machine *is* — the tunnel it took over, the ports its own bundle holds, what an adopted
// node found and is keeping, a node moving its overlay key — reaches the mesh on a report and
// nowhere else. A rekey set aside as stale is a node whose overlay key never moves, and no retry is
// coming, because the node said it once. So a report carrying any of these is acted on whenever it
// arrives, however far the mesh has moved on.
func TestAReportCarryingWhatOnlyTheNodeKnowsIsActedOnHoweverOldItIs(t *testing.T) {
for _, c := range []struct {
what string
report Report
}{
{"a rekey", Report{Node: "anchor", Declared: "d1",
Rekey: &Rekey{Previous: "k1", OverlayKey: "k2"}}},
{"the tunnel it carried", Report{Node: "anchor", Declared: "d1",
Tunnel: &CarriedTunnel{Interface: "wg0", State: "taken"}}},
{"what an adopted node holds", Report{Node: "anchor", Declared: "d1",
Held: []Held{{ID: "conf", Module: "web", Kind: "file"}}}},
{"the firewall it found", Report{Node: "anchor", Declared: "d1", Firewall: "ufw"}},
{"what is reachable on it", Report{Node: "anchor", Declared: "d1",
Reachable: []Reach{{Protocol: "tcp", Port: 443}}}},
{"the ports its own bundle holds", Report{Node: "anchor", Declared: "d1",
Carried: []int{5432}}},
} {
store := &sentAndHeard{sent: "d9"}
s, in := serving()
s.listener = store
to := &settled{}
s.act(context.Background(), in.sends(t, to, KindReport, c.report))
if len(store.heard) != 1 {
t.Errorf("%s was set aside as stale, and the mesh will never hear it again: %+v",
c.what, *to)
}
}
}