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) } } }