A reconcile that held the machine to the kept declaration just before a delivery arrived queued its report while the delivery's apply waited for it; the link published the apply's report and then the reconcile's, so the mesh's last word from the machine named the older declaration and the release plan waited on a report it had already been given. The link now sets aside an unasked report about a declaration other than the one it has applied since.
95 lines
3.4 KiB
Go
95 lines
3.4 KiB
Go
package link
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// A reconcile's report about the declaration kept before a delivery is never said after that
|
|
// delivery's report (novox/hq issue 267).
|
|
|
|
// Measured on the home server: the reconcile timer fired three seconds before a declaration
|
|
// arrived. The reconcile held the machine to the declaration kept then; the delivery's apply waited
|
|
// for it, applied the new one and was reported — and then the reconcile's report, queued meanwhile,
|
|
// went out and was stored as the machine's latest account, naming the older declaration. The
|
|
// release plan waited on a report it had already been given.
|
|
func TestAReconcileReportOlderThanTheApplyIsNotSaidAfterIt(t *testing.T) {
|
|
m, key := aMember(t)
|
|
l := newQuietLink(&said{body: signedBy(t, key, []byte(`{"declaration":2}`))})
|
|
outbox := make(chan Unasked, 1)
|
|
|
|
ctx, stop := context.WithCancel(context.Background())
|
|
defer stop()
|
|
settled := make(chan bool, 1)
|
|
apply := func(context.Context, []byte, []byte) Report {
|
|
// The reconcile ran first, on what was kept then, and queued its report while this waited.
|
|
outbox <- Unasked{Report: Report{Declared: "d1", Applied: []string{"a"}, Outward: []string{"eth0"}},
|
|
Done: func(published bool) { settled <- published; stop() }}
|
|
return Report{Declared: "d2", Applied: []string{"a", "b"}, Outward: []string{"eth0"}}
|
|
}
|
|
|
|
done := make(chan error, 1)
|
|
go func() { done <- serve(ctx, l, m, apply, nil, time.Second, outbox, &keptInMemory{}) }()
|
|
select {
|
|
case published := <-settled:
|
|
if published {
|
|
t.Fatal("the reconcile's report was counted as said")
|
|
}
|
|
case <-time.After(5 * time.Second):
|
|
t.Fatal("the reconcile's report was never settled")
|
|
}
|
|
if err := <-done; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
reports := l.said()
|
|
if len(reports) != 1 || reports[0].Declared != "d2" {
|
|
t.Fatalf("the mesh heard %+v; it should have heard only the apply of d2", reports)
|
|
}
|
|
}
|
|
|
|
// A reconcile's report about the declaration this link applied is news, and is said.
|
|
func TestAReconcileReportAboutTheAppliedDeclarationIsSaid(t *testing.T) {
|
|
m, key := aMember(t)
|
|
l := newQuietLink(&said{body: signedBy(t, key, []byte(`{"declaration":2}`))})
|
|
outbox := make(chan Unasked, 1)
|
|
|
|
ctx, stop := context.WithCancel(context.Background())
|
|
defer stop()
|
|
settled := make(chan bool, 1)
|
|
apply := func(context.Context, []byte, []byte) Report {
|
|
outbox <- Unasked{Report: Report{Declared: "d2", Applied: []string{"a", "b"}, Firewall: "nftables"},
|
|
Done: func(published bool) { settled <- published; stop() }}
|
|
return Report{Declared: "d2", Applied: []string{"a", "b"}}
|
|
}
|
|
|
|
go func() { _ = serve(ctx, l, m, apply, nil, time.Second, outbox, nil) }()
|
|
select {
|
|
case published := <-settled:
|
|
if !published {
|
|
t.Fatal("a reconcile's report about the applied declaration was not said")
|
|
}
|
|
case <-time.After(5 * time.Second):
|
|
t.Fatal("the reconcile's report was never settled")
|
|
}
|
|
if reports := l.said(); len(reports) != 2 || reports[1].Declared != "d2" {
|
|
t.Fatalf("the mesh heard %+v", reports)
|
|
}
|
|
}
|
|
|
|
func TestOvertaken(t *testing.T) {
|
|
for _, c := range []struct {
|
|
declared, applied string
|
|
want bool
|
|
}{
|
|
{"d1", "d2", true},
|
|
{"d2", "d2", false},
|
|
{"", "d2", false}, // names no declaration
|
|
{"d1", "", false}, // this link has applied nothing yet
|
|
} {
|
|
if got := overtaken(Report{Declared: c.declared}, c.applied); got != c.want {
|
|
t.Errorf("overtaken(%q, %q) = %v, want %v", c.declared, c.applied, got, c.want)
|
|
}
|
|
}
|
|
}
|