A node's record rides in its own have key, <node>.node: no bucket, writer or grant of its own, and its key's change is what readers hear (issue 1126, ADR 0555 §3)
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
// - what a person decided of each node (its endpoint, site, hub, address on the private network, public domain and
|
||||
// accounts): the same placement key, Placement.Decided;
|
||||
// - what each node observes of itself (its tunnel key, capabilities, keys, certificate, links and networks): the
|
||||
// node-engine's state `records`; the tunnel found at a node's adoption and the names of the hub's peers are decided,
|
||||
// node's own have key, `<node>.node` (HaveNode.Record); the tunnel found at a node's adoption and the names of the hub's peers are decided,
|
||||
// in its placement, and whether the hub took the tunnel over and who enrolled under a peer's key are read from both;
|
||||
// - every module's manifest: the indexer's `modules.<module>` and its manifests by digest, or the digest the
|
||||
// composing node's want names where it runs another build (Scope.Kept);
|
||||
@@ -44,7 +44,8 @@ import (
|
||||
type Reader interface {
|
||||
// Placements is every node's placement, by node (delivery.PlacementBucket).
|
||||
Placements(ctx context.Context) (map[string]delivery.Placement, error)
|
||||
// Records is every node's record, by node (delivery.NodeRecordsBucket).
|
||||
// Records is every node's record, by node: HaveNode.Record of each node's own have key, `<node>.node` of
|
||||
// delivery.HaveBucket (ADR 0555 §3).
|
||||
Records(ctx context.Context) (map[string]delivery.NodeRecord, error)
|
||||
// Modules is every module the indexer holds, by module (delivery.ModulesBucket).
|
||||
Modules(ctx context.Context) (map[string]delivery.Module, error)
|
||||
|
||||
@@ -213,12 +213,15 @@ func parseModuleKeyPart(part string) (string, error) {
|
||||
return module, nil
|
||||
}
|
||||
|
||||
// HaveNodeWord is the last token of a node's own facts' key in HaveBucket.
|
||||
const HaveNodeWord = nodeWord
|
||||
|
||||
// HaveNodeKey is the key, in HaveBucket, of a node's own facts: `<node>.node`.
|
||||
func HaveNodeKey(node string) (string, error) {
|
||||
if err := checkName("node", node); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return node + ".node", nil
|
||||
return node + "." + HaveNodeWord, nil
|
||||
}
|
||||
|
||||
// ParseHaveKey is the inverse of HaveKey and HaveNodeKey: isNode is true for `<node>.node`, with module empty.
|
||||
@@ -381,6 +384,9 @@ type HaveNode struct {
|
||||
Strays json.RawMessage `json:"strays,omitempty"`
|
||||
Filters json.RawMessage `json:"filters,omitempty"`
|
||||
Profile json.RawMessage `json:"profile,omitempty"`
|
||||
// Record is what the machine observes of itself, typed, for every node and composing part to read (issue 1126,
|
||||
// ADR 0555 §3): the node's record lives in its own have key, so it needs no bucket, writer or grant of its own.
|
||||
Record *NodeRecord `json:"record,omitempty"`
|
||||
}
|
||||
|
||||
// Presence is whether a node or a part's instance is online; the entry's own time says since when. The watchman
|
||||
@@ -524,7 +530,8 @@ func SameHave(a, b Have) bool {
|
||||
// SameHaveNode says whether two node facts are the same value, comparing each carried JSON without its spacing.
|
||||
func SameHaveNode(a, b HaveNode) bool {
|
||||
return a.V == b.V && a.Composed == b.Composed && sameJSON(a.Windows, b.Windows) && sameJSON(a.Busy, b.Busy) &&
|
||||
sameJSON(a.Strays, b.Strays) && sameJSON(a.Filters, b.Filters) && sameJSON(a.Profile, b.Profile)
|
||||
sameJSON(a.Strays, b.Strays) && sameJSON(a.Filters, b.Filters) && sameJSON(a.Profile, b.Profile) &&
|
||||
(a.Record == nil) == (b.Record == nil) && (a.Record == nil || SameNodeRecord(*a.Record, *b.Record))
|
||||
}
|
||||
|
||||
// SamePresence says whether two presences are the same value.
|
||||
|
||||
@@ -3,47 +3,16 @@ package delivery
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
)
|
||||
|
||||
// A node's record is what the node knows of itself, written by its own agent into the node-engine's state `records`,
|
||||
// one key `record.<node>` per node, and read by every node and by the parts that compose (novox/hq ADR 0547 §2: "of
|
||||
// other nodes it reads only what is public: their records"; issue 1126's prepare, L1 as narrowed by ADR 0549). Where a
|
||||
// module runs, its host ports, pins, bindings and seat holdings are not in it: they are the catalogue's placement
|
||||
// (placement.go). Nothing in it is secret: its keys are public halves, its accounts are login names.
|
||||
//
|
||||
// The agent writes it at start and whenever a fact it holds changes, only when the value differs, and says
|
||||
// `node-record-changed`. Seeded from what the node runs and reports, never from the controller's store.
|
||||
const (
|
||||
// NodeRecordsState is the node-engine's state of node records, and NodeRecordsBucket its bucket.
|
||||
NodeRecordsState = "records"
|
||||
NodeRecordsBucket = "node-engine_" + NodeRecordsState
|
||||
// NodeRecordsHistory is the revisions the records bucket keeps per key.
|
||||
NodeRecordsHistory = 5
|
||||
// NodeRecordChangedEvent is what a node's agent says when it wrote its record.
|
||||
NodeRecordChangedEvent = "node-record-changed"
|
||||
|
||||
recordWord = "record"
|
||||
)
|
||||
|
||||
// NodeRecordKey is the key, in NodeRecordsBucket, of one node's record: `record.<node>`.
|
||||
func NodeRecordKey(node string) (string, error) {
|
||||
if err := checkName("node", node); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return recordWord + "." + node, nil
|
||||
}
|
||||
|
||||
// ParseNodeRecordKey is the inverse of NodeRecordKey; it refuses any other key, naming it.
|
||||
func ParseNodeRecordKey(key string) (node string, err error) {
|
||||
node, ok := strings.CutPrefix(key, recordWord+".")
|
||||
if !ok || checkName("node", node) != nil {
|
||||
return "", fmt.Errorf("delivery: %q is not a node record key, record.<node>", key)
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
// A node's record is what the machine observes of itself (issue 1126, ADR 0555 §3), written by its own agent in its
|
||||
// own have key, `<node>.node` of the node-engine's have bucket (HaveNode.Record), whenever an observed fact changes and
|
||||
// only when the value differs. The key's change is the event its readers hear: every node and the parts that compose
|
||||
// watch it. Where a module runs, and every fact a person decides of the node (its endpoint, site, hub, address on the
|
||||
// private network, public domain, accounts, the tunnel found at adoption), are the catalogue's placement
|
||||
// (placement.go). Nothing in it is secret: its keys are public halves.
|
||||
|
||||
// RecordTunnel is a tunnel found on a node when it was adopted, as compose.Tunnel holds it: public keys and addresses
|
||||
// only. After the node took it over the tunnel is no longer there to observe, so it is kept as what was decided at
|
||||
|
||||
@@ -13,22 +13,9 @@ import (
|
||||
// check (ADR 0201 §13) refuses such a field in a state every node reads.
|
||||
var secretNamed = regexp.MustCompile(`(?i)(secret|passw|token|api[-_]?key|private[-_]?key|credential|bearer|cookie|salt|signing)`)
|
||||
|
||||
// Issue 1126 (ADR 0555): a node's record keys, reads back, refuses a node that is not a name and an empty capability, and
|
||||
// Issue 1126 (ADR 0555): a node's record reads back, rides in its have key, refuses a node that is not a name and an empty capability, and
|
||||
// neither it nor the placement file names a field like a secret.
|
||||
func TestNodeRecord(t *testing.T) {
|
||||
key, err := NodeRecordKey("ace")
|
||||
if err != nil || key != "record.ace" || NodeRecordsBucket != "node-engine_records" {
|
||||
t.Fatalf("NodeRecordKey = %q, %v; bucket %q", key, err, NodeRecordsBucket)
|
||||
}
|
||||
if n, err := ParseNodeRecordKey(key); err != nil || n != "ace" {
|
||||
t.Fatalf("ParseNodeRecordKey = %q, %v", n, err)
|
||||
}
|
||||
for _, bad := range []string{"placement.ace", "record.", "record.a.b"} {
|
||||
if _, err := ParseNodeRecordKey(bad); err == nil {
|
||||
t.Errorf("ParseNodeRecordKey(%q) took it", bad)
|
||||
}
|
||||
}
|
||||
|
||||
hub := NodeRecord{Node: "novox", TunnelKey: "k", Capabilities: []string{"docker"}, SealingKey: "age1x", ServingKey: "pub",
|
||||
Certificate: "cert", OutwardLinks: []string{"eth0"}, Networks: []catalogue.AttachedNetwork{{Link: "eth0", Network: "192.0.2.0/24"}},
|
||||
NetworksSaid: true, Contract: 3}
|
||||
@@ -40,6 +27,18 @@ func TestNodeRecord(t *testing.T) {
|
||||
if err != nil || !SameNodeRecord(back, hub) || back.V != Version {
|
||||
t.Fatalf("round trip: %+v, %v", back, err)
|
||||
}
|
||||
// It rides in the node's own have key: a have that differs only in its record is another value.
|
||||
with, without := HaveNode{V: Version, Record: &hub}, HaveNode{V: Version}
|
||||
if raw, err := EncodeHaveNode(with); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if got, err := DecodeHaveNode(raw); err != nil || got.Record == nil || !SameHaveNode(got, with) || SameHaveNode(got, without) {
|
||||
t.Fatalf("the have's record: %+v, %v", got, err)
|
||||
}
|
||||
changed := hub
|
||||
changed.Contract = 4
|
||||
if SameHaveNode(with, HaveNode{V: Version, Record: &changed}) {
|
||||
t.Error("a changed record reads as the same have")
|
||||
}
|
||||
|
||||
for name, r := range map[string]NodeRecord{
|
||||
"empty capability": {Node: "ace", Capabilities: []string{""}},
|
||||
|
||||
Reference in New Issue
Block a user