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

83 lines
2.5 KiB
Go

//go:build integration
package main
import (
"context"
"testing"
"time"
sdkdelivery "git.novox.be/novox/mesh-sdk/go/delivery"
"github.com/nats-io/nats.go/jetstream"
"github.com/novox/mesh-controller/internal/broker"
"github.com/novox/mesh-controller/internal/inventory"
"github.com/novox/mesh-controller/internal/testbus"
)
// 1183/AC5: with the watchman's presence bucket holding a node online and no alive message ever sent, the node reads here
// in `nodes` four minutes later by the controller's clock, however old its last report; and an alive message still sent
// by a node-engine is published to the same bus without error and changes nothing.
func TestAnOnlineNodeReadsHereWithNoAliveMessage(t *testing.T) { // 1183/AC5
js, err := broker.Dial(testbus.URL(t))
if err != nil {
t.Fatal(err)
}
t.Cleanup(js.Close)
if _, err := broker.RaiseBuckets(js, []broker.Bucket{
{Module: sdkdelivery.WatchmanModule, Name: "presence", History: sdkdelivery.PresenceHistory},
}); err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
api, err := jetstream.New(js.Conn())
if err != nil {
t.Fatal(err)
}
kv, err := api.KeyValue(ctx, sdkdelivery.PresenceBucket)
if err != nil {
t.Fatal(err)
}
put := func(node string, online bool) {
t.Helper()
key, err := sdkdelivery.PresenceNodeKey(node)
if err != nil {
t.Fatal(err)
}
body, err := sdkdelivery.EncodePresence(sdkdelivery.Presence{Online: online})
if err != nil {
t.Fatal(err)
}
if _, err := kv.Put(ctx, key, body); err != nil {
t.Fatal(err)
}
}
put("ace", true)
put("laptop", false)
// A node-engine still sending its alive message: harmless.
if err := js.Conn().Publish("mesh.control.ace.alive", []byte(`{"node":"ace"}`)); err != nil {
t.Fatalf("an alive message could not be published: %v", err)
}
if err := js.Conn().Flush(); err != nil {
t.Fatal(err)
}
later := time.Now().Add(4 * time.Minute)
nodes := []inventory.Node{{Name: "ace", LastSeen: time.Now().Add(-time.Hour)},
{Name: "laptop", LastSeen: time.Now().Add(-time.Hour)}}
read := func(ctx context.Context, names []string) (map[string]presenceEntry, error) {
return readPresence(ctx, js.Conn(), names)
}
heard, err := nodesHeard(ctx, nodes, read, later)
if err != nil {
t.Fatal(err)
}
if got := heardFromAt(heard[0], later); got != "here" {
t.Fatalf("a node online by presence reads %q four minutes later", got)
}
if got := heardFromAt(heard[1], later); got == "here" {
t.Fatalf("a node offline by presence reads %q", got)
}
}