Files
mesh-controller/cmd/mesh-controller/rollout_silent_bus_test.go
T

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