Merge pull request 'The contract of the node-engine's data items, so disk-load reads what the engine composed (ADR 0577)' (#151) from feat/data-items into main
This commit was merged in pull request #151.
This commit is contained in:
@@ -99,7 +99,9 @@ func engineBeyond(user, g string) bool {
|
||||
// And its own health report (issue 774).
|
||||
strings.Contains(g, "KV_"+delivery.NodeHealthBucket) || g == "$KV."+delivery.NodeHealthBucket+"."+node ||
|
||||
// And the provisions of its node's modules, read (novox/hq ADR 0570 clause 6).
|
||||
strings.Contains(g, "KV_"+provisions.ProvisionsBucket)
|
||||
strings.Contains(g, "KV_"+provisions.ProvisionsBucket) ||
|
||||
// And its node's placed modules' data items, its own node's keys (novox/hq ADR 0577 clause 1).
|
||||
strings.Contains(g, "KV_"+EngineDataBucket) || strings.HasPrefix(g, "$KV."+EngineDataBucket+"."+node+".")
|
||||
}
|
||||
|
||||
// createsAnAccountsConsumer is the grants part's create and read of a durable consumer an account needs, by its stream
|
||||
@@ -657,6 +659,8 @@ func healthChecks(t *testing.T) {
|
||||
// ADR 0565 clauses 10, 11 (issue 1953 L5-S4): mesh-hand-acts reads the controller's log beside its own.
|
||||
{"mesh-hand-acts", ControllerHandActs, true},
|
||||
{"mesh-conditions", "mesh-healers.attempts", true}, {"mesh-conditions", HealersWould, false}, {"mesh-delivery-view", HealersWould, false},
|
||||
// ADR 0577 clause 1: the node-engine's data items are disk-load's alone to read.
|
||||
{"disk-load", delivery.EngineDataRead, true}, {"restic", delivery.EngineDataRead, false},
|
||||
} {
|
||||
granted, _ := ReadsAnnouncedBy(c.reader, []string{c.read}, announced)
|
||||
if (len(granted) == 1) != c.may {
|
||||
@@ -686,8 +690,9 @@ func healthChecks(t *testing.T) {
|
||||
Reads: []string{delivery.SeatHoldersRead}},
|
||||
{Module: delivery.AssignerModule, State: []Bucket{bucket(delivery.AssignerModule, delivery.SeatHoldersState)}},
|
||||
// ADR 0571 clause 3: disk-load keeps the readings; the backup holder reads them.
|
||||
{Module: "disk-load", State: []Bucket{bucket("disk-load", "readings")}},
|
||||
{Module: "restic", Reads: []string{"disk-load.readings"}, State: []Bucket{bucket("restic", "good")}},
|
||||
// ADR 0577 clause 1: and it reads its own node's keys of the node-engine's data items, which restic may not.
|
||||
{Module: "disk-load", State: []Bucket{bucket("disk-load", "readings")}, Reads: []string{delivery.EngineDataRead}},
|
||||
{Module: "restic", Reads: []string{"disk-load.readings", delivery.EngineDataRead}, State: []Bucket{bucket("restic", "good")}},
|
||||
// ADR 0571 clauses 1, 9: the indexer reads the builder's log through its own consumer; the catalogue module asks
|
||||
// the builder's tool.
|
||||
{Module: delivery.BuilderModule, Logs: []Log{{Module: delivery.BuilderModule, Name: delivery.BuilderLog, MaxAgeDays: 30}}},
|
||||
@@ -708,7 +713,8 @@ func healthChecks(t *testing.T) {
|
||||
"a.mesh-vault": {"$JS.API.DIRECT.GET.KV_mesh-provisioner_provisions.$KV.mesh-provisioner_provisions.a.keycloak.postgres-database",
|
||||
"$JS.API.DIRECT.GET.KV_mesh-provisioner_plans.$KV.mesh-provisioner_plans.a.keycloak"},
|
||||
"a.mesh-hand-acts": {"$KV.mesh-hand-acts_warrants.mesh-config.ask-1", "mesh.log.mesh-hand-acts.acts.act-1"},
|
||||
"a.disk-load": {"$KV.disk-load_readings.a.pg.data"},
|
||||
"a.disk-load": {"$KV.disk-load_readings.a.pg.data", "$JS.API.DIRECT.GET.KV_mesh-host_data.$KV.mesh-host_data.a.pg",
|
||||
"$JS.API.CONSUMER.CREATE.KV_mesh-host_data.c1.$KV.mesh-host_data.a.*"},
|
||||
"provisioner.a": {"$JS.API.DIRECT.GET.KV_mesh-provisioner_plans.$KV.mesh-provisioner_plans.a.keycloak",
|
||||
"$JS.API.CONSUMER.CREATE.KV_mesh-provisioner_plans.c1.$KV.mesh-provisioner_plans.a.keycloak",
|
||||
// Every machine's plans, as the client V3 watches them (issue 774, 2026-10-30).
|
||||
@@ -748,7 +754,8 @@ func healthChecks(t *testing.T) {
|
||||
"a.mesh-vault": {"$KV.mesh-provisioner_provisions.a.keycloak.postgres-database", "$KV.mesh-provisioner_plans.a.keycloak",
|
||||
"mesh.seat.mesh-artifact-sweep.event.artifacts-gone"},
|
||||
// Each writes its own state alone, and these users are composed.
|
||||
"a.disk-load": {"$KV.restic_good.a.pg.data"},
|
||||
"a.disk-load": {"$KV.restic_good.a.pg.data", "$KV.mesh-host_data.a.pg",
|
||||
"$JS.API.DIRECT.GET.KV_mesh-host_data.$KV.mesh-host_data.b.pg", "$JS.API.CONSUMER.CREATE.KV_mesh-host_data.c1.$KV.mesh-host_data.b.*"},
|
||||
"a.mesh-delivery-builder": {"$KV.disk-load_readings.a.pg.data"},
|
||||
// It reads every machine's plans (a provider's consumers are bound there, ADR 0373) but writes none, nor another
|
||||
// machine's needs, which it keeps for the client before V3 until the lease write.
|
||||
@@ -756,7 +763,7 @@ func healthChecks(t *testing.T) {
|
||||
// Only the assigner writes seat-holders.
|
||||
"a.artifact-store-tools": {"$KV.mesh-delivery-assigner_seat-holders.mesh-broker"},
|
||||
// Only disk-load writes its readings.
|
||||
"a.restic": {"$KV.disk-load_readings.a.pg.data"},
|
||||
"a.restic": {"$KV.disk-load_readings.a.pg.data", "$JS.API.DIRECT.GET.KV_mesh-host_data.$KV.mesh-host_data.a.pg"},
|
||||
"a.postgres": {"$KV.mesh-provisioner_provisions.a.keycloak.postgres-database", "$KV.mesh-provisioner_plans.a.keycloak"},
|
||||
// Nor mesh-hand-acts' log and warrants, which it alone writes (ADR 0565 clause 10).
|
||||
"a.mesh-healers": {"$KV.mesh-conditions_conditions.x", "$KV.mesh-conditions_takeover.switch",
|
||||
|
||||
@@ -28,6 +28,10 @@ var EngineActsBucket = BucketName(EngineModule, EngineActsState)
|
||||
// node's name, written by that node's engine alone.
|
||||
var EnginePackagesBucket = BucketName(EngineModule, delivery.EnginePackagesState)
|
||||
|
||||
// EngineDataBucket is the engine's state of its node's placed modules' data items (novox/hq ADR 0577 clause 1): one
|
||||
// key `<node>.<module>` per module, written by that node's engine alone.
|
||||
var EngineDataBucket = delivery.EngineDataBucket
|
||||
|
||||
// EnginePackagesGrants is what a node's engine publishes to keep its node's key of the packages bucket: the key alone,
|
||||
// and its read.
|
||||
func EnginePackagesGrants(node string) []string {
|
||||
@@ -66,6 +70,10 @@ func engineGrants(p Principal) (pub, sub []string) {
|
||||
pub = append(pub, append([]string{"$KV." + delivery.NodeHealthBucket + "." + p.Node},
|
||||
readGrants(delivery.NodeHealthBucket, p.Node)...)...)
|
||||
}
|
||||
// And its node's placed modules' data items, its own node's keys alone (ADR 0577 clause 1).
|
||||
if own, err := NodeKeysGrants(EngineDataBucket, p.Node); err == nil {
|
||||
pub = append(pub, own...)
|
||||
}
|
||||
// And its node's declared packages, its own key alone (ADR 0565 clause 6).
|
||||
if safeSubject.MatchString(p.Node) {
|
||||
pub = append(pub, EnginePackagesGrants(p.Node)...)
|
||||
|
||||
@@ -156,6 +156,12 @@ func stateGrants(a stateAccess) []string {
|
||||
}
|
||||
for _, r := range a.Reads {
|
||||
if bucket, ok := bucketOfRead(r); ok {
|
||||
if NodeKeyedReads[r] {
|
||||
if machineKey != "" {
|
||||
read(bucket, machineKey+".>")
|
||||
}
|
||||
continue
|
||||
}
|
||||
read(bucket, "")
|
||||
}
|
||||
}
|
||||
@@ -167,6 +173,11 @@ func stateGrants(a stateAccess) []string {
|
||||
return out
|
||||
}
|
||||
|
||||
// NodeKeyedReads are the states keyed `<node>.>` whose reader reads its own machine's keys alone, whatever its `reads`
|
||||
// names: the node-engine's data items, each node's disk-load reading its own node's (novox/hq ADR 0577 clause 1). A
|
||||
// machine with no usable name reaches none of them.
|
||||
var NodeKeyedReads = map[string]bool{delivery.EngineDataRead: true}
|
||||
|
||||
// StreamCreatedAdvisory is the subject the bus says a stream was created on (the server's own advisory), here for a
|
||||
// bucket's stream KV_<bucket>.
|
||||
func StreamCreatedAdvisory(bucket string) string {
|
||||
|
||||
@@ -101,7 +101,10 @@ var NamedReaders = map[string][]string{VaultBusAccounts: {GrantsModule}, GrantsA
|
||||
ArtifactStoreTools,
|
||||
// ADR 0561 §3: mesh-config takes over the config only once the lease is handed away (its takeover.go leaseAway).
|
||||
ConfigModule},
|
||||
ControllerConditions: {health.KeeperModule}, ControllerHandActs: {health.HealersModule, handacts.Module}, HealersWould: {}}
|
||||
ControllerConditions: {health.KeeperModule}, ControllerHandActs: {health.HealersModule, handacts.Module}, HealersWould: {},
|
||||
// ADR 0577 clause 1: the node-engine's data items are read by disk-load alone, each node's its own node's keys
|
||||
// (NodeKeyedReads); the engine announces nothing of its own, so its readers are named here.
|
||||
delivery.EngineDataRead: {health.DiskLoadModule}}
|
||||
|
||||
// ReaderMay says whether module may read the state read (`<owner>.<name>`) by the named readers: always, for a state
|
||||
// that names none.
|
||||
|
||||
@@ -259,6 +259,10 @@ var WritersTable = []WriterRow{
|
||||
{State: "a machine's declared packages", Writer: "the node-engine of the machine, its key `<node>` alone",
|
||||
KeptIn: "key-value " + EnginePackagesBucket, Others: "read by pacman through `reads`",
|
||||
Subjects: kvOf(EnginePackagesBucket), Writes: ownNodeKeys},
|
||||
// A node's placed modules' data items (novox/hq ADR 0577 clause 1): its engine's own keys, read by that node's disk-load.
|
||||
{State: "a machine's data items", Writer: "the node-engine of the machine, under its own keys `<node>.>`",
|
||||
KeptIn: "key-value " + EngineDataBucket, Others: "read by the node's disk-load through `reads`, its own node's keys alone",
|
||||
Subjects: kvOf(EngineDataBucket), Writes: ownNodeKeys},
|
||||
{State: "a machine's hand acts", Writer: "the node-engine of the machine, under its own keys `<node>.>`, compare-and-set",
|
||||
KeptIn: "key-value " + EngineActsBucket + ", the node-engine's declared state acts",
|
||||
Others: "read through the node seat's verb acts", Subjects: kvOf(EngineActsBucket),
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package delivery
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
)
|
||||
|
||||
// **The data items of each module placed on a node, as its engine composed them** (novox/hq ADR 0577 clauses 1 and 4):
|
||||
// the node-engine's state `data` (EngineDataBucket), one key `<node>.<module>` (EngineDataKey) per module placed on the
|
||||
// node that declares data items, written by that node's engine alone, when the module's composition changes and only
|
||||
// when the value differs, and deleted when the module leaves the node. That node's disk-load watches its node's keys
|
||||
// (EngineDataNodeKeys) and measures what they name; it composes nothing itself. Only the contract is here: the state's
|
||||
// names, its key and its value; the engine composes the items and disk-load measures them.
|
||||
|
||||
// EngineDataState, EngineDataBucket and EngineDataRead are the node-engine's state data, by its local name, as the bus
|
||||
// names it and as a reader declares it.
|
||||
const (
|
||||
EngineDataState = "data"
|
||||
EngineDataBucket = "mesh-host_" + EngineDataState
|
||||
EngineDataRead = "mesh-host." + EngineDataState
|
||||
)
|
||||
|
||||
// EngineDataVersion is the version of EngineData's layout.
|
||||
const EngineDataVersion = 1
|
||||
|
||||
// EngineData is one key's value: the module's data items on the node as the engine composed them (each item's id, its
|
||||
// path resolved on that node, its measure, class, protection and what covers it), and the digest of the module's
|
||||
// composition they came from (ComposedDigestOf).
|
||||
type EngineData struct {
|
||||
V int `json:"v"`
|
||||
Node string `json:"node"`
|
||||
Module string `json:"module"`
|
||||
Digest string `json:"digest"`
|
||||
Items []catalogue.PlacedDataItem `json:"items"`
|
||||
}
|
||||
|
||||
// EngineDataKey is a module's key on a node, `<node>.<module>`, its module part escaped as a have's is (HaveKey).
|
||||
func EngineDataKey(node, module string) (string, error) { return HaveKey(node, module) }
|
||||
|
||||
// EngineDataNodeKeys is the pattern of every key of a node, `<node>.*`: what that node's disk-load watches.
|
||||
func EngineDataNodeKeys(node string) (string, error) {
|
||||
if err := checkName("node", node); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return node + ".*", nil
|
||||
}
|
||||
|
||||
// EncodeEngineData writes a value at this layout's version.
|
||||
func EncodeEngineData(d EngineData) ([]byte, error) {
|
||||
d.V = EngineDataVersion
|
||||
if _, err := EngineDataKey(d.Node, d.Module); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d.Items == nil {
|
||||
d.Items = []catalogue.PlacedDataItem{}
|
||||
}
|
||||
return json.Marshal(d)
|
||||
}
|
||||
|
||||
// DecodeEngineData reads a value; it refuses another version.
|
||||
func DecodeEngineData(raw []byte) (EngineData, error) {
|
||||
var d EngineData
|
||||
if err := json.Unmarshal(raw, &d); err != nil {
|
||||
return EngineData{}, fmt.Errorf("delivery: a module's data items are not readable: %w", err)
|
||||
}
|
||||
if d.V != EngineDataVersion {
|
||||
return EngineData{}, fmt.Errorf("delivery: a module's data items of version %d, and this reads %d", d.V, EngineDataVersion)
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
Reference in New Issue
Block a user