diff --git a/modules/messenger/cmd/messenger/adr505_test.go b/modules/messenger/cmd/messenger/adr505_test.go index f423f011..1bbec051 100644 --- a/modules/messenger/cmd/messenger/adr505_test.go +++ b/modules/messenger/cmd/messenger/adr505_test.go @@ -42,3 +42,50 @@ func TestTheSettleIsReadFromTheSettingsFileAndIsTwoMinutesByDefault(t *testing.T t.Fatalf("a setting of 0: settle %s, want none", got) } } + +// controllerConditions are the controller's own conditions as it raises them: the lease's (mesh-controller +// signals.go watchLease: scope core, id controller.lease, the machine its host names, which need not be a node's +// name) and the bus's (reconnects.go: scope bus, id the bus user). +func controllerConditions(c *clock) []Condition { + lease := cond("core.controller.lease.lost", Urgent, "the controller lost its lease") + lease.ID, lease.Machine, lease.Raised = "controller.lease", "a-host-name", c.now() + bus := cond("bus.controller.bus-reconnects", Urgent, "the controller reconnects to the bus") + bus.Raised = c.now() + health := cond("module.controller.unhealthy", Urgent, "the controller is not healthy") + health.Raised = c.now() + return []Condition{lease, bus, health} +} + +// ADR 0505 §2a: the controller's lease, bus and health conditions are held while a walk moving mesh-controller is +// open, whatever name their keys give the controller and whatever host they name. +func TestTheControllersOwnConditionsAreHeldWhileAWalkMovesMeshController(t *testing.T) { + h, tg, dt, c, _ := newHolder(t) + walk(t, h, "plan-c", WalkStarted, "", c.now(), 1200, novox("mesh-controller")) + for _, k := range controllerConditions(c) { + h.Condition(EventRaised, k) + } + runFor(h, c, time.Minute) + if saidOn(tg, dt) != 0 { + t.Fatalf("the controller's own conditions were said during its delivery: %+v %+v", tg.sends, dt.sends) + } + for _, k := range controllerConditions(c) { + if !has(outcomes(h, k.Key), "held") { + t.Errorf("%s was not held: %v", k.Key, outcomes(h, k.Key)) + } + } +} + +// ADR 0505 §2a: a walk that does not move mesh-controller holds none of them. +func TestTheControllersOwnConditionsAreNotHeldByAWalkOfAnotherModule(t *testing.T) { + h, tg, dt, c, _ := newHolder(t) + walk(t, h, "plan-g", WalkStarted, "", c.now(), 1200, Touch{Machine: "ace", Modules: []string{"gitea"}}) + for _, k := range controllerConditions(c) { + h.Condition(EventRaised, k) + } + runFor(h, c, time.Minute) + for _, k := range controllerConditions(c) { + if has(outcomes(h, k.Key), "held") || !heard(headlineOf(k.Key), tg, dt) { + t.Errorf("%s was held by a walk of gitea, or not said: %v", k.Key, outcomes(h, k.Key)) + } + } +} diff --git a/modules/messenger/cmd/messenger/walks.go b/modules/messenger/cmd/messenger/walks.go index d16dce8c..33123fb5 100644 --- a/modules/messenger/cmd/messenger/walks.go +++ b/modules/messenger/cmd/messenger/walks.go @@ -155,7 +155,7 @@ func (w *Walk) touches(s subject) bool { } } } - if s.machineNamed { + if s.machineNamed && !s.controller { return false } for _, t := range w.Touches { @@ -719,8 +719,18 @@ type subject struct { machineNamed bool // tokens are every name it carries, to tell a message about a walk itself. tokens []string + // controller: the subject is the controller itself — its lease, its bus connection, its health — matched to the + // module mesh-controller wherever a walk moves it, whatever machine or host the condition names (ADR 0505 §2a). + controller bool } +// controllerNames are the names the controller's own conditions give it in their keys (`core.controller.lease.lost`, +// `bus.controller.bus-reconnects`, `module.controller...`): each is the module ControllerModule (ADR 0505 §2a). +var controllerNames = map[string]bool{"controller": true, ControllerModule: true} + +// ControllerModule is the controller's module, as a walk that moves it names it. +const ControllerModule = "mesh-controller" + // subjectOf is a record's subject: a condition's scope and id (`..` when the record kept no // subject of its own), a notice's subject words. func subjectOf(r *Record) subject { @@ -742,6 +752,11 @@ func subjectOf(r *Record) subject { if r.Machine != "" { s.machines, s.machineNamed = append(s.machines, r.Machine), true } + if (scope == "core" || scope == "bus" || scope == "module" || scope == "seat") && len(tokens) > 0 && controllerNames[tokens[0]] { + // The controller's own subject: the module mesh-controller, wherever it runs (ADR 0505 §2a). + s.modules, s.controller = append(s.modules, ControllerModule), true + return s + } switch scope { case "machine", "node", "bus": s.machines, s.machineNamed = append(s.machines, tokens[0]), true