121 lines
3.7 KiB
Go
121 lines
3.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/nats-io/nats.go"
|
|
"github.com/nats-io/nats.go/jetstream"
|
|
|
|
"github.com/novox/mesh-controller/internal/broker"
|
|
"github.com/novox/mesh-controller/internal/testbus"
|
|
)
|
|
|
|
// silentOnABus is the rollout's silent keys on a real bus, and the waits its watcher heard expire.
|
|
func silentOnABus(t *testing.T) (*busSilent, <-chan silentWait) {
|
|
t.Helper()
|
|
js, err := broker.Dial(testbus.URL(t))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(js.Close)
|
|
if err := js.EnsureControllerBuckets(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
conn, err := nats.Connect(testbus.URL(t))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(conn.Close)
|
|
api, err := jetstream.New(conn)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
heard := make(chan silentWait, 16)
|
|
ctx, stop := context.WithCancel(context.Background())
|
|
t.Cleanup(stop)
|
|
s, err := newBusSilent(ctx, api, func(w silentWait) { heard <- w })
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return s, heard
|
|
}
|
|
|
|
func hearsSilent(t *testing.T, heard <-chan silentWait, within time.Duration) (silentWait, bool) {
|
|
t.Helper()
|
|
select {
|
|
case w := <-heard:
|
|
return w, true
|
|
case <-time.After(within):
|
|
return silentWait{}, false
|
|
}
|
|
}
|
|
|
|
// A machine that never reports on its send is named silent when its key expires (issue 974, novox/hq ADR 0406 §5):
|
|
// the watcher names the plan, the tier and the machine, which a plan id or a machine name with a dot does not blur.
|
|
func TestAKeyLeftAloneExpiresAndTheWatcherNamesTheMachineSilent(t *testing.T) {
|
|
s, heard := silentOnABus(t)
|
|
want := silentWait{Plan: "plan.7", Tier: 2, Node: "ace"}
|
|
if err := s.Arm(t.Context(), want, time.Second, time.Now()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got, ok := hearsSilent(t, heard, 10*time.Second); !ok || got != want {
|
|
t.Fatalf("heard %+v (%v); want %+v", got, ok, want)
|
|
}
|
|
}
|
|
|
|
// A machine that reports in time is never named silent: the report deletes the key, and a delete is not an expiry.
|
|
func TestAReportDeletesTheKeyAndNoOneIsNamedSilent(t *testing.T) {
|
|
s, heard := silentOnABus(t)
|
|
ctx := t.Context()
|
|
w := silentWait{Plan: "plan-1", Tier: 0, Node: "g14"}
|
|
if err := s.Arm(ctx, w, time.Second, time.Now()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.Reported(ctx, w); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got, ok := hearsSilent(t, heard, 2500*time.Millisecond); ok {
|
|
t.Fatalf("%+v was named silent after it reported", got)
|
|
}
|
|
// Reporting twice, or on a send that put no key, is no error.
|
|
if err := s.Reported(ctx, w); err != nil {
|
|
t.Fatalf("a second report: %v", err)
|
|
}
|
|
}
|
|
|
|
// A send made again to a machine puts its key again with the full time, and only the last send's key can expire.
|
|
func TestASendMadeAgainPutsTheKeyAgain(t *testing.T) {
|
|
s, heard := silentOnABus(t)
|
|
ctx := t.Context()
|
|
w := silentWait{Plan: "plan-3", Tier: 1, Node: "novox"}
|
|
if err := s.Arm(ctx, w, 2*time.Second, time.Now()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
time.Sleep(1200 * time.Millisecond)
|
|
if err := s.Arm(ctx, w, 2*time.Second, time.Now()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got, ok := hearsSilent(t, heard, 1500*time.Millisecond); ok {
|
|
t.Fatalf("%+v expired at the first send's time, not the second's", got)
|
|
}
|
|
if got, ok := hearsSilent(t, heard, 5*time.Second); !ok || got != w {
|
|
t.Fatalf("heard %+v (%v); want %+v after the second send's time", got, ok, w)
|
|
}
|
|
}
|
|
|
|
func TestASilentKeyIsParsedFromTheRightOfTheNode(t *testing.T) {
|
|
for _, w := range []silentWait{{"plan-1", 0, "ace"}, {"a.b", 12, "g14"}} {
|
|
got, ok := parseSilentKey(silentKey(w))
|
|
if !ok || got != w {
|
|
t.Errorf("%+v became %+v (%v)", w, got, ok)
|
|
}
|
|
}
|
|
for _, bad := range []string{"", "plan", "plan.x.ace", "plan..ace", ".1.ace"} {
|
|
if _, ok := parseSilentKey(bad); ok {
|
|
t.Errorf("%q was read as a wait", bad)
|
|
}
|
|
}
|
|
}
|