Say the heartbeat's interval, export the host's validator, grant the genesis controller Phase 1 (hq to-be 45)
The controller's watchdog of a machine's heartbeat (S1) is bound to three of its intervals, and a bound the controller guessed would not move when the interval does: the heartbeat now carries interval_seconds. Its self-check (D1) must judge every composed declaration as the host does, and a second validator written from the host's rules would drift from them: the host's own parsing is exported, unchanged, as github.com/novox/mesh-host/validate. The installer's first user list grants what the controller now composes for itself: its condition buckets, the condition and doctor-heartbeat events, the bus's two consumer advisories and $SRV.INFO — or the first controller would be refused them until the broker's machine is pushed.
This commit is contained in:
@@ -161,7 +161,7 @@
|
|||||||
"type": "file",
|
"type": "file",
|
||||||
"path": "/var/lib/mesh-bus-conf/accounts.conf",
|
"path": "/var/lib/mesh-bus-conf/accounts.conf",
|
||||||
"mode": "0600",
|
"mode": "0600",
|
||||||
"content": "// The first user list, carried by the installer because at genesis there is no mesh to\n// compose one. A bootstrap credential, rotated with the store's and replaced by the\n// controller's own composition from its first start onward.\naccounts {\n MESH {\n jetstream: enabled\n users = [\n { user: \"controller\", password: \"$2a$10$AHqJgOifIVbU41KmATiMhuXFs8xa7Wl2HuN4UVBCXdN2jIQzjqApy\", permissions: {\n publish: { allow: [\"$JS.ACK.CONTROL.controller.>\", \"$JS.ACK.EVENTS.controller.>\", \"$JS.API.>\", \"$KV.mesh-controller_calls.>\", \"$KV.mesh-controller_hand-acts.>\", \"$KV.SEAT_MESH_BUILD_MACHINE_cancelled.>\", \"$KV.SEAT_NODE_BUILD_AGENT_cancelled.>\", \"_INBOX.enrol.>\", \"mesh.assignment.>\", \"mesh.control.>\", \"mesh.mod.*.tool.>\", \"mesh.node.>\", \"mesh.seat.mesh-build-machine.accept.>\", \"mesh.seat.node-build-agent.accept.>\", \"mesh.seat.mesh-build-machine.tool.>\", \"mesh.seat.node-build-agent.tool.>\", \"mesh.seat.mesh-controller.event.applied\", \"mesh.seat.mesh-controller.event.built-before\", \"mesh.seat.mesh-controller.event.refused\"] }\n subscribe: { allow: [\"$JS.API.>\", \"_DELIVER.controller\", \"_DELIVER.controller.>\", \"_INBOX.controller.>\", \"mesh.control.>\", \"mesh.mod.*.event.provisioner.failing\", \"mesh.mod.*.event.provisioner.recovered\", \"mesh.mod.gitea.event.pull.merged\", \"mesh.mod.mesh-catalog.event.catching-up\", \"mesh.mod.mesh-catalog.event.upgraded\", \"mesh.seat.mesh-build-machine.event.built\", \"mesh.seat.node-build-agent.event.built\", \"mesh.seat.mesh-controller.tool.>\", \"$SRV.PING\", \"$SRV.INFO\", \"$SRV.PING.mesh-controller\", \"$SRV.PING.mesh-controller.>\", \"$SRV.INFO.mesh-controller\", \"$SRV.INFO.mesh-controller.>\", \"$SRV.STATS\", \"$SRV.STATS.mesh-controller\", \"$SRV.STATS.mesh-controller.>\"] }\n allow_responses: { max: 1, ttl: \"1m\" }\n } }\n ]\n }\n}\n"
|
"content": "// The first user list, carried by the installer because at genesis there is no mesh to\n// compose one. A bootstrap credential, rotated with the store's and replaced by the\n// controller's own composition from its first start onward.\naccounts {\n MESH {\n jetstream: enabled\n users = [\n { user: \"controller\", password: \"$2a$10$AHqJgOifIVbU41KmATiMhuXFs8xa7Wl2HuN4UVBCXdN2jIQzjqApy\", permissions: {\n publish: { allow: [\"$JS.ACK.CONTROL.controller.>\", \"$JS.ACK.EVENTS.controller.>\", \"$JS.API.>\", \"$KV.mesh-controller_calls.>\", \"$KV.mesh-controller_hand-acts.>\", \"$KV.mesh-controller_conditions.>\", \"$KV.mesh-controller_condition-history.>\", \"$KV.SEAT_MESH_BUILD_MACHINE_cancelled.>\", \"$KV.SEAT_NODE_BUILD_AGENT_cancelled.>\", \"_INBOX.enrol.>\", \"mesh.assignment.>\", \"mesh.control.>\", \"mesh.mod.*.tool.>\", \"mesh.node.>\", \"mesh.seat.mesh-build-machine.accept.>\", \"mesh.seat.node-build-agent.accept.>\", \"mesh.seat.mesh-build-machine.tool.>\", \"mesh.seat.node-build-agent.tool.>\", \"mesh.seat.mesh-controller.event.applied\", \"mesh.seat.mesh-controller.event.built-before\", \"mesh.seat.mesh-controller.event.refused\", \"mesh.seat.mesh-controller.event.condition-raised\", \"mesh.seat.mesh-controller.event.condition-changed\", \"mesh.seat.mesh-controller.event.condition-cleared\", \"mesh.seat.mesh-controller.event.doctor-heartbeat\", \"$SRV.INFO\"] }\n subscribe: { allow: [\"$JS.API.>\", \"$JS.EVENT.ADVISORY.CONSUMER.MAX_DELIVERIES.>\", \"$JS.EVENT.ADVISORY.CONSUMER.DELETED.>\", \"_DELIVER.controller\", \"_DELIVER.controller.>\", \"_INBOX.controller.>\", \"mesh.control.>\", \"mesh.mod.*.event.provisioner.failing\", \"mesh.mod.*.event.provisioner.recovered\", \"mesh.mod.gitea.event.pull.merged\", \"mesh.mod.mesh-catalog.event.catching-up\", \"mesh.mod.mesh-catalog.event.upgraded\", \"mesh.seat.mesh-build-machine.event.built\", \"mesh.seat.node-build-agent.event.built\", \"mesh.seat.mesh-controller.tool.>\", \"$SRV.PING\", \"$SRV.INFO\", \"$SRV.PING.mesh-controller\", \"$SRV.PING.mesh-controller.>\", \"$SRV.INFO.mesh-controller\", \"$SRV.INFO.mesh-controller.>\", \"$SRV.STATS\", \"$SRV.STATS.mesh-controller\", \"$SRV.STATS.mesh-controller.>\"] }\n allow_responses: { max: 1, ttl: \"1m\" }\n } }\n ]\n }\n}\n"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "broker",
|
"id": "broker",
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ const (
|
|||||||
// look like a stale one.
|
// look like a stale one.
|
||||||
type Alive struct {
|
type Alive struct {
|
||||||
Node string `json:"node"`
|
Node string `json:"node"`
|
||||||
|
// IntervalSeconds is how often this node says it is there (novox/hq to-be 45 §3, S1): the
|
||||||
|
// controller's watchdog is bound to three of them, so the bound moves with the interval rather
|
||||||
|
// than with a number the controller guessed. A controller older than that ignores it.
|
||||||
|
IntervalSeconds int `json:"interval_seconds"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Signed is a declaration and the signature over it.
|
// Signed is a declaration and the signature over it.
|
||||||
|
|||||||
@@ -514,7 +514,7 @@ func publishReport(ctx context.Context, bus Bus, m Membership, report Report,
|
|||||||
// publishAlive says this node is here, and nothing else.
|
// publishAlive says this node is here, and nothing else.
|
||||||
func publishAlive(ctx context.Context, bus Bus, m Membership, say Announce,
|
func publishAlive(ctx context.Context, bus Bus, m Membership, say Announce,
|
||||||
timeout time.Duration) {
|
timeout time.Duration) {
|
||||||
body, err := json.Marshal(Alive{Node: m.Node})
|
body, err := json.Marshal(Alive{Node: m.Node, IntervalSeconds: int(AliveEvery / time.Second)})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
// Package validate is the node-engine's own judgement of a declaration, for whoever must know before
|
||||||
|
// a declaration is sent whether a machine would take it (novox/hq to-be 45 §4, D1; §9, the merge gate).
|
||||||
|
//
|
||||||
|
// **One validator.** The controller composed declarations the host then refused whole — a manifest
|
||||||
|
// the catalogue check passed (novox/hq issue 236), a consumer's identity too long for the machine's
|
||||||
|
// names (issue 263) — because the only validator was the one the host runs as it applies. A second,
|
||||||
|
// written in the controller from the host's rules, would drift from them the first time either
|
||||||
|
// changed. So the host's own parsing is exported here, unchanged: what the controller's self-check and
|
||||||
|
// the merge gate run is what every machine runs.
|
||||||
|
//
|
||||||
|
// It judges a declaration as it arrives over the link: actions are refused, as the host refuses them
|
||||||
|
// from the link (novox/hq ADR 0005). Nothing here touches a machine.
|
||||||
|
package validate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/novox/mesh-host/internal/declaration"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Declaration is every problem the node-engine would refuse a declaration for, each in its own words;
|
||||||
|
// nil when it would take it. The body is the declaration as the controller composes it — the bytes a
|
||||||
|
// signature is made over — not the signed envelope.
|
||||||
|
func Declaration(raw []byte) []string {
|
||||||
|
_, err := declaration.Parse(raw)
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var refused *declaration.RefusalError
|
||||||
|
if errors.As(err, &refused) {
|
||||||
|
return refused.Problems
|
||||||
|
}
|
||||||
|
return []string{err.Error()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version is the declaration vocabulary this validator speaks: a declaration of another version is
|
||||||
|
// refused whole by Declaration, as by the host.
|
||||||
|
const Version = declaration.Version
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package validate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// **The validator a controller imports is the host's**: a declaration the host takes passes, and one
|
||||||
|
// it refuses is refused with the host's own words, every problem at once.
|
||||||
|
func TestTheValidatorIsTheHosts(t *testing.T) {
|
||||||
|
good := `{"declaration": 1, "resources": [{"id": "d", "type": "directory", "path": "/var/lib/x", "mode": "0755"}]}`
|
||||||
|
if problems := Declaration([]byte(good)); problems != nil {
|
||||||
|
t.Fatalf("a declaration the host takes was refused: %v", problems)
|
||||||
|
}
|
||||||
|
for _, bad := range []string{
|
||||||
|
`{"declaration": 1, "resources": []}`,
|
||||||
|
`{"declaration": 99, "resources": [{"id": "d", "type": "directory", "path": "/x"}]}`,
|
||||||
|
`{"declaration": 1, "resources": [{"id": "d", "type": "nothing-the-host-knows"}]}`,
|
||||||
|
`not json`,
|
||||||
|
} {
|
||||||
|
problems := Declaration([]byte(bad))
|
||||||
|
if len(problems) == 0 {
|
||||||
|
t.Errorf("a declaration the host refuses was passed: %s", bad)
|
||||||
|
}
|
||||||
|
for _, p := range problems {
|
||||||
|
if strings.TrimSpace(p) == "" {
|
||||||
|
t.Errorf("a problem said nothing, for %s", bad)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user