mesh-watcher: read who holds the controller and the bus from the placement seat's state, not the controller's seats (issue 1955, ADR 0571 clause 4)
This commit is contained in:
@@ -15,6 +15,7 @@ import (
|
||||
"time"
|
||||
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/health"
|
||||
)
|
||||
|
||||
@@ -150,60 +151,46 @@ func roundTrip(node string) error {
|
||||
}
|
||||
}
|
||||
|
||||
// controlNodes reads which machines hold the controller and the bus.
|
||||
// controlNodes reads which machines hold the controller and the bus, from the placement seat's state of who holds
|
||||
// which seat (novox/hq ADR 0571 clause 4).
|
||||
func controlNodes() ([]string, error) {
|
||||
raw, err := stdio.Ask("seat:mesh-controller.seats", map[string]any{})
|
||||
st := stdio.State(delivery.SeatHoldersRead)
|
||||
names, err := st.Keys()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return holdersOf(raw, "mesh-controller", "mesh-broker")
|
||||
keys := map[string][]byte{}
|
||||
for _, k := range names {
|
||||
e, err := st.Get(k)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if e != nil {
|
||||
keys[k] = e.Value
|
||||
}
|
||||
}
|
||||
return holderNodes(keys, "mesh-controller", "mesh-broker")
|
||||
}
|
||||
|
||||
// holdersOf reads the seats verb's answer, bare or inside a tool reply, for the named seats' machines.
|
||||
func holdersOf(raw json.RawMessage, seats ...string) ([]string, error) {
|
||||
var answer struct {
|
||||
Seats []struct {
|
||||
Seat string `json:"seat"`
|
||||
Holders []struct {
|
||||
Node string `json:"node"`
|
||||
} `json:"holders"`
|
||||
} `json:"seats"`
|
||||
Output string `json:"output"`
|
||||
Content []struct {
|
||||
Text string `json:"text"`
|
||||
} `json:"content"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &answer); err != nil {
|
||||
var s string
|
||||
if json.Unmarshal(raw, &s) == nil {
|
||||
return holdersOf(json.RawMessage(s), seats...)
|
||||
}
|
||||
return nil, fmt.Errorf("the seats answer is not JSON")
|
||||
}
|
||||
if len(answer.Seats) == 0 {
|
||||
switch {
|
||||
case answer.Output != "":
|
||||
return holdersOf(json.RawMessage(answer.Output), seats...)
|
||||
case len(answer.Content) > 0:
|
||||
return holdersOf(json.RawMessage(answer.Content[0].Text), seats...)
|
||||
}
|
||||
return nil, fmt.Errorf("the seats answer lists no seat")
|
||||
}
|
||||
// holderNodes is the machines holding any of the seats, in the order the seats are named, each once.
|
||||
func holderNodes(keys map[string][]byte, seats ...string) ([]string, error) {
|
||||
var out []string
|
||||
seen := map[string]bool{}
|
||||
for _, s := range answer.Seats {
|
||||
for _, want := range seats {
|
||||
if s.Seat != want {
|
||||
continue
|
||||
}
|
||||
for _, h := range s.Holders {
|
||||
if !seen[h.Node] {
|
||||
seen[h.Node] = true
|
||||
out = append(out, h.Node)
|
||||
}
|
||||
for _, seat := range seats {
|
||||
holders, err := delivery.HoldersOf(keys, seat, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, h := range holders {
|
||||
if !seen[h.Node] {
|
||||
seen[h.Node] = true
|
||||
out = append(out, h.Node)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(out) == 0 {
|
||||
return nil, fmt.Errorf("the placement seat's state names no holder of %s", strings.Join(seats, " or "))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ func TestTheManifestSaysWhatTheCodeDoes(t *testing.T) {
|
||||
if !reflect.DeepEqual(m.Consumes, []string{ControllerSeat + "." + HeartbeatEvent, "mesh-self-check.checked"}) {
|
||||
t.Fatalf("consumes %v", m.Consumes)
|
||||
}
|
||||
if !reflect.DeepEqual(m.Invokes, []string{m.Module + ".watcher_ping", "seat:mesh-controller.seats"}) {
|
||||
if !reflect.DeepEqual(m.Invokes, []string{m.Module + ".watcher_ping"}) {
|
||||
t.Fatalf("invokes %v", m.Invokes)
|
||||
}
|
||||
if len(m.Claims) != 0 {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -214,16 +213,18 @@ func TestOnTheControlNodeItSaysItIsMisplaced(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTheSeatsAnswerIsReadInEitherShape(t *testing.T) {
|
||||
bare := `{"seats":[{"seat":"mesh-controller","holders":[{"node":"n1","module":"mesh-controller"}]},
|
||||
{"seat":"mesh-broker","holders":[{"node":"n1"},{"node":"n2"}]},{"seat":"git","holders":[{"node":"n3"}]}]}`
|
||||
wrapped, _ := json.Marshal(map[string]any{"content": []map[string]string{{"type": "text", "text": bare}}})
|
||||
output, _ := json.Marshal(map[string]any{"ok": true, "output": bare})
|
||||
for _, raw := range []string{bare, string(wrapped), string(output)} {
|
||||
got, err := holdersOf(json.RawMessage(raw), "mesh-controller", "mesh-broker")
|
||||
if err != nil || strings.Join(got, ",") != "n1,n2" {
|
||||
t.Errorf("%s: %v %v", raw, got, err)
|
||||
}
|
||||
func TestTheHoldersAreReadFromThePlacementSeatsState(t *testing.T) {
|
||||
keys := map[string][]byte{
|
||||
"mesh-controller": []byte(`{"v":1,"seat":"mesh-controller","scope":"mesh","holders":[{"module":"mesh-controller","node":"n1"}]}`),
|
||||
"mesh-broker": []byte(`{"v":1,"seat":"mesh-broker","scope":"mesh","holders":[{"module":"nats","node":"n1"},{"module":"nats","node":"n2"}]}`),
|
||||
"git": []byte(`{"v":1,"seat":"git","scope":"mesh","holders":[{"module":"gitea","node":"n3"}]}`),
|
||||
}
|
||||
got, err := holderNodes(keys, "mesh-controller", "mesh-broker")
|
||||
if err != nil || strings.Join(got, ",") != "n1,n2" {
|
||||
t.Errorf("%v %v", got, err)
|
||||
}
|
||||
if _, err := holderNodes(map[string][]byte{}, "mesh-controller", "mesh-broker"); err == nil {
|
||||
t.Error("no holder named, and no error")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,4 +2,4 @@ module mesh-watcher
|
||||
|
||||
go 1.26.0
|
||||
|
||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029214732-6b8c6943811a
|
||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029220947-f9a496d3e121
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029214732-6b8c6943811a h1:elC2IjB+qbeXG+pWItLz71DenegbrdsqbcH5kyGo56Q=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029214732-6b8c6943811a/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029220947-f9a496d3e121 h1:OsA1v6n736a7RqEV8Nkip8pf5ILMbzj0PWuKqcBOhK0=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029220947-f9a496d3e121/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
"mesh-self-check.checked"
|
||||
],
|
||||
"reads": [
|
||||
"mesh-conditions.takeover"
|
||||
"mesh-conditions.takeover",
|
||||
"mesh-delivery-assigner.seat-holders"
|
||||
],
|
||||
"invokes": [
|
||||
"mesh-watcher.watcher_ping",
|
||||
"seat:mesh-controller.seats"
|
||||
"mesh-watcher.watcher_ping"
|
||||
],
|
||||
"own-secrets": {
|
||||
"telegram-token": "${dir:state}/telegram-token"
|
||||
@@ -43,7 +43,9 @@
|
||||
"type": "file",
|
||||
"path": "${dir:state}/settings.json",
|
||||
"mode": "0600",
|
||||
"takes-config": ["telegram-chat-id"],
|
||||
"takes-config": [
|
||||
"telegram-chat-id"
|
||||
],
|
||||
"merge": "json",
|
||||
"template": "files/settings.json"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user