From 73eb3bd11cef09161545a755094762ba9e2557d5 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 5 Nov 2026 16:44:52 +0100 Subject: [PATCH] mesh-self-check reads a node's presence through the watchdogs client: a part's key is no node, a refused version is an error (ADR 0634 section 4; issue 1966) Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i --- .../cmd/mesh-self-check/probes_mesh.go | 16 +++++++---- .../cmd/mesh-self-check/probes_test.go | 27 ++++++++++++++++--- modules/mesh-self-check/go.mod | 2 +- 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/modules/mesh-self-check/cmd/mesh-self-check/probes_mesh.go b/modules/mesh-self-check/cmd/mesh-self-check/probes_mesh.go index ed2eeb2f7..e2fe114ce 100644 --- a/modules/mesh-self-check/cmd/mesh-self-check/probes_mesh.go +++ b/modules/mesh-self-check/cmd/mesh-self-check/probes_mesh.go @@ -10,7 +10,7 @@ import ( "context" "encoding/json" "fmt" - watchdogs "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshwatchdogs" + watchdogsclient "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshwatchdogsclient" assignerclient "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient" placing "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" "net" @@ -65,16 +65,22 @@ func (c *Checker) heard() (map[string]bool, error) { } out := map[string]bool{} for _, k := range keys { - node, ok := watchdogs.ParsePresenceByNodeKey(k) - if !ok || node == "" { + node, _, _, err := watchdogsclient.ParsePresenceKey(k) + if err != nil || node == "" { // a part's key is no node's, and a key of no form is not read continue } raw, ok, err := c.Store.Get(PresenceRead, k) if err != nil { return nil, fmt.Errorf("the presence of %s cannot be read: %w", node, err) } - var p watchdogs.PresenceByNodeValue - if ok && json.Unmarshal(raw, &p) == nil && p.Online { + if !ok { + continue + } + p, err := watchdogsclient.DecodeNodePresence(raw) + if err != nil { + return nil, fmt.Errorf("the presence of %s is not read: %w", node, err) + } + if p.Online { out[node] = true } } diff --git a/modules/mesh-self-check/cmd/mesh-self-check/probes_test.go b/modules/mesh-self-check/cmd/mesh-self-check/probes_test.go index 5e569af83..ee1348e7d 100644 --- a/modules/mesh-self-check/cmd/mesh-self-check/probes_test.go +++ b/modules/mesh-self-check/cmd/mesh-self-check/probes_test.go @@ -6,7 +6,7 @@ import ( "errors" engineclient "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" nodeseat "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" - watchdogs "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshwatchdogs" + watchdogsclient "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshwatchdogsclient" assignerclient "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient" placing "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" catalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" @@ -269,7 +269,7 @@ func machines() *memStore { pl.Modules = []placing.PlacedModule{{Module: "sshd"}} // a module key beside the node key } place(s, pl) - _ = s.Put(PresenceRead, p.node, watchdogs.PresenceByNodeValue{V: 1, Online: p.online}) + _ = s.Put(PresenceRead, p.node, watchdogsclient.NodePresence{V: 1, Online: p.online}) key, _ := assignerclient.SeatHoldersKey(BannedSeat, p.node) _ = s.Put(SeatHoldersRead, key, placing.SeatHolders{V: 1, Seat: BannedSeat, Scope: "node", Node: p.node, Holders: []placing.SeatHoldersHoldersItem{{Module: "fail2ban", Node: p.node}}}) @@ -438,7 +438,7 @@ func (r *dataRig) holder(node string) { key, _ := assignerclient.SeatHoldersKey("node-backup", node) _ = r.s.Put(SeatHoldersRead, key, placing.SeatHolders{V: 1, Seat: "node-backup", Scope: "node", Node: node, Holders: []placing.SeatHoldersHoldersItem{{Module: "restic", Node: node}}}) - _ = r.s.Put(PresenceRead, node, watchdogs.PresenceByNodeValue{V: 1, Online: true}) + _ = r.s.Put(PresenceRead, node, watchdogsclient.NodePresence{V: 1, Online: true}) } // dataMesh builds the mesh: pg declares data (backed up within 48 h, written all the time within 24 h) and dumps; plex @@ -485,3 +485,24 @@ func dataMesh(change func(*dataRig)) *memStore { } return r.s } + +// TestPresenceIsReadThroughTheWatchdogsClient holds the node's presence to the client's codec (ADR 0634 section 4): a +// part's key (`part..`) is no node, and a value of a version the client refuses is a refusal, not a +// node that is offline. +func TestPresenceIsReadThroughTheWatchdogsClient(t *testing.T) { + s := newStore() + _ = s.Put(PresenceRead, "ace", watchdogsclient.NodePresence{V: 1, Online: true}) + _ = s.Put(PresenceRead, "part.watchman.1", watchdogsclient.PartPresence{V: 1, Online: true}) + c := &Checker{Store: s} + heard, err := c.heard() + if err != nil { + t.Fatal(err) + } + if len(heard) != 1 || !heard["ace"] { + t.Errorf("heard %v, want only ace: a part's presence is no node's", heard) + } + _ = s.Put(PresenceRead, "g14", watchdogsclient.NodePresence{V: 99, Online: true}) + if _, err := c.heard(); err == nil { + t.Error("a presence of a version the client refuses was read as a node's") + } +} diff --git a/modules/mesh-self-check/go.mod b/modules/mesh-self-check/go.mod index c1d1d7419..3a8d9b4f4 100644 --- a/modules/mesh-self-check/go.mod +++ b/modules/mesh-self-check/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000 git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog v0.0.0-00010101000000-000000000000 - git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshwatchdogs v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshwatchdogsclient v0.0.0-00010101000000-000000000000 git.novox.be/novox/mesh-contracts/go/seats/nodeengine v0.0.0-00010101000000-000000000000 git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient v0.0.0-00010101000000-000000000000 git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000