Files
mesh-controller/cmd/mesh-controller/last_heard_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

113 lines
4.2 KiB
Go

package main
import (
"context"
"errors"
"strings"
"testing"
"time"
"github.com/novox/mesh-controller/internal/inventory"
)
// aPresenceFake is the watchman's presence bucket as a reader sees it.
func aPresenceFake(entries map[string]presenceEntry, err error) presenceReader {
return func(_ context.Context, _ []string) (map[string]presenceEntry, error) { return entries, err }
}
// 1183/AC1: one function says when a machine was last heard: online is now, offline is the entry's time, and only where
// presence has no key, the last report.
func TestLastHeardIsPresenceThenTheReport(t *testing.T) { // 1183/AC1
now := time.Now()
report := now.Add(-time.Hour)
if got := lastHeard(report, nil, now); !got.Equal(report) {
t.Fatalf("no presence key: last heard is %v, want the report %v", got, report)
}
if got := lastHeard(report, &presenceEntry{Online: true, At: now.Add(-9 * time.Hour)}, now); !got.Equal(now) {
t.Fatalf("online: last heard is %v, want now", got)
}
offlineAt := now.Add(-4 * time.Minute)
if got := lastHeard(now.Add(-time.Minute), &presenceEntry{At: offlineAt}, now); !got.Equal(offlineAt) {
t.Fatalf("offline: last heard is %v, want the entry's time %v, not a report's", got, offlineAt)
}
if got := lastHeard(time.Time{}, nil, now); !got.IsZero() {
t.Fatalf("never heard and no key: %v", got)
}
}
// The machines facts, the nodes list, node and status all take that one function.
func TestEveryReaderOfLastHeardTakesPresence(t *testing.T) { // 1183/AC1
now := time.Now()
m := machineFacts{name: "laptop", lastHeard: now.Add(-time.Minute)}
applyPresence(&m, nil, &presenceEntry{At: now.Add(-4 * time.Minute)}, now)
if !m.lastHeard.Equal(now.Add(-4 * time.Minute)) {
t.Fatalf("machine facts take a report over the offline entry: %v", m.lastHeard)
}
}
func twoNodes(now time.Time) []inventory.Node {
return []inventory.Node{
{Name: "anchor", LastSeen: now.Add(-time.Hour)},
{Name: "laptop", LastSeen: now.Add(-30 * time.Second)},
{Name: "fresh"},
}
}
// 1183/AC2: a machine online by presence whose last report is an hour old reads here, is not quiet and is not silent; one
// offline for 4 minutes reads out of touch.
func TestOnlineByPresenceReadsHereWhateverTheReport(t *testing.T) { // 1183/AC2
now := time.Now()
read := aPresenceFake(map[string]presenceEntry{
"anchor": {Online: true, At: now.Add(-6 * time.Hour)},
"laptop": {Online: false, At: now.Add(-4 * time.Minute)},
}, nil)
nodes, err := nodesHeard(context.Background(), twoNodes(now), read, now)
if err != nil {
t.Fatal(err)
}
if got := heardFromAt(nodes[0], now); got != "here" {
t.Errorf("anchor online with an hour-old report reads %q", got)
}
if got := heardFromAt(nodes[1], now); got != "out of touch 4m" {
t.Errorf("laptop offline 4 minutes reads %q", got)
}
if got := heardFromAt(nodes[2], now); got != "never spoken" {
t.Errorf("a machine with no key and no report reads %q", got)
}
quiet := notHeardLately(nodes, now)
if len(quiet) != 1 || quiet[0].Name != "fresh" {
t.Errorf("quiet is %+v: an online machine is not quiet, and a machine offline 4 minutes is not quiet yet", quiet)
}
f := &machineFacts{name: "anchor", lastHeard: nodes[0].LastSeen}
if f.silent(now) {
t.Errorf("a machine online by presence is silent")
}
}
// Unreadable presence is said, and the report stands as it was.
func TestUnreadablePresenceLeavesTheReport(t *testing.T) { // 1183/AC1
now := time.Now()
nodes, err := nodesHeard(context.Background(), twoNodes(now), aPresenceFake(nil, errors.New("no bus")), now)
if err == nil || !strings.Contains(err.Error(), "no bus") {
t.Fatalf("an unreadable presence is not said: %v", err)
}
if !nodes[0].LastSeen.Equal(now.Add(-time.Hour)) {
t.Fatalf("the report was replaced: %v", nodes[0].LastSeen)
}
}
// 1183/AC4: where the watchman gives no verdict a machine is silent after a fixed three minutes, whatever interval a
// node-engine once said.
func TestTheSilentBoundIsFixed(t *testing.T) { // 1183/AC4
now := time.Now()
for _, c := range []struct {
ago time.Duration
want bool
}{{179 * time.Second, false}, {181 * time.Second, true}} {
m := machineFacts{name: "laptop", lastHeard: now.Add(-c.ago)}
if got := m.silent(now); got != c.want {
t.Errorf("heard %v ago: silent %v, want %v", c.ago, got, c.want)
}
}
}