The assigner's state and verb placement are assignments, with its events, tools, views and every caller; the assigner copies the retired state at its start (issue 1966)
Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
@@ -5,7 +5,7 @@
|
|||||||
// view may answer them: the hold of a fingerprint whose health check failed (`hold.<module>.<fingerprint hex>`: release it or
|
// view may answer them: the hold of a fingerprint whose health check failed (`hold.<module>.<fingerprint hex>`: release it or
|
||||||
// keep it held), the restore of a data module set back (`restore.<node>.<module>.<fingerprint hex>`: restore and lose newer
|
// keep it held), the restore of a data module set back (`restore.<node>.<module>.<fingerprint hex>`: restore and lose newer
|
||||||
// writes, or leave it stopped, ADR 0429 §6), a backup that failed before an apply, a move a person must allow for a module of
|
// writes, or leave it stopped, ADR 0429 §6), a backup that failed before an apply, a move a person must allow for a module of
|
||||||
// policy record, and the authorising ask of a proposal of placement.
|
// policy record, and the authorising ask of a proposal of assignments.
|
||||||
package asks
|
package asks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -14,8 +14,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
|
||||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||||
"git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||||
@@ -34,15 +34,15 @@ const (
|
|||||||
// AskKindRecord is the kind of the assigner's ask about a move a person must allow, for a module of policy record: a
|
// AskKindRecord is the kind of the assigner's ask about a move a person must allow, for a module of policy record: a
|
||||||
// composition judged to recreate a container or move data, or one that cannot be judged (ADR 0539 clause 14).
|
// composition judged to recreate a container or move data, or one that cannot be judged (ADR 0539 clause 14).
|
||||||
AskKindRecord = "record"
|
AskKindRecord = "record"
|
||||||
// AskKindPlacement is the kind of the authorising ask about a proposal of placement (novox/hq ADR 0567 §5):
|
// AskKindAssignments is the kind of the authorising ask about a proposal of assignments (novox/hq ADR 0567 §5):
|
||||||
// `placement.<proposal id>`. The kind needs no proof, as the bus's put does not: its yes is an Approve, which only a
|
// `assignments.<proposal id>`. The kind needs no proof, as the bus's put does not: its yes is an Approve, which only a
|
||||||
// channel proving who answered takes and the view never does (ViewAnswerFits).
|
// channel proving who answered takes and the view never does (ViewAnswerFits).
|
||||||
AskKindPlacement = "placement"
|
AskKindAssignments = "assignments"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AskKey is an ask's key read whole: its Kind, and the parts its kind has: a merge's Group and heads Hash, a hold's Module
|
// AskKey is an ask's key read whole: its Kind, and the parts its kind has: a merge's Group and heads Hash, a hold's Module
|
||||||
// and fingerprint FP, a restore's and a backup's Node, Module and FP, a record's Node, Module and the digest Composed of the
|
// and fingerprint FP, a restore's and a backup's Node, Module and FP, a record's Node, Module and the digest Composed of the
|
||||||
// composition it would allow, a placement's Proposal.
|
// composition it would allow, the assignments' Proposal.
|
||||||
type AskKey struct {
|
type AskKey struct {
|
||||||
Proposal string
|
Proposal string
|
||||||
Kind string
|
Kind string
|
||||||
@@ -60,11 +60,11 @@ func AskNeedsProof(kind string) (bool, error) {
|
|||||||
switch kind {
|
switch kind {
|
||||||
case meshdeliveryclient.AskKindMerge:
|
case meshdeliveryclient.AskKindMerge:
|
||||||
return true, nil
|
return true, nil
|
||||||
case AskKindHold, AskKindRestore, AskKindBackup, AskKindRecord, AskKindPlacement:
|
case AskKindHold, AskKindRestore, AskKindBackup, AskKindRecord, AskKindAssignments:
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
return false, fmt.Errorf("asks: %q is not a kind of ask, %s, %s, %s, %s, %s or %s", kind, meshdeliveryclient.AskKindMerge, AskKindHold,
|
return false, fmt.Errorf("asks: %q is not a kind of ask, %s, %s, %s, %s, %s or %s", kind, meshdeliveryclient.AskKindMerge, AskKindHold,
|
||||||
AskKindRestore, AskKindBackup, AskKindRecord, AskKindPlacement)
|
AskKindRestore, AskKindBackup, AskKindRecord, AskKindAssignments)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HoldAskKey is the key of the ask about a module's held fingerprint: `hold.<module>.<hex>`.
|
// HoldAskKey is the key of the ask about a module's held fingerprint: `hold.<module>.<hex>`.
|
||||||
@@ -103,12 +103,12 @@ func BackupAskKey(node, module, fingerprint string) (string, error) {
|
|||||||
return AskKindBackup + "." + key, nil
|
return AskKindBackup + "." + key, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PlacementAskKey is the key of the authorising ask about a proposal of placement: `placement.<proposal id>` (ADR 0567 §5).
|
// AssignmentsAskKey is the key of the authorising ask about a proposal of assignments: `assignments.<proposal id>` (ADR 0567 §5).
|
||||||
func PlacementAskKey(proposal string) (string, error) {
|
func AssignmentsAskKey(proposal string) (string, error) {
|
||||||
if err := meshmoduleassignerclient.CheckProposalID(proposal); err != nil {
|
if err := meshmoduleassignerclient.CheckProposalID(proposal); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return AskKindPlacement + "." + proposal, nil
|
return AskKindAssignments + "." + proposal, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RecordAskKey is the key of the ask about a move of a module on a node to the composition whose digest it names:
|
// RecordAskKey is the key of the ask about a move of a module on a node to the composition whose digest it names:
|
||||||
@@ -124,7 +124,7 @@ func RecordAskKey(node, module, composed string) (string, error) {
|
|||||||
return AskKindRecord + "." + have + "." + strings.TrimPrefix(composed, "sha256:"), nil
|
return AskKindRecord + "." + have + "." + strings.TrimPrefix(composed, "sha256:"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseAnyAskKey reads an ask key of any kind (a merge, hold, restore, backup, record or placement ask); it refuses an unknown
|
// ParseAnyAskKey reads an ask key of any kind (a merge, hold, restore, backup, record or assignments ask); it refuses an unknown
|
||||||
// kind and a key whose parts do not fit its kind, naming the key.
|
// kind and a key whose parts do not fit its kind, naming the key.
|
||||||
func ParseAnyAskKey(key string) (AskKey, error) {
|
func ParseAnyAskKey(key string) (AskKey, error) {
|
||||||
kind, rest, _ := strings.Cut(key, ".")
|
kind, rest, _ := strings.Cut(key, ".")
|
||||||
@@ -167,7 +167,7 @@ func ParseAnyAskKey(key string) (AskKey, error) {
|
|||||||
return refused(err)
|
return refused(err)
|
||||||
}
|
}
|
||||||
return AskKey{Kind: kind, Node: node, Module: module, FP: fp}, nil
|
return AskKey{Kind: kind, Node: node, Module: module, FP: fp}, nil
|
||||||
case AskKindPlacement:
|
case AskKindAssignments:
|
||||||
if err := meshmoduleassignerclient.CheckProposalID(rest); err != nil {
|
if err := meshmoduleassignerclient.CheckProposalID(rest); err != nil {
|
||||||
return refused(err)
|
return refused(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,17 +17,17 @@ func TestTheAssignersAskKeysKeepTheirFormsAndTheValueItsRules(t *testing.T) {
|
|||||||
hold, _ := HoldAskKey("gitea", fp)
|
hold, _ := HoldAskKey("gitea", fp)
|
||||||
restore, _ := RestoreAskKey("n1", "gitea", fp)
|
restore, _ := RestoreAskKey("n1", "gitea", fp)
|
||||||
backup, _ := BackupAskKey("n1", "novox.be", fp)
|
backup, _ := BackupAskKey("n1", "novox.be", fp)
|
||||||
placement, _ := PlacementAskKey("p-20261028t183000-1a2b3c")
|
assignments, _ := AssignmentsAskKey("p-20261028t183000-1a2b3c")
|
||||||
record, _ := RecordAskKey("n1", "gitea", digest)
|
record, _ := RecordAskKey("n1", "gitea", digest)
|
||||||
for key, want := range map[string]struct {
|
for key, want := range map[string]struct {
|
||||||
got string
|
got string
|
||||||
back AskKey
|
back AskKey
|
||||||
}{
|
}{
|
||||||
"hold": {hold, AskKey{Kind: "hold", Module: "gitea", FP: fp}},
|
"hold": {hold, AskKey{Kind: "hold", Module: "gitea", FP: fp}},
|
||||||
"restore": {restore, AskKey{Kind: "restore", Node: "n1", Module: "gitea", FP: fp}},
|
"restore": {restore, AskKey{Kind: "restore", Node: "n1", Module: "gitea", FP: fp}},
|
||||||
"backup": {backup, AskKey{Kind: "backup", Node: "n1", Module: "novox.be", FP: fp}},
|
"backup": {backup, AskKey{Kind: "backup", Node: "n1", Module: "novox.be", FP: fp}},
|
||||||
"placement": {placement, AskKey{Kind: "placement", Proposal: "p-20261028t183000-1a2b3c"}},
|
"assignments": {assignments, AskKey{Kind: "assignments", Proposal: "p-20261028t183000-1a2b3c"}},
|
||||||
"record": {record, AskKey{Kind: "record", Node: "n1", Module: "gitea", Composed: digest}},
|
"record": {record, AskKey{Kind: "record", Node: "n1", Module: "gitea", Composed: digest}},
|
||||||
} {
|
} {
|
||||||
back, err := ParseAnyAskKey(want.got)
|
back, err := ParseAnyAskKey(want.got)
|
||||||
if err != nil || back != want.back {
|
if err != nil || back != want.back {
|
||||||
@@ -35,7 +35,7 @@ func TestTheAssignersAskKeysKeepTheirFormsAndTheValueItsRules(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for got, want := range map[string]string{hold: "hold.gitea." + hex, restore: "restore.n1.gitea." + hex, backup: "backup.n1.novox=2ebe." + hex,
|
for got, want := range map[string]string{hold: "hold.gitea." + hex, restore: "restore.n1.gitea." + hex, backup: "backup.n1.novox=2ebe." + hex,
|
||||||
placement: "placement.p-20261028t183000-1a2b3c", record: "record.n1.gitea." + strings.Repeat("cd", 32)} {
|
assignments: "assignments.p-20261028t183000-1a2b3c", record: "record.n1.gitea." + strings.Repeat("cd", 32)} {
|
||||||
if got != want {
|
if got != want {
|
||||||
t.Errorf("key %q, want %q", got, want)
|
t.Errorf("key %q, want %q", got, want)
|
||||||
}
|
}
|
||||||
@@ -45,7 +45,7 @@ func TestTheAssignersAskKeysKeepTheirFormsAndTheValueItsRules(t *testing.T) {
|
|||||||
t.Errorf("a merge ask key: %+v, %v", merge, err)
|
t.Errorf("a merge ask key: %+v, %v", merge, err)
|
||||||
}
|
}
|
||||||
for _, bad := range []string{"", "other.x", "hold.gitea", "hold.gitea.src2:" + hex, "restore.n1", "backup.n1.node", "backup.n1.gitea." + hex[:60],
|
for _, bad := range []string{"", "other.x", "hold.gitea", "hold.gitea.src2:" + hex, "restore.n1", "backup.n1.node", "backup.n1.gitea." + hex[:60],
|
||||||
"placement.AB", "record.n1.gitea." + hex[:10], "record.n1.node." + hex, "merge.g"} {
|
"assignment.AB", "record.n1.gitea." + hex[:10], "record.n1.node." + hex, "merge.g"} {
|
||||||
if _, err := ParseAnyAskKey(bad); err == nil {
|
if _, err := ParseAnyAskKey(bad); err == nil {
|
||||||
t.Errorf("ParseAnyAskKey(%q) reads a wrong key", bad)
|
t.Errorf("ParseAnyAskKey(%q) reads a wrong key", bad)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ go 1.26.0
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient 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/clients/meshmoduleassignerclient v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds v0.0.0-00010101000000-000000000000
|
||||||
@@ -14,7 +14,7 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
git.novox.be/novox/mesh-contracts/go/seats/nodeengine v0.0.0-00010101000000-000000000000 // indirect
|
git.novox.be/novox/mesh-contracts/go/seats/nodeengine v0.0.0-00010101000000-000000000000 // indirect
|
||||||
git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000 // indirect
|
git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000 // indirect
|
||||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/containerimageregistryclient v0.0.0-00010101000000-000000000000 // indirect
|
git.novox.be/novox/mesh-delivery/contracts/go/clients/containerimageregistryclient v0.0.0-00010101000000-000000000000 // indirect
|
||||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry v0.0.0-00010101000000-000000000000 // indirect
|
git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry v0.0.0-00010101000000-000000000000 // indirect
|
||||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog v0.0.0-00010101000000-000000000000 // indirect
|
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog v0.0.0-00010101000000-000000000000 // indirect
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"maps"
|
"maps"
|
||||||
|
"mesh-delivery-assigner/internal/assignment"
|
||||||
"mesh-delivery-assigner/internal/buckets"
|
"mesh-delivery-assigner/internal/buckets"
|
||||||
"mesh-delivery-assigner/internal/placement"
|
|
||||||
"mesh-delivery-assigner/internal/puts"
|
"mesh-delivery-assigner/internal/puts"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -21,10 +21,10 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||||
mwc "git.novox.be/novox/mesh-contracts/placement/go/clients/meshwantsclient"
|
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||||
mdcc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
mdcc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||||
|
mwc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshwantsclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-assigner/asks"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-assigner/asks"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||||
@@ -37,10 +37,10 @@ import (
|
|||||||
|
|
||||||
"mesh-delivery-assigner/internal/judge"
|
"mesh-delivery-assigner/internal/judge"
|
||||||
|
|
||||||
|
mmac "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
|
||||||
|
mma "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||||
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
|
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
|
||||||
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
|
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
|
||||||
mmac "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
|
||||||
mma "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
|
||||||
"git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient"
|
"git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient"
|
||||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||||
"git.novox.be/novox/mesh-sdk/go/announce"
|
"git.novox.be/novox/mesh-sdk/go/announce"
|
||||||
@@ -56,7 +56,7 @@ import (
|
|||||||
"git.novox.be/novox/mesh-sdk/go/views"
|
"git.novox.be/novox/mesh-sdk/go/views"
|
||||||
|
|
||||||
"mesh-delivery-assigner/internal/assign"
|
"mesh-delivery-assigner/internal/assign"
|
||||||
plc "mesh-delivery-assigner/internal/placement"
|
plc "mesh-delivery-assigner/internal/assignment"
|
||||||
)
|
)
|
||||||
|
|
||||||
// bed is an embedded bus with JetStream, every bucket the assigner reads and keeps, and the stream the mesh's events
|
// bed is an embedded bus with JetStream, every bucket the assigner reads and keeps, and the stream the mesh's events
|
||||||
@@ -67,7 +67,7 @@ type bed struct {
|
|||||||
ctx context.Context
|
ctx context.Context
|
||||||
dir string
|
dir string
|
||||||
buckets map[string]jetstream.KeyValue
|
buckets map[string]jetstream.KeyValue
|
||||||
// placed is, by node, the modules the bed's placement names on it (place).
|
// placed is, by node, the modules the bed's assignments names on it (place).
|
||||||
placed map[string][]string
|
placed map[string][]string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,7 +229,7 @@ func saysWhatItKeeps(t *testing.T, b *bed, done <-chan error) {
|
|||||||
func TestTheAssignerDoesNotStartWithoutABucketItReads(t *testing.T) {
|
func TestTheAssignerDoesNotStartWithoutABucketItReads(t *testing.T) {
|
||||||
for _, missing := range readBuckets {
|
for _, missing := range readBuckets {
|
||||||
if startsWithout(missing) {
|
if startsWithout(missing) {
|
||||||
continue // it starts without it (the placement and the grants' source: main_test.go; the puts: below)
|
continue // it starts without it (the assignments and the grants' source: main_test.go; the puts: below)
|
||||||
}
|
}
|
||||||
b := startBed(t, missing)
|
b := startBed(t, missing)
|
||||||
select {
|
select {
|
||||||
@@ -278,26 +278,36 @@ func TestTheAssignerDoesNotStartWithoutABucketItReads(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Without the seat's placement state it starts all the same, and once the bucket is made (the controller makes
|
// Without the seat's assignments state it starts all the same, and once the bucket is made (the controller makes
|
||||||
// it from the manifest) it is read with no restart: a node it names gets its own want, the node composing its own.
|
// it from the manifest) it is read with no restart: a node it names gets its own want, the node composing its own.
|
||||||
pb := startBed(t, plc.PlacementBucket)
|
pb := startBed(t, plc.AssignmentsBucket)
|
||||||
pdone := pb.start(t)
|
pdone := pb.start(t)
|
||||||
select {
|
select {
|
||||||
case err := <-pdone:
|
case err := <-pdone:
|
||||||
t.Fatalf("without %s the assigner stopped: %v", plc.PlacementBucket, err)
|
t.Fatalf("without %s the assigner stopped: %v", plc.AssignmentsBucket, err)
|
||||||
case <-time.After(2 * time.Second):
|
case <-time.After(2 * time.Second):
|
||||||
}
|
}
|
||||||
placement, err := pb.js.CreateKeyValue(pb.ctx, jetstream.KeyValueConfig{Bucket: plc.PlacementBucket, History: 5})
|
// ADR 0641: the state the seat's assignments were kept in before holds n1's keys, and the form of one key per node; the
|
||||||
|
// assigner copies the keys into the state made after its start, drops the second form, and leaves the old state empty.
|
||||||
|
values, _ := mmac.AssignmentsValues(mma.Assignments{Node: "n1", Modules: []mma.PlacedModule{{Module: "web"}}})
|
||||||
|
for _, k := range slices.Sorted(maps.Keys(values)) { // a key per module, then the node key
|
||||||
|
pb.putJSON(t, retiredBucket, k, values[k])
|
||||||
|
}
|
||||||
|
pb.putJSON(t, retiredBucket, "placement.n1", values["n1.node"])
|
||||||
|
assignments, err := pb.js.CreateKeyValue(pb.ctx, jetstream.KeyValueConfig{Bucket: plc.AssignmentsBucket, History: 5})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
pl, _ := mmac.EncodePlacement(mma.Placement{Node: "n1", Modules: []mma.PlacedModule{{Module: "web"}}})
|
|
||||||
plkey, _ := mmac.LegacyKey("n1") // the form before, read beside the new (research 072 row 37)
|
|
||||||
if _, err := placement.Put(pb.ctx, plkey, pl); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if nw, err := mwc.DecodeNodeWant(pb.get(t, buckets.Wants, "want.n1.node")); err != nil || !nw.Self {
|
if nw, err := mwc.DecodeNodeWant(pb.get(t, buckets.Wants, "want.n1.node")); err != nil || !nw.Self {
|
||||||
t.Fatalf("the placement state made after the start was not read: n1's own want = %+v, %v", nw, err)
|
t.Fatalf("the assignments state made after the start was not read: n1's own want = %+v, %v", nw, err)
|
||||||
|
}
|
||||||
|
for _, k := range slices.Sorted(maps.Keys(values)) {
|
||||||
|
if e, err := assignments.Get(pb.ctx, k); err != nil || !slices.Equal(e.Value(), values[k]) {
|
||||||
|
t.Errorf("%s was not copied into %s: %v", k, plc.AssignmentsBucket, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if left, err := pb.buckets[retiredBucket].Keys(pb.ctx); err == nil || !errors.Is(err, jetstream.ErrNoKeysFound) {
|
||||||
|
t.Errorf("the retired state still holds %v (%v)", left, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,8 +319,8 @@ func (b *bed) putJSON(t *testing.T, bucket, key string, value []byte) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// place adds module to the seat's placement of node, written as the seat writes it: one node key and a key per module
|
// place adds module to the seat's assignments of node, written as the seat writes it: one node key and a key per module
|
||||||
// (research 072 row 37). The placement alone names where modules run (ADR 0567 §8).
|
// (research 072 row 37). The assignments alone names where modules run (ADR 0567 §8).
|
||||||
func (b *bed) place(t *testing.T, node, module string) {
|
func (b *bed) place(t *testing.T, node, module string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if b.placed == nil {
|
if b.placed == nil {
|
||||||
@@ -319,16 +329,16 @@ func (b *bed) place(t *testing.T, node, module string) {
|
|||||||
if !slices.Contains(b.placed[node], module) {
|
if !slices.Contains(b.placed[node], module) {
|
||||||
b.placed[node] = append(b.placed[node], module)
|
b.placed[node] = append(b.placed[node], module)
|
||||||
}
|
}
|
||||||
pl := mma.Placement{Node: node}
|
pl := mma.Assignments{Node: node}
|
||||||
for _, m := range b.placed[node] {
|
for _, m := range b.placed[node] {
|
||||||
pl.Modules = append(pl.Modules, mma.PlacedModule{Module: m})
|
pl.Modules = append(pl.Modules, mma.PlacedModule{Module: m})
|
||||||
}
|
}
|
||||||
values, err := mmac.PlacementValues(pl)
|
values, err := mmac.AssignmentsValues(pl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
for key, raw := range values {
|
for key, raw := range values {
|
||||||
b.putJSON(t, plc.PlacementBucket, key, raw)
|
b.putJSON(t, plc.AssignmentsBucket, key, raw)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -426,11 +436,11 @@ func TestWantsAreWrittenAndNoDifferenceIs(t *testing.T) {
|
|||||||
// indexer its runtime of its own answers each first-view tool to the readers its manifest's callers lists name, at
|
// indexer its runtime of its own answers each first-view tool to the readers its manifest's callers lists name, at
|
||||||
// its mark, and refuses every other account, the assigner's own included.
|
// its mark, and refuses every other account, the assigner's own included.
|
||||||
firstViewsBeforeAnyNewFormState(t, b, map[string][]string{
|
firstViewsBeforeAnyNewFormState(t, b, map[string][]string{
|
||||||
"placement": {"n1.mesh-grants", "n1.node-tools", "n1.mesh-delivery-indexer"},
|
"assignments": {"n1.mesh-grants", "n1.node-tools", "n1.mesh-delivery-indexer"},
|
||||||
"seat-holders": {"n1.node-tools"},
|
"seat-holders": {"n1.node-tools"},
|
||||||
"wants": {"n1.mesh-grants"},
|
"wants": {"n1.mesh-grants"},
|
||||||
})
|
})
|
||||||
graphKey := meshconditionsclient.Key(meshconditionsclient.ScopeModule, placement.Module+"."+buckets.IndexerModule+"."+graph.State, views.UnansweredKind)
|
graphKey := meshconditionsclient.Key(meshconditionsclient.ScopeModule, assignment.Module+"."+buckets.IndexerModule+"."+graph.State, views.UnansweredKind)
|
||||||
graphRaised := func() bool {
|
graphRaised := func() bool {
|
||||||
e, err := b.buckets[assign.FindingsBucket].Get(b.ctx, graphKey)
|
e, err := b.buckets[assign.FindingsBucket].Get(b.ctx, graphKey)
|
||||||
return err == nil && strings.Contains(string(e.Value()), buckets.IndexerModule+".graph")
|
return err == nil && strings.Contains(string(e.Value()), buckets.IndexerModule+".graph")
|
||||||
@@ -492,16 +502,17 @@ func TestWantsAreWrittenAndNoDifferenceIs(t *testing.T) {
|
|||||||
t.Errorf("the condition of web on neither list on g14 was not cleared once g14 got its want")
|
t.Errorf("the condition of web on neither list on g14 was not cleared once g14 got its want")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Issue 1126 (ADR 0547 §2): the catalogue's placement reaches the indexer's state naming web on every node the
|
// Issue 1126 (ADR 0547 §2): the catalogue's assignments reaches the indexer's state naming web on every node the
|
||||||
// assignments name it on. Each node then composes its own: its want names the build alone (Self, no composition), and
|
// assignments name it on. Each node then composes its own: its want names the build alone (Self, no composition), and
|
||||||
// its own want says it composes its own, which the node agent reads (mesh-host's TestNats...ComposesAndAppliesItsOwn).
|
// its own want says it composes its own, which the node agent reads (mesh-host's TestNats...ComposesAndAppliesItsOwn).
|
||||||
for _, node := range []string{"ace", "g14", "shanks"} {
|
for _, node := range []string{"ace", "g14", "shanks"} {
|
||||||
pl, err := mmac.EncodePlacement(mma.Placement{Node: node, Modules: []mma.PlacedModule{{Module: "web"}}})
|
values, err := mmac.AssignmentsValues(mma.Assignments{Node: node, Modules: []mma.PlacedModule{{Module: "web"}}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
pkey, _ := mmac.LegacyKey(node)
|
for _, k := range slices.Sorted(maps.Keys(values)) {
|
||||||
b.putJSON(t, plc.PlacementBucket, pkey, pl)
|
b.putJSON(t, plc.AssignmentsBucket, k, values[k])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if w := b.wantNow(t, "ace", "web", "ace's want once placed: the build alone, composed by the node", func(w mwc.Want) bool { return w.Self }); w.Composed != "" || w.Fp != fNew {
|
if w := b.wantNow(t, "ace", "web", "ace's want once placed: the build alone, composed by the node", func(w mwc.Want) bool { return w.Self }); w.Composed != "" || w.Fp != fNew {
|
||||||
t.Errorf("ace's want once placed = %+v", w)
|
t.Errorf("ace's want once placed = %+v", w)
|
||||||
@@ -521,7 +532,7 @@ func TestWantsAreWrittenAndNoDifferenceIs(t *testing.T) {
|
|||||||
// ADR 0603 (L3, D15): one source revision changes a provider (db), its new consumer (app) and a build module (tool, whose
|
// ADR 0603 (L3, D15): one source revision changes a provider (db), its new consumer (app) and a build module (tool, whose
|
||||||
// recipe moved: ace runs its first build and the second, under the agreed toolchain, is the one the source revision names).
|
// recipe moved: ace runs its first build and the second, under the agreed toolchain, is the one the source revision names).
|
||||||
// The keys are written as their writers write them: the indexer's modules and manifests, the builder's builds, the
|
// The keys are written as their writers write them: the indexer's modules and manifests, the builder's builds, the
|
||||||
// controller's grants, the placement seat's placement and the provisioner's provisions. The build module's want moves at
|
// controller's grants, the assignments seat's assignments and the provisioner's provisions. The build module's want moves at
|
||||||
// once and the provider's with it; the consumer's waits for its provision, with the words naming it, and moves once the
|
// once and the provider's with it; the consumer's waits for its provision, with the words naming it, and moves once the
|
||||||
// provisioner provides it (rule 8, ADR 0570 §4): nothing waits for a turn or an order, only for a key.
|
// provisioner provides it (rule 8, ADR 0570 §4): nothing waits for a turn or an order, only for a key.
|
||||||
offer := `"provides":[{"name":"database","scope":"mesh","answered-by":"helper"}]`
|
offer := `"provides":[{"name":"database","scope":"mesh","answered-by":"helper"}]`
|
||||||
@@ -1095,7 +1106,7 @@ func (s bedStore) kv(state string) jetstream.KeyValue {
|
|||||||
if strings.Contains(state, ".") {
|
if strings.Contains(state, ".") {
|
||||||
return s.b.buckets[strings.Replace(state, ".", "_", 1)]
|
return s.b.buckets[strings.Replace(state, ".", "_", 1)]
|
||||||
}
|
}
|
||||||
return s.b.buckets[placement.Module+"_"+state]
|
return s.b.buckets[assignment.Module+"_"+state]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s bedStore) get(state, key string) ([]byte, uint64, bool, error) {
|
func (s bedStore) get(state, key string) ([]byte, uint64, bool, error) {
|
||||||
@@ -1125,23 +1136,23 @@ func (s bedStore) history(string, string) ([]stdio.StateRevision, error) { retur
|
|||||||
|
|
||||||
// 1126 (ADR 0567 §2-§6), the seat mesh-module-assigner whole on the bus: a proposal moving web from the anchor to the laptop is
|
// 1126 (ADR 0567 §2-§6), the seat mesh-module-assigner whole on the bus: a proposal moving web from the anchor to the laptop is
|
||||||
// proposed through the verb, the assigner finds both nodes must answer, they answer the same in their haves, the
|
// proposed through the verb, the assigner finds both nodes must answer, they answer the same in their haves, the
|
||||||
// operator is asked and approves, and each key is written at a new revision while placement-changed is heard on the
|
// operator is asked and approves, and each key is written at a new revision while assignments-changed is heard on the
|
||||||
// assigner's stream naming both nodes. A second proposal laid over the old revision of a key moved since is laid again,
|
// assigner's stream naming both nodes. A second proposal laid over the old revision of a key moved since is laid again,
|
||||||
// and declined, writes nothing and is withdrawn.
|
// and declined, writes nothing and is withdrawn.
|
||||||
func TestAProposalIsAnsweredApprovedAndWritten(t *testing.T) {
|
func TestAProposalIsAnsweredApprovedAndWritten(t *testing.T) {
|
||||||
b := startBed(t, "")
|
b := startBed(t, "")
|
||||||
if _, err := b.js.CreateStream(b.ctx, jetstream.StreamConfig{Name: "MOD_" + placement.Module + "_placement",
|
if _, err := b.js.CreateStream(b.ctx, jetstream.StreamConfig{Name: "MOD_" + assignment.Module + "_assignments",
|
||||||
Subjects: placementSubjects()}); err != nil { // the seat's four events: the views family has a stream of its own
|
Subjects: assignmentsSubjects()}); err != nil { // the seat's four events: the views family has a stream of its own
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
heard := make(chan mma.PlacementEvent, 16)
|
heard := make(chan mma.AssignmentsEvent, 16)
|
||||||
nc, err := nats.Connect(b.srv.ClientURL())
|
nc, err := nats.Connect(b.srv.ClientURL())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
t.Cleanup(nc.Close)
|
t.Cleanup(nc.Close)
|
||||||
if _, err := nc.Subscribe(seatdef.EventSubject(mma.Seat, mma.EventPlacementChanged), func(m *nats.Msg) {
|
if _, err := nc.Subscribe(seatdef.EventSubject(mma.Seat, mma.EventAssignmentsChanged), func(m *nats.Msg) {
|
||||||
var e mma.PlacementEvent
|
var e mma.AssignmentsEvent
|
||||||
if json.Unmarshal(m.Data, &e) == nil {
|
if json.Unmarshal(m.Data, &e) == nil {
|
||||||
heard <- e
|
heard <- e
|
||||||
}
|
}
|
||||||
@@ -1155,38 +1166,34 @@ func TestAProposalIsAnsweredApprovedAndWritten(t *testing.T) {
|
|||||||
b.putJSON(t, buckets.Modules, "web", []byte(`{"v":1}`))
|
b.putJSON(t, buckets.Modules, "web", []byte(`{"v":1}`))
|
||||||
b.putJSON(t, buckets.Modules, "shell", []byte(`{"v":1}`))
|
b.putJSON(t, buckets.Modules, "shell", []byte(`{"v":1}`))
|
||||||
b.putJSON(t, buckets.Modules, "mesh-enrolment", []byte(`{"v":1}`))
|
b.putJSON(t, buckets.Modules, "mesh-enrolment", []byte(`{"v":1}`))
|
||||||
// place writes a node's placement as another writer would, in its node and module keys (research 072 row 37), and
|
// place writes a node's assignments as another writer would, in its node and module keys (research 072 row 37), and
|
||||||
// answers its node key's revision; legacy writes it in the form before, placement.<node>, alone.
|
// answers its node key's revision.
|
||||||
place := func(node string, legacy bool, modules ...string) uint64 {
|
place := func(node string, modules ...string) uint64 {
|
||||||
p := mma.Placement{Node: node, Modules: []mma.PlacedModule{}}
|
p := mma.Assignments{Node: node, Modules: []mma.PlacedModule{}}
|
||||||
for _, m := range modules {
|
for _, m := range modules {
|
||||||
p.Modules = append(p.Modules, mma.PlacedModule{Module: m})
|
p.Modules = append(p.Modules, mma.PlacedModule{Module: m})
|
||||||
if m == "mesh-enrolment" { // the module that holds the seat mesh-node-enroller: who may add a node is read from it
|
if m == "mesh-enrolment" { // the module that holds the seat mesh-node-enroller: who may add a node is read from it
|
||||||
p.Holds = append(p.Holds, mma.Holding{Claim: "mesh-node-enroller", Scope: "mesh", Module: m})
|
p.Holds = append(p.Holds, mma.Holding{Claim: "mesh-node-enroller", Scope: "mesh", Module: m})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
values, _ := mmac.PlacementValues(p)
|
values, _ := mmac.AssignmentsValues(p)
|
||||||
key, _ := mmac.NodeKey(node)
|
key, _ := mmac.NodeKey(node)
|
||||||
if legacy {
|
|
||||||
key, _ = mmac.LegacyKey(node)
|
|
||||||
values = map[string][]byte{key: values[key]}
|
|
||||||
}
|
|
||||||
for _, k := range slices.Sorted(maps.Keys(values)) {
|
for _, k := range slices.Sorted(maps.Keys(values)) {
|
||||||
if k == key {
|
if k == key {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if _, err := b.buckets[plc.PlacementBucket].Put(b.ctx, k, values[k]); err != nil {
|
if _, err := b.buckets[plc.AssignmentsBucket].Put(b.ctx, k, values[k]); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rev, err := b.buckets[plc.PlacementBucket].Put(b.ctx, key, values[key])
|
rev, err := b.buckets[plc.AssignmentsBucket].Put(b.ctx, key, values[key])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
return rev
|
return rev
|
||||||
}
|
}
|
||||||
place("anchor", true, "web")
|
place("anchor", "web")
|
||||||
laptopRev := place("laptop", false, "shell")
|
laptopRev := place("laptop", "shell")
|
||||||
bound := judge.AnswerBound
|
bound := judge.AnswerBound
|
||||||
t.Cleanup(func() { judge.AnswerBound = bound })
|
t.Cleanup(func() { judge.AnswerBound = bound })
|
||||||
judge.AnswerBound = 2 * time.Second
|
judge.AnswerBound = 2 * time.Second
|
||||||
@@ -1194,9 +1201,9 @@ func TestAProposalIsAnsweredApprovedAndWritten(t *testing.T) {
|
|||||||
// The holder converts anchor, held in the form before alone, to its node and module keys once at its start.
|
// The holder converts anchor, held in the form before alone, to its node and module keys once at its start.
|
||||||
var anchorRev uint64
|
var anchorRev uint64
|
||||||
for deadline := time.Now().Add(15 * time.Second); anchorRev == 0; time.Sleep(50 * time.Millisecond) {
|
for deadline := time.Now().Add(15 * time.Second); anchorRev == 0; time.Sleep(50 * time.Millisecond) {
|
||||||
if e, err := b.buckets[plc.PlacementBucket].Get(b.ctx, "anchor.node"); err == nil {
|
if e, err := b.buckets[plc.AssignmentsBucket].Get(b.ctx, "anchor.node"); err == nil {
|
||||||
anchorRev = e.Revision()
|
anchorRev = e.Revision()
|
||||||
if _, err := b.buckets[plc.PlacementBucket].Get(b.ctx, "anchor.web"); err != nil {
|
if _, err := b.buckets[plc.AssignmentsBucket].Get(b.ctx, "anchor.web"); err != nil {
|
||||||
t.Fatalf("anchor converted without its module key: %v", err)
|
t.Fatalf("anchor converted without its module key: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1250,33 +1257,29 @@ func TestAProposalIsAnsweredApprovedAndWritten(t *testing.T) {
|
|||||||
}
|
}
|
||||||
answerAll(p)
|
answerAll(p)
|
||||||
p = proposalNow(p.ID, "asked", func(p mma.Proposal) bool { return p.State == mmac.ProposalAsked })
|
p = proposalNow(p.ID, "asked", func(p mma.Proposal) bool { return p.State == mmac.ProposalAsked })
|
||||||
akey, _ := asks.PlacementAskKey(p.ID)
|
akey, _ := asks.AssignmentsAskKey(p.ID)
|
||||||
b.answer(t, akey, assign.OptionYes)
|
b.answer(t, akey, assign.OptionYes)
|
||||||
select {
|
select {
|
||||||
case e := <-heard:
|
case e := <-heard:
|
||||||
if e.Proposal.ID != p.ID || len(e.Nodes) != 2 || changeIn(e.Nodes[0]).Before != anchorRev || changeIn(e.Nodes[1]).Before != laptopRev ||
|
if e.Proposal.ID != p.ID || len(e.Nodes) != 2 || changeIn(e.Nodes[0]).Before != anchorRev || changeIn(e.Nodes[1]).Before != laptopRev ||
|
||||||
changeIn(e.Nodes[0]).After <= anchorRev || e.Warrant == nil || !slices.Equal(changeIn(e.Nodes[0]).Removed, []string{"web"}) {
|
changeIn(e.Nodes[0]).After <= anchorRev || e.Warrant == nil || !slices.Equal(changeIn(e.Nodes[0]).Removed, []string{"web"}) {
|
||||||
t.Errorf("placement-changed = %+v", e)
|
t.Errorf("assignments-changed = %+v", e)
|
||||||
}
|
}
|
||||||
case <-time.After(15 * time.Second):
|
case <-time.After(15 * time.Second):
|
||||||
t.Fatal("no placement-changed heard")
|
t.Fatal("no assignments-changed heard")
|
||||||
}
|
}
|
||||||
for node, want := range map[string][]string{"anchor": {}, "laptop": {"shell", "web"}} {
|
for node, want := range map[string][]string{"anchor": {}, "laptop": {"shell", "web"}} {
|
||||||
// The node key names the modules, each module key carries its own, and the mirror of the form before follows.
|
// The node key names the modules, each module key carries its own.
|
||||||
values := map[string][]byte{}
|
values := map[string][]byte{}
|
||||||
for _, key := range []string{node + ".node", "placement." + node, node + ".web", node + ".shell"} {
|
for _, key := range []string{node + ".node", node + ".web", node + ".shell"} {
|
||||||
if e, err := b.buckets[plc.PlacementBucket].Get(b.ctx, key); err == nil {
|
if e, err := b.buckets[plc.AssignmentsBucket].Get(b.ctx, key); err == nil {
|
||||||
values[key] = e.Value()
|
values[key] = e.Value()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
got, err := mmac.PlacementsOf(values)
|
got, err := mmac.AssignmentsOf(values)
|
||||||
if err != nil || !slices.Equal(mmac.Assigned(got[node]), want) {
|
if err != nil || !slices.Equal(mmac.Assigned(got[node]), want) {
|
||||||
t.Errorf("%s = %v, %v, want %v", node, mmac.Assigned(got[node]), err, want)
|
t.Errorf("%s = %v, %v, want %v", node, mmac.Assigned(got[node]), err, want)
|
||||||
}
|
}
|
||||||
mirror, _ := mmac.DecodePlacement(values["placement."+node])
|
|
||||||
if !slices.Equal(mmac.Assigned(mirror), want) {
|
|
||||||
t.Errorf("the mirror placement.%s = %v, want %v", node, mmac.Assigned(mirror), want)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if b.hasKey(plc.ProposalsBucket, p.ID) {
|
if b.hasKey(plc.ProposalsBucket, p.ID) {
|
||||||
t.Error("an applied proposal stays in the state")
|
t.Error("an applied proposal stays in the state")
|
||||||
@@ -1288,13 +1291,13 @@ func TestAProposalIsAnsweredApprovedAndWritten(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
moved := q.(proposed).Proposal
|
moved := q.(proposed).Proposal
|
||||||
place("laptop", false, "shell", "web") // another writer moved the key
|
place("laptop", "shell", "web") // another writer moved the key
|
||||||
relaid := proposalNow(moved.ID, "laid again", func(p mma.Proposal) bool {
|
relaid := proposalNow(moved.ID, "laid again", func(p mma.Proposal) bool {
|
||||||
return strings.Contains(p.Words, "laid again") && len(p.Must) > 0
|
return strings.Contains(p.Words, "laid again") && len(p.Must) > 0
|
||||||
})
|
})
|
||||||
answerAll(relaid)
|
answerAll(relaid)
|
||||||
proposalNow(moved.ID, "asked", func(p mma.Proposal) bool { return p.State == mmac.ProposalAsked })
|
proposalNow(moved.ID, "asked", func(p mma.Proposal) bool { return p.State == mmac.ProposalAsked })
|
||||||
akey, _ = asks.PlacementAskKey(moved.ID)
|
akey, _ = asks.AssignmentsAskKey(moved.ID)
|
||||||
b.answer(t, akey, assign.OptionNo)
|
b.answer(t, akey, assign.OptionNo)
|
||||||
for deadline := time.Now().Add(15 * time.Second); b.hasKey(plc.ProposalsBucket, moved.ID); time.Sleep(50 * time.Millisecond) {
|
for deadline := time.Now().Add(15 * time.Second); b.hasKey(plc.ProposalsBucket, moved.ID); time.Sleep(50 * time.Millisecond) {
|
||||||
if time.Now().After(deadline) {
|
if time.Now().After(deadline) {
|
||||||
@@ -1302,7 +1305,7 @@ func TestAProposalIsAnsweredApprovedAndWritten(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
key, _ := mmac.NodeKey("laptop")
|
key, _ := mmac.NodeKey("laptop")
|
||||||
if e, _ := b.buckets[plc.PlacementBucket].Get(b.ctx, key); e == nil || !strings.Contains(string(e.Value()), "web") {
|
if e, _ := b.buckets[plc.AssignmentsBucket].Get(b.ctx, key); e == nil || !strings.Contains(string(e.Value()), "web") {
|
||||||
t.Error("a declined proposal wrote its key")
|
t.Error("a declined proposal wrote its key")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1320,7 +1323,7 @@ func TestAProposalIsAnsweredApprovedAndWritten(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 774/S59 (ADR 0587 §7): a joining node is written on the enrolment's warrant with no node to answer and no ask raised,
|
// 774/S59 (ADR 0587 §7): a joining node is written on the enrolment's warrant with no node to answer and no ask raised,
|
||||||
// and placement-changed carries that warrant. The assigner reads no lease (ADR 0567 §8): the enrolment's own issue waits
|
// and assignments-changed carries that warrant. The assigner reads no lease (ADR 0567 §8): the enrolment's own issue waits
|
||||||
// for the lease write (ADR 0587 §4).
|
// for the lease write (ADR 0587 §4).
|
||||||
for len(heard) > 0 {
|
for len(heard) > 0 {
|
||||||
<-heard
|
<-heard
|
||||||
@@ -1329,7 +1332,7 @@ func TestAProposalIsAnsweredApprovedAndWritten(t *testing.T) {
|
|||||||
// now, on a node of its own.
|
// now, on a node of its own.
|
||||||
hubRecord, _ := nodeengineclient.EncodeHaveNode(nodeengine.HaveByNodeNodeValue{Record: &nodeengine.NodeRecord{Node: "hub"}})
|
hubRecord, _ := nodeengineclient.EncodeHaveNode(nodeengine.HaveByNodeNodeValue{Record: &nodeengine.NodeRecord{Node: "hub"}})
|
||||||
b.putJSON(t, buckets.Have, "hub.node", hubRecord)
|
b.putJSON(t, buckets.Have, "hub.node", hubRecord)
|
||||||
place("hub", false, "mesh-enrolment")
|
place("hub", "mesh-enrolment")
|
||||||
joined, err := verbs.doPropose(map[string]any{"why": "enrolment of ace", "warrant": `{"node":"ace","address":"10.9.0.2","ask":"enrol-ace-1","warrant":"enrol-ace-1@2026-10-28T20:00:00Z"}`,
|
joined, err := verbs.doPropose(map[string]any{"why": "enrolment of ace", "warrant": `{"node":"ace","address":"10.9.0.2","ask":"enrol-ace-1","warrant":"enrol-ace-1@2026-10-28T20:00:00Z"}`,
|
||||||
"changes": `[{"op":"add-node","node":"ace","decided":{"address":"10.9.0.2"},"keys":{"overlay":"dHVubmVs"}}]`}, "novox.mesh-enrolment")
|
"changes": `[{"op":"add-node","node":"ace","decided":{"address":"10.9.0.2"},"keys":{"overlay":"dHVubmVs"}}]`}, "novox.mesh-enrolment")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1338,19 +1341,19 @@ func TestAProposalIsAnsweredApprovedAndWritten(t *testing.T) {
|
|||||||
select {
|
select {
|
||||||
case e := <-heard:
|
case e := <-heard:
|
||||||
if e.Proposal.ID != joined.(proposed).Proposal.ID || e.Warrant == nil || e.Warrant.Warrant != "enrol-ace-1@2026-10-28T20:00:00Z" || len(e.Nodes) != 1 || changeIn(e.Nodes[0]).Node != "ace" {
|
if e.Proposal.ID != joined.(proposed).Proposal.ID || e.Warrant == nil || e.Warrant.Warrant != "enrol-ace-1@2026-10-28T20:00:00Z" || len(e.Nodes) != 1 || changeIn(e.Nodes[0]).Node != "ace" {
|
||||||
t.Errorf("placement-changed for the joining node = %+v", e)
|
t.Errorf("assignments-changed for the joining node = %+v", e)
|
||||||
}
|
}
|
||||||
case <-time.After(15 * time.Second):
|
case <-time.After(15 * time.Second):
|
||||||
t.Fatal("no placement-changed heard for the joining node")
|
t.Fatal("no assignments-changed heard for the joining node")
|
||||||
}
|
}
|
||||||
if ace, err := b.buckets[plc.PlacementBucket].Get(b.ctx, "ace.node"); err != nil || !strings.Contains(string(ace.Value()), "10.9.0.2") {
|
if ace, err := b.buckets[plc.AssignmentsBucket].Get(b.ctx, "ace.node"); err != nil || !strings.Contains(string(ace.Value()), "10.9.0.2") {
|
||||||
t.Errorf("the joining node's key = %v, %v", ace, err)
|
t.Errorf("the joining node's key = %v, %v", ace, err)
|
||||||
}
|
}
|
||||||
if akey, _ := asks.PlacementAskKey(joined.(proposed).Proposal.ID); b.hasKey(buckets.Asks, akey) || b.hasKey(plc.ProposalsBucket, joined.(proposed).Proposal.ID) {
|
if akey, _ := asks.AssignmentsAskKey(joined.(proposed).Proposal.ID); b.hasKey(buckets.Asks, akey) || b.hasKey(plc.ProposalsBucket, joined.(proposed).Proposal.ID) {
|
||||||
t.Error("an ask was raised for the joining node, or its proposal stays open")
|
t.Error("an ask was raised for the joining node, or its proposal stays open")
|
||||||
}
|
}
|
||||||
// Its enrolment ends before it confirms: a remove-node on the same warrant deletes its key at once, with no ask, and
|
// Its enrolment ends before it confirms: a remove-node on the same warrant deletes its key at once, with no ask, and
|
||||||
// placement-changed carries the warrant (the operator's decision of 2026-11-02 01:05).
|
// assignments-changed carries the warrant (the operator's decision of 2026-11-02 01:05).
|
||||||
for len(heard) > 0 {
|
for len(heard) > 0 {
|
||||||
<-heard
|
<-heard
|
||||||
}
|
}
|
||||||
@@ -1361,12 +1364,12 @@ func TestAProposalIsAnsweredApprovedAndWritten(t *testing.T) {
|
|||||||
select {
|
select {
|
||||||
case e := <-heard:
|
case e := <-heard:
|
||||||
if e.Warrant == nil || len(e.Nodes) != 1 || changeIn(e.Nodes[0]).Node != "ace" {
|
if e.Warrant == nil || len(e.Nodes) != 1 || changeIn(e.Nodes[0]).Node != "ace" {
|
||||||
t.Errorf("placement-changed for the removed node = %+v", e)
|
t.Errorf("assignments-changed for the removed node = %+v", e)
|
||||||
}
|
}
|
||||||
case <-time.After(15 * time.Second):
|
case <-time.After(15 * time.Second):
|
||||||
t.Fatal("no placement-changed heard for the removed node")
|
t.Fatal("no assignments-changed heard for the removed node")
|
||||||
}
|
}
|
||||||
if _, err := b.buckets[plc.PlacementBucket].Get(b.ctx, "ace.node"); err == nil {
|
if _, err := b.buckets[plc.AssignmentsBucket].Get(b.ctx, "ace.node"); err == nil {
|
||||||
t.Error("the removed node's key stays")
|
t.Error("the removed node's key stays")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1382,8 +1385,8 @@ func (b *bed) viewsHold(t *testing.T, state, key string) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
for deadline := time.Now().Add(20 * time.Second); ; time.Sleep(50 * time.Millisecond) {
|
for deadline := time.Now().Add(20 * time.Second); ; time.Sleep(50 * time.Millisecond) {
|
||||||
f := &views.Follower{Owner: placement.Module, Tool: state, State: state, Ask: func(context.Context, string) (views.Answer, error) {
|
f := &views.Follower{Owner: assignment.Module, Tool: state, State: state, Ask: func(context.Context, string) (views.Answer, error) {
|
||||||
return views.FirstView(placement.Module, state, src(kv), src(marks), "", time.Now())
|
return views.FirstView(assignment.Module, state, src(kv), src(marks), "", time.Now())
|
||||||
}}
|
}}
|
||||||
if err := f.Start(b.ctx); err != nil {
|
if err := f.Start(b.ctx); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -1392,7 +1395,7 @@ func (b *bed) viewsHold(t *testing.T, state, key string) {
|
|||||||
gap := false
|
gap := false
|
||||||
for seq := info.State.FirstSeq; seq > 0 && seq <= info.State.LastSeq; seq++ {
|
for seq := info.State.FirstSeq; seq > 0 && seq <= info.State.LastSeq; seq++ {
|
||||||
m, err := st.GetMsg(b.ctx, seq)
|
m, err := st.GetMsg(b.ctx, seq)
|
||||||
if owner, _ := views.OwnerOfEvent(m.Subject); err != nil || owner != placement.Module || views.StateOfEvent(m.Subject) != state {
|
if owner, _ := views.OwnerOfEvent(m.Subject); err != nil || owner != assignment.Module || views.StateOfEvent(m.Subject) != state {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
e, err := views.DecodeEvent(m.Data)
|
e, err := views.DecodeEvent(m.Data)
|
||||||
@@ -1446,11 +1449,11 @@ func (s bedSource) Get(key string) (*views.Entry, bool, error) {
|
|||||||
return &views.Entry{Value: e.Value(), Revision: e.Revision()}, true, nil
|
return &views.Entry{Value: e.Value(), Revision: e.Revision()}, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// placementSubjects are the subjects of the seat's four events on the assigner's stream placement.
|
// assignmentsSubjects are the subjects of the seat's four events on the assigner's stream assignments.
|
||||||
func placementSubjects() []string {
|
func assignmentsSubjects() []string {
|
||||||
var out []string
|
var out []string
|
||||||
for _, e := range mma.Events() {
|
for _, e := range mma.Events() {
|
||||||
out = append(out, process.ModuleStreamSubject(placement.Module, e))
|
out = append(out, process.ModuleStreamSubject(assignment.Module, e))
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
@@ -1553,7 +1556,7 @@ func firstViewsBeforeAnyNewFormState(t *testing.T, b *bed, granted map[string][]
|
|||||||
outR, outW := io.Pipe()
|
outR, outW := io.Pipe()
|
||||||
t.Cleanup(func() { inW.Close(); outW.Close() })
|
t.Cleanup(func() { inW.Close(); outW.Close() })
|
||||||
go func() {
|
go func() {
|
||||||
_ = stdio.ServeOn(placement.Module, viewTools(func(k string) string { return env[k] }, state, time.Now), inR, outW)
|
_ = stdio.ServeOn(assignment.Module, viewTools(func(k string) string { return env[k] }, state, time.Now), inR, outW)
|
||||||
}()
|
}()
|
||||||
answers := make(chan map[string]any, 16)
|
answers := make(chan map[string]any, 16)
|
||||||
go func() {
|
go func() {
|
||||||
@@ -1574,7 +1577,7 @@ func firstViewsBeforeAnyNewFormState(t *testing.T, b *bed, granted map[string][]
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
t.Cleanup(nc.Close)
|
t.Cleanup(nc.Close)
|
||||||
sub, err := nc.Subscribe("mesh.mod."+placement.Module+".call.*.*", func(msg *nats.Msg) {
|
sub, err := nc.Subscribe("mesh.mod."+assignment.Module+".call.*.*", func(msg *nats.Msg) {
|
||||||
one <- struct{}{}
|
one <- struct{}{}
|
||||||
defer func() { <-one }()
|
defer func() { <-one }()
|
||||||
tokens := strings.Split(msg.Subject, ".")
|
tokens := strings.Split(msg.Subject, ".")
|
||||||
@@ -1606,7 +1609,7 @@ func firstViewsBeforeAnyNewFormState(t *testing.T, b *bed, granted map[string][]
|
|||||||
ask := func(user, tool string) (views.Answer, error) {
|
ask := func(user, tool string) (views.Answer, error) {
|
||||||
return views.AskVia(func(key string, body any) (json.RawMessage, error) {
|
return views.AskVia(func(key string, body any) (json.RawMessage, error) {
|
||||||
data, _ := json.Marshal(body)
|
data, _ := json.Marshal(body)
|
||||||
msg, err := nc.Request("mesh.mod."+placement.Module+".call."+tool+"."+strings.ReplaceAll(user, ".", "~"), data, 5*time.Second)
|
msg, err := nc.Request("mesh.mod."+assignment.Module+".call."+tool+"."+strings.ReplaceAll(user, ".", "~"), data, 5*time.Second)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -1622,7 +1625,7 @@ func firstViewsBeforeAnyNewFormState(t *testing.T, b *bed, granted map[string][]
|
|||||||
t.Errorf("%s's first view of %s before any new-form state: %v", user, tool, err)
|
t.Errorf("%s's first view of %s before any new-form state: %v", user, tool, err)
|
||||||
case slices.Contains(allowed, user):
|
case slices.Contains(allowed, user):
|
||||||
keys, _ := b.buckets[viewedStates[tool]].Keys(b.ctx)
|
keys, _ := b.buckets[viewedStates[tool]].Keys(b.ctx)
|
||||||
if a.Owner != placement.Module || a.State != tool || len(a.Values) != len(keys) {
|
if a.Owner != assignment.Module || a.State != tool || len(a.Values) != len(keys) {
|
||||||
t.Errorf("%s's first view of %s = %+v, the state holds %v", user, tool, a, keys)
|
t.Errorf("%s's first view of %s = %+v, the state holds %v", user, tool, a, keys)
|
||||||
}
|
}
|
||||||
case err == nil || !strings.Contains(err.Error(), "refused"):
|
case err == nil || !strings.Contains(err.Error(), "refused"):
|
||||||
@@ -1632,7 +1635,7 @@ func firstViewsBeforeAnyNewFormState(t *testing.T, b *bed, granted map[string][]
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// changeIn reads one entry of a placement-changed event's nodes.
|
// changeIn reads one entry of assignments-changed event's nodes.
|
||||||
func changeIn(body map[string]any) plc.Change {
|
func changeIn(body map[string]any) plc.Change {
|
||||||
raw, _ := json.Marshal(body)
|
raw, _ := json.Marshal(body)
|
||||||
var c plc.Change
|
var c plc.Change
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
"mesh-delivery-assigner/internal/assignment"
|
||||||
"mesh-delivery-assigner/internal/buckets"
|
"mesh-delivery-assigner/internal/buckets"
|
||||||
"mesh-delivery-assigner/internal/placement"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"slices"
|
"slices"
|
||||||
@@ -23,10 +23,10 @@ import (
|
|||||||
"github.com/nats-io/nats.go"
|
"github.com/nats-io/nats.go"
|
||||||
"github.com/nats-io/nats.go/jetstream"
|
"github.com/nats-io/nats.go/jetstream"
|
||||||
|
|
||||||
|
mma "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||||
mcc "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient"
|
mcc "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient"
|
||||||
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
|
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
|
||||||
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
|
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
|
||||||
mma "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
|
||||||
"git.novox.be/novox/mesh-sdk/go/announce"
|
"git.novox.be/novox/mesh-sdk/go/announce"
|
||||||
"git.novox.be/novox/mesh-sdk/go/process"
|
"git.novox.be/novox/mesh-sdk/go/process"
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ import (
|
|||||||
"git.novox.be/novox/mesh-sdk/go/views"
|
"git.novox.be/novox/mesh-sdk/go/views"
|
||||||
|
|
||||||
"mesh-delivery-assigner/internal/assign"
|
"mesh-delivery-assigner/internal/assign"
|
||||||
plc "mesh-delivery-assigner/internal/placement"
|
plc "mesh-delivery-assigner/internal/assignment"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -75,29 +75,29 @@ var readFamilies = []string{buckets.ComposedRead}
|
|||||||
// ownBuckets are the assigner's own states, as its manifest declares them: the seat mesh-module-assigner's three among them
|
// ownBuckets are the assigner's own states, as its manifest declares them: the seat mesh-module-assigner's three among them
|
||||||
// (ADR 0567 §1), named after the assigner as every module state is.
|
// (ADR 0567 §1), named after the assigner as every module state is.
|
||||||
var ownBuckets = []string{buckets.Asks, buckets.Differences,
|
var ownBuckets = []string{buckets.Asks, buckets.Differences,
|
||||||
buckets.Puts, buckets.Wants, plc.PlacementBucket, plc.ProposalsBucket,
|
buckets.Puts, buckets.Wants, plc.AssignmentsBucket, plc.ProposalsBucket,
|
||||||
plc.CandidatesBucket, plc.PulsesBucket, assign.FindingsBucket, assign.PoliciesBucket, plc.SeatHoldersBucket,
|
plc.CandidatesBucket, plc.PulsesBucket, assign.FindingsBucket, assign.PoliciesBucket, plc.SeatHoldersBucket,
|
||||||
engineReadsBucket}
|
engineReadsBucket, retiredBucket}
|
||||||
|
|
||||||
// engineReadsState is the assigner's state of what each node's engine states it reads (ADR 0618 §3), one key `<node>`, as
|
// engineReadsState is the assigner's state of what each node's engine states it reads (ADR 0618 §3), one key `<node>`, as
|
||||||
// the verb manifest-reads last answered it or the fallback read it: what assigner_engine_reads lists and asks again.
|
// the verb manifest-reads last answered it or the fallback read it: what assigner_engine_reads lists and asks again.
|
||||||
const engineReadsState = "engine-reads"
|
const engineReadsState = "engine-reads"
|
||||||
|
|
||||||
// engineReadsBucket is that state's bucket.
|
// engineReadsBucket is that state's bucket.
|
||||||
const engineReadsBucket = placement.Module + "_" + engineReadsState
|
const engineReadsBucket = assignment.Module + "_" + engineReadsState
|
||||||
|
|
||||||
// seatBuckets are the seat's states, new with it: until the controller has made them from the manifest and the bus grants
|
// seatBuckets are the seat's states, new with it: until the controller has made them from the manifest and the bus grants
|
||||||
// them, the assigner starts without them and binds each once the bus says it was created (issue 1790's kind).
|
// them, the assigner starts without them and binds each once the bus says it was created (issue 1790's kind).
|
||||||
var seatBuckets = []string{plc.PlacementBucket, plc.ProposalsBucket, plc.CandidatesBucket,
|
var seatBuckets = []string{plc.AssignmentsBucket, plc.ProposalsBucket, plc.CandidatesBucket,
|
||||||
plc.PulsesBucket}
|
plc.PulsesBucket}
|
||||||
|
|
||||||
// keptStates is what the assigner keeps and who may read it (ADR 0495 §2-§3): its wants by the watchman and the view
|
// keptStates is what the assigner keeps and who may read it (ADR 0495 §2-§3): its wants by the watchman and the view
|
||||||
// (and the node agent from release 6), its differences by the view, its asks by the view
|
// (and the node agent from release 6), its differences by the view, its asks by the view
|
||||||
// and the teller, its puts by whoever follows one (ADR 0546), and the seat mesh-module-assigner's placement, proposals and
|
// and the teller, its puts by whoever follows one (ADR 0546), and the seat mesh-module-assigner's assignments, proposals and
|
||||||
// candidates by the node agents, the grants part, mesh-config and the view (ADR 0567 §9), and its placement by
|
// candidates by the node agents, the grants part, mesh-config and the view (ADR 0567 §9), and its assignments by
|
||||||
// the builder, the checker and the indexer, through mesh-sdk's toolchain gate (issue 774, ADR 0549 §3).
|
// the builder, the checker and the indexer, through mesh-sdk's toolchain gate (issue 774, ADR 0549 §3).
|
||||||
var keptStates = []announce.State{
|
var keptStates = []announce.State{
|
||||||
{Name: mma.StatePlacement, Readable: true},
|
{Name: mma.StateAssignments, Readable: true},
|
||||||
{Name: mma.StateProposals, Readable: true},
|
{Name: mma.StateProposals, Readable: true},
|
||||||
{Name: mma.StateCandidates, Readable: true},
|
{Name: mma.StateCandidates, Readable: true},
|
||||||
{Name: mma.StatePulses, Readable: true},
|
{Name: mma.StatePulses, Readable: true},
|
||||||
@@ -111,6 +111,7 @@ var keptStates = []announce.State{
|
|||||||
{Name: views.MarksState, Readable: true}, // what its views family said each state up to (views.go)
|
{Name: views.MarksState, Readable: true}, // what its views family said each state up to (views.go)
|
||||||
{Name: views.StatusState, Readable: true}, // its first views of the indexer's states (views.go)
|
{Name: views.StatusState, Readable: true}, // its first views of the indexer's states (views.go)
|
||||||
{Name: engineReadsState, Readable: true},
|
{Name: engineReadsState, Readable: true},
|
||||||
|
{Name: retiredState}, // read only by the copy into assignments (migrate.go)
|
||||||
}
|
}
|
||||||
|
|
||||||
// kvSet is every bucket the assigner opened, by name.
|
// kvSet is every bucket the assigner opened, by name.
|
||||||
@@ -129,7 +130,7 @@ func open(ctx context.Context, js process.BucketOpener, logf func(string, ...any
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if slices.Contains(seatBuckets, name) && err != nil {
|
if slices.Contains(seatBuckets, name) && err != nil {
|
||||||
// The seat's states come in the same delivery as this assigner (ADR 0567 §1): without placement no module is placed.
|
// The seat's states come in the same delivery as this assigner (ADR 0567 §1): without assignments no module is placed.
|
||||||
logf("the bucket %s cannot be opened (%v): it is bound once the bus says it was made", name, err)
|
logf("the bucket %s cannot be opened (%v): it is bound once the bus says it was made", name, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -169,6 +170,12 @@ func open(ctx context.Context, js process.BucketOpener, logf func(string, ...any
|
|||||||
logf("the bucket %s cannot be opened (%v): what each node's engine reads is kept in memory only until it starts again", name, err)
|
logf("the bucket %s cannot be opened (%v): what each node's engine reads is kept in memory only until it starts again", name, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if name == retiredBucket && err != nil {
|
||||||
|
// The retired state is read only for the copy into `assignments` (migrate.go): a mesh made after the switch has
|
||||||
|
// none, and nothing is copied.
|
||||||
|
logf("the bucket %s cannot be opened (%v): nothing is copied from it", name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
if name == buckets.Grants && err != nil {
|
if name == buckets.Grants && err != nil {
|
||||||
// The grants part's grants (ADR 0571 clause 9): without them no module counts as granted until it is bound.
|
// The grants part's grants (ADR 0571 clause 9): without them no module counts as granted until it is bound.
|
||||||
logf("the bucket %s cannot be opened (%v): no grant is read until it is bound once the bus says it was made", name, err)
|
logf("the bucket %s cannot be opened (%v): no grant is read until it is bound once the bus says it was made", name, err)
|
||||||
@@ -187,7 +194,7 @@ func open(ctx context.Context, js process.BucketOpener, logf func(string, ...any
|
|||||||
func startsWithout(name string) bool {
|
func startsWithout(name string) bool {
|
||||||
switch name {
|
switch name {
|
||||||
case plc.SeatHoldersBucket, buckets.Provisions, assign.PoliciesBucket, assign.FindingsBucket,
|
case plc.SeatHoldersBucket, buckets.Provisions, assign.PoliciesBucket, assign.FindingsBucket,
|
||||||
buckets.Puts, buckets.Source, buckets.Merges, engineReadsBucket:
|
buckets.Puts, buckets.Source, buckets.Merges, engineReadsBucket, retiredBucket:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return slices.Contains(seatBuckets, name) || name == buckets.Grants
|
return slices.Contains(seatBuckets, name) || name == buckets.Grants
|
||||||
@@ -271,7 +278,7 @@ var watched = []string{
|
|||||||
buckets.Grants,
|
buckets.Grants,
|
||||||
buckets.Answers,
|
buckets.Answers,
|
||||||
buckets.Builds,
|
buckets.Builds,
|
||||||
plc.PlacementBucket,
|
plc.AssignmentsBucket,
|
||||||
plc.ProposalsBucket,
|
plc.ProposalsBucket,
|
||||||
plc.CandidatesBucket,
|
plc.CandidatesBucket,
|
||||||
plc.PulsesBucket,
|
plc.PulsesBucket,
|
||||||
@@ -419,11 +426,11 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber,
|
|||||||
name string
|
name string
|
||||||
kv jetstream.KeyValue
|
kv jetstream.KeyValue
|
||||||
}
|
}
|
||||||
placementAppeared := make(chan appeared, len(seatBuckets)+2)
|
assignmentsAppeared := make(chan appeared, len(seatBuckets)+2)
|
||||||
for _, name := range seatBuckets {
|
for _, name := range seatBuckets {
|
||||||
if kvs[name] == nil && js != nil {
|
if kvs[name] == nil && js != nil {
|
||||||
process.AwaitBucket(ctx, js, nc, name, "the seat mesh-module-assigner waits for it",
|
process.AwaitBucket(ctx, js, nc, name, "the seat mesh-module-assigner waits for it",
|
||||||
logger.Printf).Ready(func(kv jetstream.KeyValue) { placementAppeared <- appeared{name, kv} })
|
logger.Printf).Ready(func(kv jetstream.KeyValue) { assignmentsAppeared <- appeared{name, kv} })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// And the two that choose where grants are read from, with the same delivery as the grants part that writes one.
|
// And the two that choose where grants are read from, with the same delivery as the grants part that writes one.
|
||||||
@@ -431,16 +438,20 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber,
|
|||||||
if kvs[name] == nil && js != nil {
|
if kvs[name] == nil && js != nil {
|
||||||
name := name
|
name := name
|
||||||
process.AwaitBucket(ctx, js, nc, name, "until then the bucket is not read",
|
process.AwaitBucket(ctx, js, nc, name, "until then the bucket is not read",
|
||||||
logger.Printf).Ready(func(kv jetstream.KeyValue) { placementAppeared <- appeared{name, kv} })
|
logger.Printf).Ready(func(kv jetstream.KeyValue) { assignmentsAppeared <- appeared{name, kv} })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
takePlacement := func(a appeared) error {
|
takeAssignments := func(a appeared) error {
|
||||||
if kvs[a.name] != nil {
|
if kvs[a.name] != nil {
|
||||||
return nil // bound already, by the other of its two events
|
return nil // bound already, by the other of its two events
|
||||||
}
|
}
|
||||||
kvs[a.name] = a.kv
|
kvs[a.name] = a.kv
|
||||||
logger.Printf("the bucket %s is open and watched", a.name)
|
logger.Printf("the bucket %s is open and watched", a.name)
|
||||||
return watch(a.name)
|
if err := watch(a.name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
wake() // a state made empty brings no change of its own: the pass copies the retired state into it (migrate.go)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
for range started {
|
for range started {
|
||||||
select {
|
select {
|
||||||
@@ -476,8 +487,8 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber,
|
|||||||
case err := <-errs:
|
case err := <-errs:
|
||||||
return err
|
return err
|
||||||
case <-settled:
|
case <-settled:
|
||||||
case kv := <-placementAppeared:
|
case kv := <-assignmentsAppeared:
|
||||||
if err := takePlacement(kv); err != nil {
|
if err := takeAssignments(kv); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case <-dirty:
|
case <-dirty:
|
||||||
@@ -491,7 +502,7 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber,
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if kv, err := js.KeyValue(ctx, name); err == nil {
|
if kv, err := js.KeyValue(ctx, name); err == nil {
|
||||||
if err := takePlacement(appeared{name, kv}); err != nil {
|
if err := takeAssignments(appeared{name, kv}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
@@ -511,7 +522,7 @@ var PutsReopenEvery = time.Minute
|
|||||||
// (ADR 0437 clause 9).
|
// (ADR 0437 clause 9).
|
||||||
func sayVia(ctx context.Context, js jetstream.JetStream) func(oc.Condition) error {
|
func sayVia(ctx context.Context, js jetstream.JetStream) func(oc.Condition) error {
|
||||||
return func(c oc.Condition) error {
|
return func(c oc.Condition) error {
|
||||||
subject, err := occ.ConditionSubject(placement.Module)
|
subject, err := occ.ConditionSubject(assignment.Module)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -528,7 +539,7 @@ func sayVia(ctx context.Context, js jetstream.JetStream) func(oc.Condition) erro
|
|||||||
// consumer on EVENTS, and asks the node it names again; the hub also hands the indexer's views to the followers of
|
// consumer on EVENTS, and asks the node it names again; the hub also hands the indexer's views to the followers of
|
||||||
// indexerViews, and starts once both are registered.
|
// indexerViews, and starts once both are registered.
|
||||||
func hearReadsChanged(ctx context.Context, js jetstream.JetStream, hub *process.EventHub, reads *enginereads.Keeper, findings assign.Findings, logf func(string, ...any)) {
|
func hearReadsChanged(ctx context.Context, js jetstream.JetStream, hub *process.EventHub, reads *enginereads.Keeper, findings assign.Findings, logf func(string, ...any)) {
|
||||||
account := process.Account{Node: thisNode, Module: placement.Module}
|
account := process.Account{Node: thisNode, Module: assignment.Module}
|
||||||
w := notHeardWriter(findings, logf)
|
w := notHeardWriter(findings, logf)
|
||||||
handle := func(m jetstream.Msg) {
|
handle := func(m jetstream.Msg) {
|
||||||
if err := m.Ack(); err != nil {
|
if err := m.Ack(); err != nil {
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"mesh-delivery-assigner/internal/assign"
|
"mesh-delivery-assigner/internal/assign"
|
||||||
|
plc "mesh-delivery-assigner/internal/assignment"
|
||||||
"mesh-delivery-assigner/internal/buckets"
|
"mesh-delivery-assigner/internal/buckets"
|
||||||
plc "mesh-delivery-assigner/internal/placement"
|
|
||||||
|
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
@@ -20,9 +20,9 @@ import (
|
|||||||
|
|
||||||
"github.com/nats-io/nats.go/jetstream"
|
"github.com/nats-io/nats.go/jetstream"
|
||||||
|
|
||||||
|
mmac "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
|
||||||
|
mma "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||||
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
|
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
|
||||||
mmac "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
|
||||||
mma "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
|
||||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||||
"git.novox.be/novox/mesh-sdk/go/process"
|
"git.novox.be/novox/mesh-sdk/go/process"
|
||||||
)
|
)
|
||||||
@@ -112,12 +112,12 @@ func TestOpensWhatItsManifestNames(t *testing.T) {
|
|||||||
for _, name := range append(append([]string{}, readBuckets...), ownBuckets...) {
|
for _, name := range append(append([]string{}, readBuckets...), ownBuckets...) {
|
||||||
kvs[name] = &watchKV{memKV: &memKV{values: map[string][]byte{}}}
|
kvs[name] = &watchKV{memKV: &memKV{values: map[string][]byte{}}}
|
||||||
}
|
}
|
||||||
placed, err := mmac.PlacementValues(mma.Placement{Node: "ace"})
|
placed, err := mmac.AssignmentsValues(mma.Assignments{Node: "ace"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
for key, raw := range placed {
|
for key, raw := range placed {
|
||||||
kv := kvs[plc.PlacementBucket].(*watchKV)
|
kv := kvs[plc.AssignmentsBucket].(*watchKV)
|
||||||
kv.entries = append(kv.entries, kvEntry{key: key, value: raw})
|
kv.entries = append(kv.entries, kvEntry{key: key, value: raw})
|
||||||
}
|
}
|
||||||
kvs[buckets.Puts].(*watchKV).closed = true
|
kvs[buckets.Puts].(*watchKV).closed = true
|
||||||
@@ -192,3 +192,8 @@ func (e kvEntry) Value() []byte { return e.value }
|
|||||||
func (e kvEntry) Operation() jetstream.KeyValueOp { return jetstream.KeyValuePut }
|
func (e kvEntry) Operation() jetstream.KeyValueOp { return jetstream.KeyValuePut }
|
||||||
func (e kvEntry) Revision() uint64 { return 1 }
|
func (e kvEntry) Revision() uint64 { return 1 }
|
||||||
func (e kvEntry) Created() time.Time { return time.Time{} }
|
func (e kvEntry) Created() time.Time { return time.Time{} }
|
||||||
|
|
||||||
|
// Keys lists nothing: the retired state this watch stands for holds no key.
|
||||||
|
func (w *watchKV) Keys(context.Context, ...jetstream.WatchOpt) ([]string, error) {
|
||||||
|
return nil, jetstream.ErrNoKeysFound
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/nats-io/nats.go/jetstream"
|
||||||
|
|
||||||
|
plc "mesh-delivery-assigner/internal/assignment"
|
||||||
|
|
||||||
|
mmac "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The state this seat's assignments were kept in before ADR 0641 gave it its name. The assigner reads it here and nowhere
|
||||||
|
// else: at its start it copies each key into `assignments` (copyRetired) and keeps no reader of it afterwards. The
|
||||||
|
// manifest still declares the state, so the assigner's account may read it for the copy; a later release drops it.
|
||||||
|
const (
|
||||||
|
retiredState = "placement"
|
||||||
|
retiredBucket = plc.Module + "_" + retiredState
|
||||||
|
)
|
||||||
|
|
||||||
|
// CopiedEvent is the module's event saying the copy was made, once: mesh.mod.mesh-delivery-assigner.event.assignments-copied.
|
||||||
|
const CopiedEvent = "assignments-copied"
|
||||||
|
|
||||||
|
// copiedSaid is what the event carries: the keys copied, those already in `assignments` (kept as they are), and the keys of
|
||||||
|
// the form `placement.<node>` that were dropped (their node key holds the value).
|
||||||
|
type copiedSaid struct {
|
||||||
|
Copied int `json:"copied"`
|
||||||
|
Present int `json:"present"`
|
||||||
|
Dropped int `json:"dropped"`
|
||||||
|
Skipped int `json:"skipped"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// copyRetired is the holder's switch from the retired state to `assignments`, with no gap: for each key of the retired state
|
||||||
|
// in the form `<node>.node` or `<node>.<module>` it copies the raw value into `assignments` when the key is missing there,
|
||||||
|
// the module keys first and the node keys, which commit a node, last. A key copied or already there is then deleted from the
|
||||||
|
// retired state, so a restart never brings back what `assignments` has dropped since. The keys of the form
|
||||||
|
// `placement.<node>` are dropped, and a key that fits no form is left and counted. It answers whether the seat may go on: a
|
||||||
|
// copy that failed is tried again at the next pass.
|
||||||
|
func (p *passer) copyRetired(ctx context.Context) bool {
|
||||||
|
if p.copied {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
old, next := p.kvs[retiredBucket], p.kvs[plc.AssignmentsBucket]
|
||||||
|
if next == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if old == nil {
|
||||||
|
p.logf("assignments: the retired state %s cannot be read, so nothing is copied from it", retiredBucket)
|
||||||
|
p.copied = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
names, err := old.Keys(ctx)
|
||||||
|
if err != nil && !errors.Is(err, jetstream.ErrNoKeysFound) {
|
||||||
|
p.logf("assignments: the keys of %s cannot be listed, and are tried again at the next pass: %v", retiredBucket, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// A node's module keys before its node key: the node key is the commit the readers wait for.
|
||||||
|
slices.SortStableFunc(names, func(a, b string) int {
|
||||||
|
return strings.Compare(sortForm(a), sortForm(b))
|
||||||
|
})
|
||||||
|
said := copiedSaid{}
|
||||||
|
for _, key := range names {
|
||||||
|
_, _, kind, perr := mmac.ParseKey(key)
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(key, retiredState+"."):
|
||||||
|
// The node named placement could not exist before, so this is the form that held a whole node in one key.
|
||||||
|
said.Dropped++
|
||||||
|
case perr != nil || (kind != mmac.NodeKind && kind != mmac.ModuleKind):
|
||||||
|
said.Skipped++
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
entry, err := old.Get(ctx, key)
|
||||||
|
if errors.Is(err, jetstream.ErrKeyNotFound) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
p.logf("assignments: %s cannot be read, and the copy is tried again at the next pass: %v", key, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if entry.Operation() != jetstream.KeyValuePut {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rev, err := next.Create(ctx, key, entry.Value())
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, jetstream.ErrKeyExists):
|
||||||
|
said.Present++
|
||||||
|
case err != nil:
|
||||||
|
p.logf("assignments: %s is not copied, and the copy is tried again at the next pass: %v", key, err)
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
said.Copied++
|
||||||
|
p.w.putEntry(plc.AssignmentsBucket, key, entry.Value(), rev, p.clock())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := old.Delete(ctx, key); err != nil {
|
||||||
|
p.logf("assignments: %s is not deleted from %s after its copy, and the copy is tried again at the next pass: %v", key, retiredBucket, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.copied = true
|
||||||
|
if said == (copiedSaid{}) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
p.logf("assignments: copied %d keys of %s into %s (%d already there kept as they are, %d of the form placement.<node> dropped, %d fitting no form left)",
|
||||||
|
said.Copied, retiredBucket, plc.AssignmentsBucket, said.Present, said.Dropped, said.Skipped)
|
||||||
|
if said.Copied+said.Present+said.Dropped == 0 {
|
||||||
|
return true // only keys left behind: the line above says them at each start, and the event was said with the copy
|
||||||
|
}
|
||||||
|
if p.seat != nil {
|
||||||
|
if err := p.seat.bus.Emit(CopiedEvent, said); err != nil {
|
||||||
|
p.logf("assignments: the copy is not said as an event: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// sortForm orders `<node>.<module>` keys before `<node>.node`.
|
||||||
|
func sortForm(key string) string {
|
||||||
|
if node, ok := strings.CutSuffix(key, ".node"); ok {
|
||||||
|
return node + "\x7f"
|
||||||
|
}
|
||||||
|
return key
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||||
mwc "git.novox.be/novox/mesh-contracts/placement/go/clients/meshwantsclient"
|
mwc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshwantsclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-assigner/asks"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-assigner/asks"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-grants/grantsstate"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-grants/grantsstate"
|
||||||
@@ -26,7 +26,7 @@ import (
|
|||||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient/enginereads"
|
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient/enginereads"
|
||||||
|
|
||||||
"mesh-delivery-assigner/internal/assign"
|
"mesh-delivery-assigner/internal/assign"
|
||||||
plc "mesh-delivery-assigner/internal/placement"
|
plc "mesh-delivery-assigner/internal/assignment"
|
||||||
)
|
)
|
||||||
|
|
||||||
// passer makes the assigner's passes: from what the world holds now, it writes each key whose value differs from the
|
// passer makes the assigner's passes: from what the world holds now, it writes each key whose value differs from the
|
||||||
@@ -74,8 +74,8 @@ type passer struct {
|
|||||||
seat *seatActs
|
seat *seatActs
|
||||||
publish publisher
|
publish publisher
|
||||||
saidProposed map[string]bool
|
saidProposed map[string]bool
|
||||||
// convertedAll says every node in the form before was converted to its node and module keys (convertPlacement).
|
// copied says the retired state was copied into `assignments` by this process, or found empty (copyRetired).
|
||||||
convertedAll bool
|
copied bool
|
||||||
// saidWaits is the last said of the seat's wait for its states (sayWaits).
|
// saidWaits is the last said of the seat's wait for its states (sayWaits).
|
||||||
saidWaits string
|
saidWaits string
|
||||||
intended map[string]map[string]map[string]bool
|
intended map[string]map[string]map[string]bool
|
||||||
@@ -123,7 +123,7 @@ func (p *passer) clock() time.Time {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *passer) pass(ctx context.Context) {
|
func (p *passer) pass(ctx context.Context) {
|
||||||
p.placementSeat(ctx)
|
p.assignmentsSeat(ctx)
|
||||||
p.wants(ctx)
|
p.wants(ctx)
|
||||||
p.puts(ctx)
|
p.puts(ctx)
|
||||||
p.nodeWants(ctx)
|
p.nodeWants(ctx)
|
||||||
@@ -1071,7 +1071,7 @@ func (p *passer) provisionsMissing(ctx context.Context, missing map[string]bool)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// unplacedFindings writes, from the lease write, the finding `machine.<node>.unplaced` for each node that runs but no
|
// unplacedFindings writes, from the lease write, the finding `machine.<node>.unplaced` for each node that runs but no
|
||||||
// placement names, which gets no want (ADR 0571 §6), and deletes it once the node is placed.
|
// assignments names, which gets no want (ADR 0571 §6), and deletes it once the node is placed.
|
||||||
func (p *passer) unplacedFindings() {
|
func (p *passer) unplacedFindings() {
|
||||||
if p.findings.Put == nil {
|
if p.findings.Put == nil {
|
||||||
return
|
return
|
||||||
@@ -1106,20 +1106,20 @@ func (p *passer) unplacedFindings() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// seatHolders writes the seat mesh-module-assigner's state seat-holders from every node's placement (ADR 0571 §4): one key per
|
// seatHolders writes the seat mesh-module-assigner's state seat-holders from every node's assignments (ADR 0571 §4): one key per
|
||||||
// seat held once, `<seat>`, and one per node of a node seat, `<seat>.<node>`, written only where it differs, and a key no
|
// seat held once, `<seat>`, and one per node of a node seat, `<seat>.<node>`, written only where it differs, and a key no
|
||||||
// placement holds any more deleted. A pass follows each placement change and each manifest the indexer takes in, so no
|
// assignments holds any more deleted. A pass follows each assignments change and each manifest the indexer takes in, so no
|
||||||
// timer writes it.
|
// timer writes it.
|
||||||
func (p *passer) seatHolders(ctx context.Context) {
|
func (p *passer) seatHolders(ctx context.Context) {
|
||||||
kv := p.kvs[plc.SeatHoldersBucket]
|
kv := p.kvs[plc.SeatHoldersBucket]
|
||||||
if kv == nil {
|
if kv == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
placements := p.w.placements()
|
assignments := p.w.assignments()
|
||||||
if len(placements) == 0 {
|
if len(assignments) == 0 {
|
||||||
return // no placement read yet: nothing is known of who holds what, and nothing is deleted
|
return // no assignments read yet: nothing is known of who holds what, and nothing is deleted
|
||||||
}
|
}
|
||||||
want := plc.SeatHoldersOf(placements)
|
want := plc.SeatHoldersOf(assignments)
|
||||||
have := p.w.read(plc.SeatHoldersBucket)
|
have := p.w.read(plc.SeatHoldersBucket)
|
||||||
for key, sh := range want {
|
for key, sh := range want {
|
||||||
value, err := json.Marshal(sh)
|
value, err := json.Marshal(sh)
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ import (
|
|||||||
|
|
||||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||||
wdc "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshwatchdogsclient"
|
wdc "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshwatchdogsclient"
|
||||||
mwc "git.novox.be/novox/mesh-contracts/placement/go/clients/meshwantsclient"
|
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||||
mdc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
mdc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||||
|
mwc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshwantsclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||||
mdseat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
|
mdseat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-assigner/asks"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-assigner/asks"
|
||||||
@@ -26,10 +26,10 @@ import (
|
|||||||
|
|
||||||
"github.com/nats-io/nats.go/jetstream"
|
"github.com/nats-io/nats.go/jetstream"
|
||||||
|
|
||||||
|
mmac "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
|
||||||
|
mma "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||||
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
|
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
|
||||||
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
|
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
|
||||||
mmac "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
|
||||||
mma "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
|
||||||
"git.novox.be/novox/mesh-sdk/go/busretry"
|
"git.novox.be/novox/mesh-sdk/go/busretry"
|
||||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ import (
|
|||||||
"git.novox.be/novox/mesh-delivery/sdk/graph"
|
"git.novox.be/novox/mesh-delivery/sdk/graph"
|
||||||
|
|
||||||
"mesh-delivery-assigner/internal/assign"
|
"mesh-delivery-assigner/internal/assign"
|
||||||
plc "mesh-delivery-assigner/internal/placement"
|
plc "mesh-delivery-assigner/internal/assignment"
|
||||||
)
|
)
|
||||||
|
|
||||||
// memKV is the part of a bucket a pass writes: puts and deletes.
|
// memKV is the part of a bucket a pass writes: puts and deletes.
|
||||||
@@ -70,20 +70,20 @@ func newPasser(kvs kvSet, at time.Time) *passer {
|
|||||||
now: func() time.Time { return at }}
|
now: func() time.Time { return at }}
|
||||||
}
|
}
|
||||||
|
|
||||||
// placeOn puts the seat's placement of node, naming the modules, into the world: one node key and a key per module
|
// placeOn puts the seat's assignments of node, naming the modules, into the world: one node key and a key per module
|
||||||
// (research 072 row 37).
|
// (research 072 row 37).
|
||||||
func placeOn(t *testing.T, w *world, node string, modules ...string) {
|
func placeOn(t *testing.T, w *world, node string, modules ...string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
pl := mma.Placement{Node: node}
|
pl := mma.Assignments{Node: node}
|
||||||
for _, m := range modules {
|
for _, m := range modules {
|
||||||
pl.Modules = append(pl.Modules, mma.PlacedModule{Module: m})
|
pl.Modules = append(pl.Modules, mma.PlacedModule{Module: m})
|
||||||
}
|
}
|
||||||
values, err := mmac.PlacementValues(pl)
|
values, err := mmac.AssignmentsValues(pl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
for key, raw := range values {
|
for key, raw := range values {
|
||||||
w.put(plc.PlacementBucket, key, raw)
|
w.put(plc.AssignmentsBucket, key, raw)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -491,7 +491,7 @@ func putOfTheBus(t *testing.T) {
|
|||||||
// the journal, so it is loud with no operator channel.
|
// the journal, so it is loud with no operator channel.
|
||||||
func TestAModuleOnNeitherListIsRaisedOnceAndClearedOnce(t *testing.T) {
|
func TestAModuleOnNeitherListIsRaisedOnceAndClearedOnce(t *testing.T) {
|
||||||
// A pair only one source names is a condition of the same kind (issue 1126): folded here to hold the count.
|
// A pair only one source names is a condition of the same kind (issue 1126): folded here to hold the count.
|
||||||
aPlacementAloneNamesWhereModulesRun(t)
|
aAssignmentsAloneNamesWhereModulesRun(t)
|
||||||
// The verb edges a want waits on come from the graph and what runs (ADR 0614 §2, §3): folded here to hold the count.
|
// The verb edges a want waits on come from the graph and what runs (ADR 0614 §2, §3): folded here to hold the count.
|
||||||
aWantWaitsOnTheVerbEdgesTheGraphKeeps(t)
|
aWantWaitsOnTheVerbEdgesTheGraphKeeps(t)
|
||||||
t.Run("gathers", testThePassGathersTheModulesOnNeitherList)
|
t.Run("gathers", testThePassGathersTheModulesOnNeitherList)
|
||||||
@@ -972,18 +972,18 @@ func aReleasesApproveAnswersTheMoveOfItsBuildAlone(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 774/S26/AC1 (ADR 0549 §5, ADR 0567 §8): the seat's placement alone names where modules run: no controller state is read, so a
|
// 774/S26/AC1 (ADR 0549 §5, ADR 0567 §8): the seat's assignments alone names where modules run: no controller state is read, so a
|
||||||
// module in an assignment only is no module of its node, and before any placement is read nothing is placed.
|
// module in an assignment only is no module of its node, and before any assignments is read nothing is placed.
|
||||||
func aPlacementAloneNamesWhereModulesRun(t *testing.T) {
|
func aAssignmentsAloneNamesWhereModulesRun(t *testing.T) {
|
||||||
p := newPasser(kvSet{}, time.Date(2026, 10, 26, 18, 0, 0, 0, time.UTC))
|
p := newPasser(kvSet{}, time.Date(2026, 10, 26, 18, 0, 0, 0, time.UTC))
|
||||||
p.w.put("mesh-controller_assignments", "g14.b", []byte(`{"node":"g14","module":"b","build":"b-1"}`)) // read no more
|
p.w.put("mesh-controller_assignments", "g14.b", []byte(`{"node":"g14","module":"b","build":"b-1"}`)) // read no more
|
||||||
if got := p.w.assigned(); len(got) != 0 {
|
if got := p.w.assigned(); len(got) != 0 {
|
||||||
t.Fatalf("before any placement: assigned %v", got)
|
t.Fatalf("before any assignments: assigned %v", got)
|
||||||
}
|
}
|
||||||
placeOn(t, p.w, "g14", "c", "a")
|
placeOn(t, p.w, "g14", "c", "a")
|
||||||
placeOn(t, p.w, "n2", "d")
|
placeOn(t, p.w, "n2", "d")
|
||||||
if got := p.w.assigned(); !slices.Equal(got["g14"], []string{"a", "c"}) || !slices.Equal(got["n2"], []string{"d"}) || len(got) != 2 {
|
if got := p.w.assigned(); !slices.Equal(got["g14"], []string{"a", "c"}) || !slices.Equal(got["n2"], []string{"d"}) || len(got) != 2 {
|
||||||
t.Errorf("the placement alone: assigned %v", got)
|
t.Errorf("the assignments alone: assigned %v", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1000,8 +1000,8 @@ func aPlacedNodesWantsNameTheBuildAloneAndItsYesComesFromItsHave(t *testing.T) {
|
|||||||
mod, _ := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Fp2: fpTest, Repository: "novox/web", Commit: strings.Repeat("1", 40), Path: meshcatalogclient.RootPath})
|
mod, _ := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Fp2: fpTest, Repository: "novox/web", Commit: strings.Repeat("1", 40), Path: meshcatalogclient.RootPath})
|
||||||
put(buckets.Modules, "web", mod) // the trunk's build is not done: rule 1 holds g14 at fpOld
|
put(buckets.Modules, "web", mod) // the trunk's build is not done: rule 1 holds g14 at fpOld
|
||||||
placeOn(t, p.w, "g14", "web")
|
placeOn(t, p.w, "g14", "web")
|
||||||
pl, _ := mmac.EncodePlacement(mma.Placement{Node: "g14", Modules: []mma.PlacedModule{{Module: "web"}}})
|
pl, _ := mmac.EncodeAssignments(mma.Assignments{Node: "g14", Modules: []mma.PlacedModule{{Module: "web"}}})
|
||||||
put(plc.PlacementBucket, "placement.g14", pl)
|
put(plc.AssignmentsBucket, "assignments.g14", pl)
|
||||||
on, _ := presenceValue(true)
|
on, _ := presenceValue(true)
|
||||||
put(buckets.Presence, "g14", on)
|
put(buckets.Presence, "g14", on)
|
||||||
have, _ := nodeengineclient.EncodeHave(nodeengine.HaveByNodeModuleValue{Fp: fpOld, Composed: was, Phase: nodeengineclient.PhaseRunning, Health: nodeengineclient.HealthOK})
|
have, _ := nodeengineclient.EncodeHave(nodeengine.HaveByNodeModuleValue{Fp: fpOld, Composed: was, Phase: nodeengineclient.PhaseRunning, Health: nodeengineclient.HealthOK})
|
||||||
@@ -1076,19 +1076,19 @@ func aPlacedNodesWantsNameTheBuildAloneAndItsYesComesFromItsHave(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 774/S25/AC1 (ADR 0567 §8): an approved placement change is written and followed with its warrant recorded and
|
// 774/S25/AC1 (ADR 0567 §8): an approved assignments change is written and followed with its warrant recorded and
|
||||||
// placement-changed said, and no verb of the controller is called; the refused-call conditions a previous process raised are
|
// assignments-changed said, and no verb of the controller is called; the refused-call conditions a previous process raised are
|
||||||
// cleared once, and said again at a later pass while the findings cannot be read.
|
// cleared once, and said again at a later pass while the findings cannot be read.
|
||||||
func TestAnApprovedPlacementCallsNoControllerVerb(t *testing.T) {
|
func TestAnApprovedAssignmentsCallsNoControllerVerb(t *testing.T) {
|
||||||
p := newPasser(kvSet{}, time.Date(2026, 11, 2, 9, 0, 0, 0, time.UTC))
|
p := newPasser(kvSet{}, time.Date(2026, 11, 2, 9, 0, 0, 0, time.UTC))
|
||||||
bus := &fakeBus{calls: make(chan string, 8)}
|
bus := &fakeBus{calls: make(chan string, 8)}
|
||||||
p.seat = newSeatActs(bus, func() {}, func(string, ...any) {})
|
p.seat = newSeatActs(bus, func() {}, func(string, ...any) {})
|
||||||
var said []string
|
var said []string
|
||||||
p.publish = func(event string, _ any, _ string) error { said = append(said, event); return nil }
|
p.publish = func(event string, _ any, _ string) error { said = append(said, event); return nil }
|
||||||
prop := mma.Proposal{ID: "p-one", By: "operator@laptop", Why: "move web"}
|
prop := mma.Proposal{ID: "p-one", By: "operator@laptop", Why: "move web"}
|
||||||
change := plc.ChangeOf(mma.Placement{Node: "anchor"}, mma.Placement{Node: "anchor", Modules: []mma.PlacedModule{{Module: "web"}}})
|
change := plc.ChangeOf(mma.Assignments{Node: "anchor"}, mma.Assignments{Node: "anchor", Modules: []mma.PlacedModule{{Module: "web"}}})
|
||||||
p.followWrite(prop, mdc.Answer{}, []plc.Change{change})
|
p.followWrite(prop, mdc.Answer{}, []plc.Change{change})
|
||||||
if len(bus.calls) != 0 || !slices.Equal(said, []string{mma.EventPlacementChanged}) {
|
if len(bus.calls) != 0 || !slices.Equal(said, []string{mma.EventAssignmentsChanged}) {
|
||||||
t.Fatalf("calls = %d, said = %v", len(bus.calls), said)
|
t.Fatalf("calls = %d, said = %v", len(bus.calls), said)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||||
mwc "git.novox.be/novox/mesh-contracts/placement/go/clients/meshwantsclient"
|
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||||
|
mwc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshwantsclient"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-assigner/asks"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-assigner/asks"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-grants/grantsstate"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-grants/grantsstate"
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"maps"
|
"maps"
|
||||||
|
"mesh-delivery-assigner/internal/assignment"
|
||||||
"mesh-delivery-assigner/internal/buckets"
|
"mesh-delivery-assigner/internal/buckets"
|
||||||
"mesh-delivery-assigner/internal/placement"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"slices"
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -24,24 +24,24 @@ import (
|
|||||||
"github.com/nats-io/nats.go"
|
"github.com/nats-io/nats.go"
|
||||||
"github.com/nats-io/nats.go/jetstream"
|
"github.com/nats-io/nats.go/jetstream"
|
||||||
|
|
||||||
|
mmac "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
|
||||||
|
mma "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||||
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient"
|
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient"
|
||||||
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
|
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
|
||||||
mmac "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
|
||||||
mma "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
|
||||||
"git.novox.be/novox/mesh-sdk/go/seatdef"
|
"git.novox.be/novox/mesh-sdk/go/seatdef"
|
||||||
|
|
||||||
"mesh-delivery-assigner/internal/assign"
|
"mesh-delivery-assigner/internal/assign"
|
||||||
|
plc "mesh-delivery-assigner/internal/assignment"
|
||||||
"mesh-delivery-assigner/internal/judge"
|
"mesh-delivery-assigner/internal/judge"
|
||||||
plc "mesh-delivery-assigner/internal/placement"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// The seat mesh-module-assigner's holder in the assigner's process (novox/hq ADR 0567 §2-§8): each pass follows every open
|
// The seat mesh-module-assigner's holder in the assigner's process (novox/hq ADR 0567 §2-§8): each pass follows every open
|
||||||
// proposal one step, from what the world holds. A proposal the verbs wrote is said (placement-proposed); the nodes it
|
// proposal one step, from what the world holds. A proposal the verbs wrote is said (assignments-proposed); the nodes it
|
||||||
// must hear from are found by composing every node with and without it, and those its key does not change get a reached
|
// must hear from are found by composing every node with and without it, and those its key does not change get a reached
|
||||||
// candidate; their answers are judged (placement-judged) and, on a passing verdict, the operator is asked to approve it;
|
// candidate; their answers are judged (assignments-judged) and, on a passing verdict, the operator is asked to approve it;
|
||||||
// on the warrant each changed key is written by compare-and-set at the revision it was laid over (placement-changed),
|
// on the warrant each changed key is written by compare-and-set at the revision it was laid over (assignments-changed),
|
||||||
// and the controller's assignments are kept in step through its verbs until the lease write. A key moved meanwhile
|
// and the controller's assignments are kept in step through its verbs until the lease write. A key moved meanwhile
|
||||||
// lays the proposal again; a decline, an expiry or a withdrawal ends it (placement-withdrawn). Every event is said on the
|
// lays the proposal again; a decline, an expiry or a withdrawal ends it (assignments-withdrawn). Every event is said on the
|
||||||
// assigner's own stream, so only this process says them. Nothing polls: the wake at an answer's bound is the one wake.
|
// assigner's own stream, so only this process says them. Nothing polls: the wake at an answer's bound is the one wake.
|
||||||
|
|
||||||
// publisher says one of the seat's events on the assigner's stream, under a message id the bus keeps one of.
|
// publisher says one of the seat's events on the assigner's stream, under a message id the bus keeps one of.
|
||||||
@@ -58,7 +58,7 @@ func seatPublisher(ctx context.Context, js jetstream.JetStream, node string) pub
|
|||||||
}
|
}
|
||||||
msg := nats.NewMsg(seatdef.EventSubject(mma.Seat, event))
|
msg := nats.NewMsg(seatdef.EventSubject(mma.Seat, event))
|
||||||
msg.Header.Set("x-event-id", id)
|
msg.Header.Set("x-event-id", id)
|
||||||
msg.Header.Set("x-source", placement.Module)
|
msg.Header.Set("x-source", assignment.Module)
|
||||||
msg.Header.Set("x-node", node)
|
msg.Header.Set("x-node", node)
|
||||||
msg.Header.Set("x-time", time.Now().UTC().Format(time.RFC3339Nano))
|
msg.Header.Set("x-time", time.Now().UTC().Format(time.RFC3339Nano))
|
||||||
msg.Header.Set("content-type", "application/json")
|
msg.Header.Set("content-type", "application/json")
|
||||||
@@ -69,7 +69,7 @@ func seatPublisher(ctx context.Context, js jetstream.JetStream, node string) pub
|
|||||||
}
|
}
|
||||||
|
|
||||||
// seatActs keeps the seat's acts while this process runs: the warrant recorded in the hand-act log. The seat calls no verb of
|
// seatActs keeps the seat's acts while this process runs: the warrant recorded in the hand-act log. The seat calls no verb of
|
||||||
// the controller: placement is the one source (ADR 0567 §8, issue 774).
|
// the controller: assignments is the one source (ADR 0567 §8, issue 774).
|
||||||
type seatActs struct {
|
type seatActs struct {
|
||||||
bus caller
|
bus caller
|
||||||
wake func()
|
wake func()
|
||||||
@@ -82,7 +82,7 @@ func newSeatActs(bus caller, wake func(), logf func(string, ...any)) *seatActs {
|
|||||||
return &seatActs{bus: bus, wake: wake, logf: logf, rec: meshhandactsclient.Recorder{Ask: bus.Call}}
|
return &seatActs{bus: bus, wake: wake, logf: logf, rec: meshhandactsclient.Recorder{Ask: bus.Call}}
|
||||||
}
|
}
|
||||||
|
|
||||||
// record records once, in the hand-act log, that placement was written on the operator's warrant (§5): the router's
|
// record records once, in the hand-act log, that assignments was written on the operator's warrant (§5): the router's
|
||||||
// record of the teller's ask says who chose and how (ADR 0274). The log is the seat mesh-hand-acts' from its delivery, and
|
// record of the teller's ask says who chose and how (ADR 0274). The log is the seat mesh-hand-acts' from its delivery, and
|
||||||
// the controller's while that seat has no holder (ADR 0565 §11, meshhandactsclient.Recorder).
|
// the controller's while that seat has no holder (ADR 0565 §11, meshhandactsclient.Recorder).
|
||||||
func (a *seatActs) record(warrant string) {
|
func (a *seatActs) record(warrant string) {
|
||||||
@@ -92,9 +92,9 @@ func (a *seatActs) record(warrant string) {
|
|||||||
ask, _, _ := strings.Cut(warrant, "@")
|
ask, _, _ := strings.Cut(warrant, "@")
|
||||||
go func() {
|
go func() {
|
||||||
if taken, err := a.rec.RecordWarrant(buckets.TellerModule, ask); err != nil {
|
if taken, err := a.rec.RecordWarrant(buckets.TellerModule, ask); err != nil {
|
||||||
a.logf("the placement's warrant %s is not recorded in the hand-act log: %v", ask, err)
|
a.logf("the assignments's warrant %s is not recorded in the hand-act log: %v", ask, err)
|
||||||
} else {
|
} else {
|
||||||
a.logf("the placement's warrant %s is recorded in the log of %s", ask, taken.Log)
|
a.logf("the assignments's warrant %s is recorded in the log of %s", ask, taken.Log)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
@@ -147,10 +147,9 @@ func (p *passer) sayWaits(unbound []string) {
|
|||||||
p.said[c.Key], p.saidWaits = true, state
|
p.said[c.Key], p.saidWaits = true, state
|
||||||
}
|
}
|
||||||
|
|
||||||
// seatPlacements is every node's placement and the revision of its node key (its legacy key's for a node not yet
|
// seatAssignments is every node's assignments and the revision of its node key, as the watch holds them: the revision a proposal is laid over (§2, research 072 row 37).
|
||||||
// converted), as the watch holds them: the revision a proposal is laid over (§2, research 072 row 37).
|
func (p *passer) seatAssignments() (map[string]mma.Assignments, map[string]uint64) {
|
||||||
func (p *passer) seatPlacements() (map[string]mma.Placement, map[string]uint64) {
|
return p.w.assignmentKeys.AllRevisions()
|
||||||
return p.w.placement.AllRevisions()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// candidatesOf is a proposal's candidates by node, with their keys' revisions and the newest write.
|
// candidatesOf is a proposal's candidates by node, with their keys' revisions and the newest write.
|
||||||
@@ -194,8 +193,8 @@ func (p *passer) answersTo(id string) map[string]*nodeengine.CandidateAnswer {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// placementSeat takes every open proposal one step further.
|
// assignmentsSeat takes every open proposal one step further.
|
||||||
func (p *passer) placementSeat(ctx context.Context) {
|
func (p *passer) assignmentsSeat(ctx context.Context) {
|
||||||
for _, b := range seatBuckets {
|
for _, b := range seatBuckets {
|
||||||
if p.kvs[b] == nil {
|
if p.kvs[b] == nil {
|
||||||
return
|
return
|
||||||
@@ -204,8 +203,10 @@ func (p *passer) placementSeat(ctx context.Context) {
|
|||||||
if p.saidProposed == nil {
|
if p.saidProposed == nil {
|
||||||
p.saidProposed, p.intended = map[string]bool{}, map[string]map[string]map[string]bool{}
|
p.saidProposed, p.intended = map[string]bool{}, map[string]map[string]map[string]bool{}
|
||||||
}
|
}
|
||||||
p.convertPlacement(ctx)
|
if !p.copyRetired(ctx) {
|
||||||
current, revs := p.seatPlacements()
|
return // the old state is copied whole before the seat takes a step: a half-copied state would place nothing on a node
|
||||||
|
}
|
||||||
|
current, revs := p.seatAssignments()
|
||||||
ids := make([]string, 0)
|
ids := make([]string, 0)
|
||||||
raws := p.w.read(plc.ProposalsBucket)
|
raws := p.w.read(plc.ProposalsBucket)
|
||||||
for id := range raws {
|
for id := range raws {
|
||||||
@@ -251,7 +252,7 @@ func (p *passer) deletePulses(ctx context.Context, id string) {
|
|||||||
|
|
||||||
// ask raises (or raises again) a proposal's authorising ask and its bound, and answers the proposal asked.
|
// ask raises (or raises again) a proposal's authorising ask and its bound, and answers the proposal asked.
|
||||||
func (p *passer) ask(ctx context.Context, prop mma.Proposal, akey, brief string) (mma.Proposal, bool) {
|
func (p *passer) ask(ctx context.Context, prop mma.Proposal, akey, brief string) (mma.Proposal, bool) {
|
||||||
a, err := assign.PlacementAsk(akey, prop, brief, assign.PlacementAsking(prop, p.clock()), p.clock())
|
a, err := assign.AssignmentsAsk(akey, prop, brief, assign.AssignmentsAsking(prop, p.clock()), p.clock())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.logf("the ask about the proposal %s is not written: %v", prop.ID, err)
|
p.logf("the ask about the proposal %s is not written: %v", prop.ID, err)
|
||||||
return prop, false
|
return prop, false
|
||||||
@@ -270,9 +271,9 @@ func (p *passer) ask(ctx context.Context, prop mma.Proposal, akey, brief string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// stepProposal takes one proposal one step further.
|
// stepProposal takes one proposal one step further.
|
||||||
func (p *passer) stepProposal(ctx context.Context, prop mma.Proposal, rev uint64, current map[string]mma.Placement, revs map[string]uint64) {
|
func (p *passer) stepProposal(ctx context.Context, prop mma.Proposal, rev uint64, current map[string]mma.Assignments, revs map[string]uint64) {
|
||||||
if !p.saidProposed[prop.ID] && prop.State == mmac.ProposalAnswering && prop.Verdict == nil {
|
if !p.saidProposed[prop.ID] && prop.State == mmac.ProposalAnswering && prop.Verdict == nil {
|
||||||
p.sayEvent(mma.EventPlacementProposed, mma.PlacementEvent{Proposal: prop}, prop.ID)
|
p.sayEvent(mma.EventAssignmentsProposed, mma.AssignmentsEvent{Proposal: prop}, prop.ID)
|
||||||
p.saidProposed[prop.ID] = true
|
p.saidProposed[prop.ID] = true
|
||||||
}
|
}
|
||||||
if prop.Withdrawn != "" {
|
if prop.Withdrawn != "" {
|
||||||
@@ -281,14 +282,14 @@ func (p *passer) stepProposal(ctx context.Context, prop mma.Proposal, rev uint64
|
|||||||
}
|
}
|
||||||
if prop.Seed || prop.HandBack {
|
if prop.Seed || prop.HandBack {
|
||||||
// Adopt and hand-back are gone since the lease write (ADR 0547 §11, ADR 0567 §11): a proposal one left open ends.
|
// Adopt and hand-back are gone since the lease write (ADR 0547 §11, ADR 0567 §11): a proposal one left open ends.
|
||||||
p.endProposal(ctx, prop, "adopt and hand-back are gone since the lease write: placement is the mesh's alone")
|
p.endProposal(ctx, prop, "adopt and hand-back are gone since the lease write: assignments is the mesh's alone")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if prop.Warrant != nil {
|
if prop.Warrant != nil {
|
||||||
p.addNode(ctx, prop, revs)
|
p.addNode(ctx, prop, revs)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
akey, _ := asks.PlacementAskKey(prop.ID)
|
akey, _ := asks.AssignmentsAskKey(prop.ID)
|
||||||
again := prop.Again != nil && !prop.Again.IsZero() && (prop.Verdict == nil || prop.Again.After(prop.Verdict.At))
|
again := prop.Again != nil && !prop.Again.IsZero() && (prop.Verdict == nil || prop.Again.After(prop.Verdict.At))
|
||||||
if prop.State == mmac.ProposalAsked {
|
if prop.State == mmac.ProposalAsked {
|
||||||
askPulse, _ := plc.PulseAskKey(prop.ID)
|
askPulse, _ := plc.PulseAskKey(prop.ID)
|
||||||
@@ -349,7 +350,7 @@ func (p *passer) stepProposal(ctx context.Context, prop mma.Proposal, rev uint64
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if p.writeProposal(ctx, prop, rev) {
|
if p.writeProposal(ctx, prop, rev) {
|
||||||
p.sayEvent(mma.EventPlacementJudged, mma.PlacementEvent{Proposal: prop},
|
p.sayEvent(mma.EventAssignmentsJudged, mma.AssignmentsEvent{Proposal: prop},
|
||||||
fmt.Sprintf("%s.judged.%d", prop.ID, v.At.UnixNano()))
|
fmt.Sprintf("%s.judged.%d", prop.ID, v.At.UnixNano()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -371,7 +372,7 @@ func moved(prop mma.Proposal, revs map[string]uint64) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// intendedOf is, by proposal and the revisions it was laid over, what Intended computed, kept while this process runs.
|
// intendedOf is, by proposal and the revisions it was laid over, what Intended computed, kept while this process runs.
|
||||||
func (p *passer) intendedOf(prop mma.Proposal, current map[string]mma.Placement, cands map[string]mma.CandidatesByProposalNodeValue) map[string]map[string]bool {
|
func (p *passer) intendedOf(prop mma.Proposal, current map[string]mma.Assignments, cands map[string]mma.CandidatesByProposalNodeValue) map[string]map[string]bool {
|
||||||
key := fmt.Sprint(prop.ID, prop.Base)
|
key := fmt.Sprint(prop.ID, prop.Base)
|
||||||
if got, ok := p.intended[key]; ok {
|
if got, ok := p.intended[key]; ok {
|
||||||
return got
|
return got
|
||||||
@@ -381,14 +382,14 @@ func (p *passer) intendedOf(prop mma.Proposal, current map[string]mma.Placement,
|
|||||||
return got
|
return got
|
||||||
}
|
}
|
||||||
|
|
||||||
// compute is what the proposal changes on each node by its placement, and the nodes that answer it: every node with a
|
// compute is what the proposal changes on each node by its assignments, and the nodes that answer it: every node with a
|
||||||
// placement or a record (judge.Intended, ADR 0593 §8). The holder composes nothing: each node says what its compositions
|
// assignments or a record (judge.Intended, ADR 0593 §8). The holder composes nothing: each node says what its compositions
|
||||||
// change in its answer.
|
// change in its answer.
|
||||||
func (p *passer) compute(prop mma.Proposal, current map[string]mma.Placement, cands map[string]mma.CandidatesByProposalNodeValue) (map[string]map[string]bool, []string) {
|
func (p *passer) compute(prop mma.Proposal, current map[string]mma.Assignments, cands map[string]mma.CandidatesByProposalNodeValue) (map[string]map[string]bool, []string) {
|
||||||
laid := map[string]*mma.Placement{}
|
laid := map[string]*mma.Assignments{}
|
||||||
for _, node := range plc.Nodes(prop) {
|
for _, node := range plc.Nodes(prop) {
|
||||||
if c, ok := cands[node]; ok && !c.Reached {
|
if c, ok := cands[node]; ok && !c.Reached {
|
||||||
laid[node] = c.Placement
|
laid[node] = c.Assignments
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
nodes := map[string]bool{}
|
nodes := map[string]bool{}
|
||||||
@@ -403,7 +404,7 @@ func (p *passer) compute(prop mma.Proposal, current map[string]mma.Placement, ca
|
|||||||
|
|
||||||
// mustAnswer finds the nodes the proposal must hear from (§3), writes a reached candidate for each whose key it does not
|
// mustAnswer finds the nodes the proposal must hear from (§3), writes a reached candidate for each whose key it does not
|
||||||
// change, and writes them into the proposal.
|
// change, and writes them into the proposal.
|
||||||
func (p *passer) mustAnswer(ctx context.Context, prop mma.Proposal, rev uint64, current map[string]mma.Placement, cands map[string]mma.CandidatesByProposalNodeValue) {
|
func (p *passer) mustAnswer(ctx context.Context, prop mma.Proposal, rev uint64, current map[string]mma.Assignments, cands map[string]mma.CandidatesByProposalNodeValue) {
|
||||||
for _, node := range plc.Nodes(prop) {
|
for _, node := range plc.Nodes(prop) {
|
||||||
if _, ok := cands[node]; !ok {
|
if _, ok := cands[node]; !ok {
|
||||||
return // the verbs write the candidates before the proposal: wait to hear them
|
return // the verbs write the candidates before the proposal: wait to hear them
|
||||||
@@ -421,7 +422,7 @@ func (p *passer) mustAnswer(ctx context.Context, prop mma.Proposal, rev uint64,
|
|||||||
}
|
}
|
||||||
c := mma.CandidatesByProposalNodeValue{Proposal: prop.ID, Reached: true}
|
c := mma.CandidatesByProposalNodeValue{Proposal: prop.ID, Reached: true}
|
||||||
if pl, ok := current[node]; ok {
|
if pl, ok := current[node]; ok {
|
||||||
c.Placement = &pl
|
c.Assignments = &pl
|
||||||
}
|
}
|
||||||
p.put(ctx, plc.CandidatesBucket, key, func() ([]byte, error) { return plc.EncodeCandidate(c) })
|
p.put(ctx, plc.CandidatesBucket, key, func() ([]byte, error) { return plc.EncodeCandidate(c) })
|
||||||
}
|
}
|
||||||
@@ -436,7 +437,7 @@ func (p *passer) mustAnswer(ctx context.Context, prop mma.Proposal, rev uint64,
|
|||||||
|
|
||||||
// layAgain lays a proposal over the keys as they stand now (§5: a key moved): its candidates are written again, the
|
// layAgain lays a proposal over the keys as they stand now (§5: a key moved): its candidates are written again, the
|
||||||
// nodes that must answer found again, and it is judged and asked again; a proposal that no longer lays ends.
|
// nodes that must answer found again, and it is judged and asked again; a proposal that no longer lays ends.
|
||||||
func (p *passer) layAgain(ctx context.Context, prop mma.Proposal, rev uint64, current map[string]mma.Placement, revs map[string]uint64, why string) {
|
func (p *passer) layAgain(ctx context.Context, prop mma.Proposal, rev uint64, current map[string]mma.Assignments, revs map[string]uint64, why string) {
|
||||||
held, recorded := p.lays(ctx)
|
held, recorded := p.lays(ctx)
|
||||||
again, laid, findings := assign.Propose(assign.ProposeIn{ID: prop.ID, By: prop.By, Why: prop.Why, At: prop.At, Changes: prop.Changes,
|
again, laid, findings := assign.Propose(assign.ProposeIn{ID: prop.ID, By: prop.By, Why: prop.Why, At: prop.At, Changes: prop.Changes,
|
||||||
Current: current, Revisions: revs, Held: held, Recorded: recorded})
|
Current: current, Revisions: revs, Held: held, Recorded: recorded})
|
||||||
@@ -448,7 +449,7 @@ func (p *passer) layAgain(ctx context.Context, prop mma.Proposal, rev uint64, cu
|
|||||||
p.deletePulses(ctx, prop.ID)
|
p.deletePulses(ctx, prop.ID)
|
||||||
for _, node := range plc.Nodes(again) {
|
for _, node := range plc.Nodes(again) {
|
||||||
key, _ := plc.CandidateKey(prop.ID, node)
|
key, _ := plc.CandidateKey(prop.ID, node)
|
||||||
c := mma.CandidatesByProposalNodeValue{Proposal: prop.ID, Placement: laid[node]}
|
c := mma.CandidatesByProposalNodeValue{Proposal: prop.ID, Assignments: laid[node]}
|
||||||
p.put(ctx, plc.CandidatesBucket, key, func() ([]byte, error) { return plc.EncodeCandidate(c) })
|
p.put(ctx, plc.CandidatesBucket, key, func() ([]byte, error) { return plc.EncodeCandidate(c) })
|
||||||
}
|
}
|
||||||
again.Written, again.Words = ptr(p.clock().UTC()), why+": laid again over the keys as they stand, to be judged and asked again"
|
again.Written, again.Words = ptr(p.clock().UTC()), why+": laid again over the keys as they stand, to be judged and asked again"
|
||||||
@@ -469,9 +470,9 @@ func (p *passer) lays(ctx context.Context) (func(string) bool, func(string) bool
|
|||||||
}
|
}
|
||||||
|
|
||||||
// warranted writes an approved proposal (§5): each changed key by compare-and-set at the revision it was laid over; then the warrant recorded, the candidates and the
|
// warranted writes an approved proposal (§5): each changed key by compare-and-set at the revision it was laid over; then the warrant recorded, the candidates and the
|
||||||
// proposal deleted, and placement-changed said.
|
// proposal deleted, and assignments-changed said.
|
||||||
func (p *passer) warranted(ctx context.Context, prop mma.Proposal, rev uint64, akey string, current map[string]mma.Placement, revs map[string]uint64) {
|
func (p *passer) warranted(ctx context.Context, prop mma.Proposal, rev uint64, akey string, current map[string]mma.Assignments, revs map[string]uint64) {
|
||||||
if err := p.performs(akey, assign.PlacementAct(prop)); err != nil {
|
if err := p.performs(akey, assign.AssignmentsAct(prop)); err != nil {
|
||||||
p.endProposal(ctx, prop, "its warrant does not bind these changes, so nothing is written: "+err.Error())
|
p.endProposal(ctx, prop, "its warrant does not bind these changes, so nothing is written: "+err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -481,9 +482,9 @@ func (p *passer) warranted(ctx context.Context, prop mma.Proposal, rev uint64, a
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
cands, _, _ := p.candidatesOf(prop.ID)
|
cands, _, _ := p.candidatesOf(prop.ID)
|
||||||
laid := map[string]*mma.Placement{}
|
laid := map[string]*mma.Assignments{}
|
||||||
for _, node := range plc.Nodes(prop) {
|
for _, node := range plc.Nodes(prop) {
|
||||||
laid[node] = cands[node].Placement
|
laid[node] = cands[node].Assignments
|
||||||
}
|
}
|
||||||
writes, err := assign.Writes(prop, laid)
|
writes, err := assign.Writes(prop, laid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -491,7 +492,7 @@ func (p *passer) warranted(ctx context.Context, prop mma.Proposal, rev uint64, a
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
var changes []plc.Change
|
var changes []plc.Change
|
||||||
writtenLaid := map[string]*mma.Placement{}
|
writtenLaid := map[string]*mma.Assignments{}
|
||||||
after := maps.Clone(current)
|
after := maps.Clone(current)
|
||||||
afterRevs := maps.Clone(revs)
|
afterRevs := maps.Clone(revs)
|
||||||
for _, w := range writes {
|
for _, w := range writes {
|
||||||
@@ -499,7 +500,7 @@ func (p *passer) warranted(ctx context.Context, prop mma.Proposal, rev uint64, a
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
// Another writer came first (a hand-back): what is written stands and is followed through; the rest is laid
|
// Another writer came first (a hand-back): what is written stands and is followed through; the rest is laid
|
||||||
// again over the keys as this write left them.
|
// again over the keys as this write left them.
|
||||||
p.logf("placement %s is not written at revision %d (%v): the rest of the proposal %s is laid again", w.Key, w.Base, err, prop.ID)
|
p.logf("assignments %s is not written at revision %d (%v): the rest of the proposal %s is laid again", w.Key, w.Base, err, prop.ID)
|
||||||
p.followWrite(prop, answer, changes)
|
p.followWrite(prop, answer, changes)
|
||||||
if rest, ok := assign.Rest(prop, writtenLaid); ok {
|
if rest, ok := assign.Rest(prop, writtenLaid); ok {
|
||||||
p.layAgain(ctx, rest, rev, after, afterRevs, "a key moved while it was written, after "+strings.Join(sortedNodes(writtenLaid), ", ")+" were")
|
p.layAgain(ctx, rest, rev, after, afterRevs, "a key moved while it was written, after "+strings.Join(sortedNodes(writtenLaid), ", ")+" were")
|
||||||
@@ -509,7 +510,7 @@ func (p *passer) warranted(ctx context.Context, prop mma.Proposal, rev uint64, a
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
before := current[w.Node]
|
before := current[w.Node]
|
||||||
now := mma.Placement{Node: w.Node}
|
now := mma.Assignments{Node: w.Node}
|
||||||
if w.Value != nil {
|
if w.Value != nil {
|
||||||
now = *w.Value
|
now = *w.Value
|
||||||
after[w.Node] = *w.Value
|
after[w.Node] = *w.Value
|
||||||
@@ -532,9 +533,9 @@ func (p *passer) warranted(ctx context.Context, prop mma.Proposal, rev uint64, a
|
|||||||
// addNode writes a joining node on the warrant of the enrolment that issued its token (ADR 0587 §7): the operator's Approve
|
// addNode writes a joining node on the warrant of the enrolment that issued its token (ADR 0587 §7): the operator's Approve
|
||||||
// was the issue's, so no node is waited on and no ask is raised. The key is created at the revision it was laid over (none: a
|
// was the issue's, so no node is waited on and no ask is raised. The key is created at the revision it was laid over (none: a
|
||||||
// node with a key is no joining node); the enrolment recorded the warrant in the hand-act log when it was given, so this does
|
// node with a key is no joining node); the enrolment recorded the warrant in the hand-act log when it was given, so this does
|
||||||
// not record it again; placement-changed carries the warrant. A key written meanwhile ends the proposal, saying so.
|
// not record it again; assignments-changed carries the warrant. A key written meanwhile ends the proposal, saying so.
|
||||||
func (p *passer) addNode(ctx context.Context, prop mma.Proposal, revs map[string]uint64) {
|
func (p *passer) addNode(ctx context.Context, prop mma.Proposal, revs map[string]uint64) {
|
||||||
current, _ := p.seatPlacements()
|
current, _ := p.seatAssignments()
|
||||||
if moved(prop, revs) {
|
if moved(prop, revs) {
|
||||||
p.endProposal(ctx, prop, "a key it was laid over was written since: the enrolment proposes the node again")
|
p.endProposal(ctx, prop, "a key it was laid over was written since: the enrolment proposes the node again")
|
||||||
return
|
return
|
||||||
@@ -556,7 +557,7 @@ func (p *passer) addNode(ctx context.Context, prop mma.Proposal, revs map[string
|
|||||||
p.endProposal(ctx, prop, "the node's key was not written: "+err.Error())
|
p.endProposal(ctx, prop, "the node's key was not written: "+err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c := plc.ChangeOf(mma.Placement{Node: node}, value)
|
c := plc.ChangeOf(mma.Assignments{Node: node}, value)
|
||||||
c.Before, c.After = uint64(prop.Base[node]), rev
|
c.Before, c.After = uint64(prop.Base[node]), rev
|
||||||
p.deleteCandidates(ctx, prop.ID)
|
p.deleteCandidates(ctx, prop.ID)
|
||||||
p.deletePulses(ctx, prop.ID)
|
p.deletePulses(ctx, prop.ID)
|
||||||
@@ -569,9 +570,9 @@ func (p *passer) addNode(ctx context.Context, prop mma.Proposal, revs map[string
|
|||||||
}
|
}
|
||||||
|
|
||||||
// removeNode deletes the key of a joining node whose enrolment ended unconfirmed, on that enrolment's warrant (the operator's
|
// removeNode deletes the key of a joining node whose enrolment ended unconfirmed, on that enrolment's warrant (the operator's
|
||||||
// decision of 2026-11-02 01:05): no node answers and no ask is raised, as for its add-node; placement-changed carries the warrant.
|
// decision of 2026-11-02 01:05): no node answers and no ask is raised, as for its add-node; assignments-changed carries the warrant.
|
||||||
// A node that runs modules or holds seats is no joining node and stays (LayChanges' finding ends the proposal).
|
// A node that runs modules or holds seats is no joining node and stays (LayChanges' finding ends the proposal).
|
||||||
func (p *passer) removeNode(ctx context.Context, prop mma.Proposal, current map[string]mma.Placement, findings []string) {
|
func (p *passer) removeNode(ctx context.Context, prop mma.Proposal, current map[string]mma.Assignments, findings []string) {
|
||||||
node := prop.Changes[0].Node
|
node := prop.Changes[0].Node
|
||||||
before, ok := current[node]
|
before, ok := current[node]
|
||||||
if len(findings) > 0 || !ok {
|
if len(findings) > 0 || !ok {
|
||||||
@@ -583,7 +584,7 @@ func (p *passer) removeNode(ctx context.Context, prop mma.Proposal, current map[
|
|||||||
p.endProposal(ctx, prop, "the node's key was not deleted: "+err.Error())
|
p.endProposal(ctx, prop, "the node's key was not deleted: "+err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c := plc.ChangeOf(before, mma.Placement{Node: node})
|
c := plc.ChangeOf(before, mma.Assignments{Node: node})
|
||||||
c.Before, c.After = uint64(prop.Base[node]), rev
|
c.Before, c.After = uint64(prop.Base[node]), rev
|
||||||
p.deleteCandidates(ctx, prop.ID)
|
p.deleteCandidates(ctx, prop.ID)
|
||||||
p.deletePulses(ctx, prop.ID)
|
p.deletePulses(ctx, prop.ID)
|
||||||
@@ -591,12 +592,12 @@ func (p *passer) removeNode(ctx context.Context, prop mma.Proposal, current map[
|
|||||||
p.w.put(plc.ProposalsBucket, prop.ID, nil)
|
p.w.put(plc.ProposalsBucket, prop.ID, nil)
|
||||||
w := prop.Warrant
|
w := prop.Warrant
|
||||||
p.sayChanged(prop, mdc.Answer{Option: assign.OptionYes, Warrant: w.Warrant,
|
p.sayChanged(prop, mdc.Answer{Option: assign.OptionYes, Warrant: w.Warrant,
|
||||||
Words: fmt.Sprintf("the machine %s left placement: its enrolment (ask %s) ended before it confirmed", w.Node, w.Ask)},
|
Words: fmt.Sprintf("the machine %s left assignments: its enrolment (ask %s) ended before it confirmed", w.Node, w.Ask)},
|
||||||
[]plc.Change{c})
|
[]plc.Change{c})
|
||||||
}
|
}
|
||||||
|
|
||||||
// followWrite follows the keys a warrant wrote: the warrant recorded and placement-changed said. No verb of the
|
// followWrite follows the keys a warrant wrote: the warrant recorded and assignments-changed said. No verb of the
|
||||||
// controller is called: the placement is the one source of where modules run (ADR 0567 §8, issue 774).
|
// controller is called: the assignments is the one source of where modules run (ADR 0567 §8, issue 774).
|
||||||
func (p *passer) followWrite(prop mma.Proposal, answer mdc.Answer, changes []plc.Change) {
|
func (p *passer) followWrite(prop mma.Proposal, answer mdc.Answer, changes []plc.Change) {
|
||||||
if len(changes) == 0 {
|
if len(changes) == 0 {
|
||||||
return
|
return
|
||||||
@@ -605,7 +606,7 @@ func (p *passer) followWrite(prop mma.Proposal, answer mdc.Answer, changes []plc
|
|||||||
p.sayChanged(prop, answer, changes)
|
p.sayChanged(prop, answer, changes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func sortedNodes(m map[string]*mma.Placement) []string {
|
func sortedNodes(m map[string]*mma.Assignments) []string {
|
||||||
out := make([]string, 0, len(m))
|
out := make([]string, 0, len(m))
|
||||||
for n := range m {
|
for n := range m {
|
||||||
out = append(out, n)
|
out = append(out, n)
|
||||||
@@ -614,7 +615,7 @@ func sortedNodes(m map[string]*mma.Placement) []string {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// sayChanged says placement-changed for the keys written.
|
// sayChanged says assignments-changed for the keys written.
|
||||||
func (p *passer) sayChanged(prop mma.Proposal, answer mdc.Answer, changes []plc.Change) {
|
func (p *passer) sayChanged(prop mma.Proposal, answer mdc.Answer, changes []plc.Change) {
|
||||||
if len(changes) == 0 {
|
if len(changes) == 0 {
|
||||||
return
|
return
|
||||||
@@ -626,14 +627,14 @@ func (p *passer) sayChanged(prop mma.Proposal, answer mdc.Answer, changes []plc.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
plc.SortChanges(changes)
|
plc.SortChanges(changes)
|
||||||
p.sayEvent(mma.EventPlacementChanged, mma.PlacementEvent{Proposal: prop, Nodes: changeBodies(changes),
|
p.sayEvent(mma.EventAssignmentsChanged, mma.AssignmentsEvent{Proposal: prop, Nodes: changeBodies(changes),
|
||||||
Warrant: &mma.PlacementEventWarrant{Warrant: answer.Warrant, Words: answer.Words, At: at}}, prop.ID+".changed")
|
Warrant: &mma.AssignmentsEventWarrant{Warrant: answer.Warrant, Words: answer.Words, At: at}}, prop.ID+".changed")
|
||||||
}
|
}
|
||||||
|
|
||||||
// endProposal ends a proposal: its ask, its candidates and its key deleted, and placement-withdrawn said carrying it
|
// endProposal ends a proposal: its ask, its candidates and its key deleted, and assignments-withdrawn said carrying it
|
||||||
// whole with why it ended.
|
// whole with why it ended.
|
||||||
func (p *passer) endProposal(ctx context.Context, prop mma.Proposal, why string) {
|
func (p *passer) endProposal(ctx context.Context, prop mma.Proposal, why string) {
|
||||||
if akey, err := asks.PlacementAskKey(prop.ID); err == nil && p.w.has(buckets.Asks, akey) {
|
if akey, err := asks.AssignmentsAskKey(prop.ID); err == nil && p.w.has(buckets.Asks, akey) {
|
||||||
if err := p.kvs[buckets.Asks].Delete(ctx, akey); err == nil {
|
if err := p.kvs[buckets.Asks].Delete(ctx, akey); err == nil {
|
||||||
p.w.put(buckets.Asks, akey, nil)
|
p.w.put(buckets.Asks, akey, nil)
|
||||||
}
|
}
|
||||||
@@ -646,7 +647,7 @@ func (p *passer) endProposal(ctx context.Context, prop mma.Proposal, why string)
|
|||||||
}
|
}
|
||||||
p.w.put(plc.ProposalsBucket, prop.ID, nil)
|
p.w.put(plc.ProposalsBucket, prop.ID, nil)
|
||||||
p.logf("the proposal %s ends: %s", prop.ID, why)
|
p.logf("the proposal %s ends: %s", prop.ID, why)
|
||||||
p.sayEvent(mma.EventPlacementWithdrawn, mma.PlacementEvent{Proposal: prop, Ended: why}, prop.ID+".withdrawn")
|
p.sayEvent(mma.EventAssignmentsWithdrawn, mma.AssignmentsEvent{Proposal: prop, Ended: why}, prop.ID+".withdrawn")
|
||||||
}
|
}
|
||||||
|
|
||||||
// deleteCandidates deletes every candidate of a proposal.
|
// deleteCandidates deletes every candidate of a proposal.
|
||||||
@@ -686,7 +687,7 @@ func (p *passer) writeProposal(ctx context.Context, prop mma.Proposal, rev uint6
|
|||||||
}
|
}
|
||||||
|
|
||||||
// say says one of the seat's events, said in the journal when the bus refuses it.
|
// say says one of the seat's events, said in the journal when the bus refuses it.
|
||||||
func (p *passer) sayEvent(event string, body mma.PlacementEvent, id string) {
|
func (p *passer) sayEvent(event string, body mma.AssignmentsEvent, id string) {
|
||||||
if p.publish == nil {
|
if p.publish == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -695,30 +696,30 @@ func (p *passer) sayEvent(event string, body mma.PlacementEvent, id string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeNode writes one node's placement after (nil: none) as its keys (mesh-sdk mmac.Keys.Writes; research
|
// writeNode writes one node's assignments after (nil: none) as its keys (mesh-sdk mmac.Keys.Writes; research
|
||||||
// 072 row 37): the module keys that change, each carrying the value it replaces, then the node key, the commit, then the
|
// 072 row 37): the module keys that change, each carrying the value it replaces, then the node key, the commit, then the
|
||||||
// module keys no longer placed. It answers the node key's revision after (0: deleted). A write that fails up to the
|
// module keys no longer placed. It answers the node key's revision after (0: deleted). A write that fails up to the
|
||||||
// commit ends it with nothing changed for any reader, the module keys written before it read at their previous values;
|
// commit ends it with nothing changed for any reader, the module keys written before it read at their previous values;
|
||||||
// a delete after the commit that fails leaves a module key the node key does not name, which places nothing and is
|
// a delete after the commit that fails leaves a module key the node key does not name, which places nothing and is
|
||||||
// deleted at the node's next write. Each key written is taken into the world at once, so the next write of the pass
|
// deleted at the node's next write. Each key written is taken into the world at once, so the next write of the pass
|
||||||
// reads it.
|
// reads it.
|
||||||
func (p *passer) writeNode(ctx context.Context, node string, after *mma.Placement) (uint64, error) {
|
func (p *passer) writeNode(ctx context.Context, node string, after *mma.Assignments) (uint64, error) {
|
||||||
writes, err := p.w.placement.Writes(node, after)
|
writes, err := p.w.assignmentKeys.Writes(node, after)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
kv := p.kvs[plc.PlacementBucket]
|
kv := p.kvs[plc.AssignmentsBucket]
|
||||||
committed, rev := false, uint64(0)
|
committed, rev := false, uint64(0)
|
||||||
for _, w := range writes {
|
for _, w := range writes {
|
||||||
got, err := writeKey(ctx, kv, w)
|
got, err := writeKey(ctx, kv, w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if committed {
|
if committed {
|
||||||
p.logf("placement %s is not deleted after %s's write (%v): it places nothing, and goes at the node's next write", w.Key, node, err)
|
p.logf("assignments %s is not deleted after %s's write (%v): it places nothing, and goes at the node's next write", w.Key, node, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return 0, fmt.Errorf("placement %s at revision %d: %w", w.Key, w.Base, err)
|
return 0, fmt.Errorf("assignments %s at revision %d: %w", w.Key, w.Base, err)
|
||||||
}
|
}
|
||||||
p.w.putEntry(plc.PlacementBucket, w.Key, w.Value, got, p.clock())
|
p.w.putEntry(plc.AssignmentsBucket, w.Key, w.Value, got, p.clock())
|
||||||
if w.Commit {
|
if w.Commit {
|
||||||
committed, rev = true, got
|
committed, rev = true, got
|
||||||
}
|
}
|
||||||
@@ -726,7 +727,7 @@ func (p *passer) writeNode(ctx context.Context, node string, after *mma.Placemen
|
|||||||
return rev, nil
|
return rev, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeKey makes one write of a placement key by compare-and-set and answers the key's revision after (0: deleted).
|
// writeKey makes one write of an assignments key by compare-and-set and answers the key's revision after (0: deleted).
|
||||||
func writeKey(ctx context.Context, kv jetstream.KeyValue, w mmac.KeyWrite) (uint64, error) {
|
func writeKey(ctx context.Context, kv jetstream.KeyValue, w mmac.KeyWrite) (uint64, error) {
|
||||||
switch {
|
switch {
|
||||||
case w.Value == nil:
|
case w.Value == nil:
|
||||||
@@ -738,33 +739,6 @@ func writeKey(ctx context.Context, kv jetstream.KeyValue, w mmac.KeyWrite) (uint
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// convertPlacement converts every node the state holds only in the form before, `placement.<node>`, into its node key
|
|
||||||
// and module keys (research 072 row 37), once: each node converted is said, and a node whose conversion fails is said
|
|
||||||
// and tried again at the next pass. The legacy key is left for the readers not yet delivered; a node key wins over it
|
|
||||||
// for every reader that reads both, and it is deleted with the node key when the node's placement is.
|
|
||||||
func (p *passer) convertPlacement(ctx context.Context) {
|
|
||||||
if p.convertedAll {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
migrations, err := p.w.placement.Migrations()
|
|
||||||
if err != nil {
|
|
||||||
p.logf("placement: some nodes in the form before cannot be converted, and stay read from it: %v", err)
|
|
||||||
}
|
|
||||||
failed := false
|
|
||||||
for _, node := range slices.Sorted(maps.Keys(migrations)) {
|
|
||||||
before, rev, _ := p.w.placement.Of(node)
|
|
||||||
got, err := p.writeNode(ctx, node, &before)
|
|
||||||
if err != nil {
|
|
||||||
failed = true
|
|
||||||
p.logf("placement: %s is not converted from placement.%s (revision %d) to its node and module keys, and is tried again at the next pass: %v", node, node, rev, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
p.logf("placement: %s converted from placement.%s (revision %d) to %s.node (revision %d) and %d module keys (research 072 row 37); placement.%s stays for readers not yet delivered",
|
|
||||||
node, node, rev, node, got, len(before.Modules), node)
|
|
||||||
}
|
|
||||||
p.convertedAll = !failed && err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ptr[T any](v T) *T { return &v }
|
func ptr[T any](v T) *T { return &v }
|
||||||
|
|
||||||
// changeBodies are the changes as the seat's event carries them.
|
// changeBodies are the changes as the seat's event carries them.
|
||||||
|
|||||||
@@ -14,12 +14,12 @@ import (
|
|||||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||||
|
|
||||||
mmac "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
mmac "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
|
||||||
mma "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
mma "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||||
|
|
||||||
"mesh-delivery-assigner/internal/assign"
|
"mesh-delivery-assigner/internal/assign"
|
||||||
plc "mesh-delivery-assigner/internal/placement"
|
plc "mesh-delivery-assigner/internal/assignment"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The seat mesh-module-assigner's seven verbs (novox/hq ADR 0567 §10), served through the tool runner beside the put tools: a
|
// The seat mesh-module-assigner's seven verbs (novox/hq ADR 0567 §10), served through the tool runner beside the put tools: a
|
||||||
@@ -59,18 +59,18 @@ func newSeatTools(st seatStore, ask func(string, any) (json.RawMessage, error))
|
|||||||
return &seatTools{st: st, ask: ask, now: time.Now}
|
return &seatTools{st: st, ask: ask, now: time.Now}
|
||||||
}
|
}
|
||||||
|
|
||||||
// keyed is one placement key as a verb answers it.
|
// keyed is one assignments key as a verb answers it.
|
||||||
type keyed struct {
|
type keyed struct {
|
||||||
Placement mma.Placement `json:"placement"`
|
Assignments mma.Assignments `json:"assignments"`
|
||||||
Revision uint64 `json:"revision"`
|
Revision uint64 `json:"revision"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// placements reads every node's placement and the revision of its node key (its legacy key's before it is converted),
|
// assignments reads every node's assignments and the revision of its node key (its legacy key's before it is converted),
|
||||||
// assembled from its keys in either form (mmac.Keys; research 072 row 37).
|
// assembled from its keys in either form (mmac.Keys; research 072 row 37).
|
||||||
func (s *seatTools) placements() (map[string]mma.Placement, map[string]uint64, error) {
|
func (s *seatTools) assignments() (map[string]mma.Assignments, map[string]uint64, error) {
|
||||||
keys, err := s.st.keys(mma.StatePlacement)
|
keys, err := s.st.keys(mma.StateAssignments)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("placement cannot be listed: %w", err)
|
return nil, nil, fmt.Errorf("assignments cannot be listed: %w", err)
|
||||||
}
|
}
|
||||||
sort.Strings(keys)
|
sort.Strings(keys)
|
||||||
held := mmac.NewKeys()
|
held := mmac.NewKeys()
|
||||||
@@ -79,9 +79,9 @@ func (s *seatTools) placements() (map[string]mma.Placement, map[string]uint64, e
|
|||||||
if _, _, _, err := mmac.ParseKey(key); err != nil {
|
if _, _, _, err := mmac.ParseKey(key); err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
raw, rev, found, err := s.st.get(mma.StatePlacement, key)
|
raw, rev, found, err := s.st.get(mma.StateAssignments, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("placement key %s cannot be read: %w", key, err)
|
return nil, nil, fmt.Errorf("assignments key %s cannot be read: %w", key, err)
|
||||||
}
|
}
|
||||||
if !found {
|
if !found {
|
||||||
continue
|
continue
|
||||||
@@ -92,7 +92,7 @@ func (s *seatTools) placements() (map[string]mma.Placement, map[string]uint64, e
|
|||||||
}
|
}
|
||||||
out, revs := held.AllRevisions()
|
out, revs := held.AllRevisions()
|
||||||
if len(bad) > 0 && len(out) == 0 {
|
if len(bad) > 0 && len(out) == 0 {
|
||||||
return nil, nil, fmt.Errorf("placement cannot be read: %s", strings.Join(bad, "; "))
|
return nil, nil, fmt.Errorf("assignments cannot be read: %s", strings.Join(bad, "; "))
|
||||||
}
|
}
|
||||||
return out, revs, nil
|
return out, revs, nil
|
||||||
}
|
}
|
||||||
@@ -145,14 +145,14 @@ func (s *seatTools) proposal(id string) (mma.Proposal, error) {
|
|||||||
|
|
||||||
// write writes a proposal laid by assign.Propose: each candidate, then the proposal, whose write the assigner's process
|
// write writes a proposal laid by assign.Propose: each candidate, then the proposal, whose write the assigner's process
|
||||||
// takes as the event to compose and judge it.
|
// takes as the event to compose and judge it.
|
||||||
func (s *seatTools) write(p mma.Proposal, laid map[string]*mma.Placement) error {
|
func (s *seatTools) write(p mma.Proposal, laid map[string]*mma.Assignments) error {
|
||||||
p.Written = ptr(s.now().UTC())
|
p.Written = ptr(s.now().UTC())
|
||||||
for _, node := range plc.Nodes(p) {
|
for _, node := range plc.Nodes(p) {
|
||||||
key, err := plc.CandidateKey(p.ID, node)
|
key, err := plc.CandidateKey(p.ID, node)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
raw, err := plc.EncodeCandidate(mma.CandidatesByProposalNodeValue{Proposal: p.ID, Placement: laid[node]})
|
raw, err := plc.EncodeCandidate(mma.CandidatesByProposalNodeValue{Proposal: p.ID, Assignments: laid[node]})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -215,7 +215,7 @@ func (s *seatTools) doPropose(args map[string]any, by string) (any, error) {
|
|||||||
return nil, errors.New("the seed is gone with adopt: propose takes place, take-off, pin, unpin, hold, unhold, decide and config")
|
return nil, errors.New("the seed is gone with adopt: propose takes place, take-off, pin, unpin, hold, unhold, decide and config")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
current, revs, err := s.placements()
|
current, revs, err := s.assignments()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -282,7 +282,7 @@ func moduleOfCaller(by string, enrollers []string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// doWithdraw is the verb withdraw (§2): the proposal is marked, and the assigner deletes it and its candidates and says
|
// doWithdraw is the verb withdraw (§2): the proposal is marked, and the assigner deletes it and its candidates and says
|
||||||
// placement-withdrawn carrying it whole.
|
// assignments-withdrawn carrying it whole.
|
||||||
func (s *seatTools) doWithdraw(args map[string]any, by string) (any, error) {
|
func (s *seatTools) doWithdraw(args map[string]any, by string) (any, error) {
|
||||||
why := arg(args, "why")
|
why := arg(args, "why")
|
||||||
if why == "" {
|
if why == "" {
|
||||||
@@ -321,8 +321,8 @@ func (s *seatTools) doAgain(args map[string]any) (any, error) {
|
|||||||
return map[string]any{"proposal": p.ID, "again": p.Again, "follow": followProposal(p.ID)}, nil
|
return map[string]any{"proposal": p.ID, "again": p.Again, "follow": followProposal(p.ID)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// placementAnswer is what the verb placement answers.
|
// assignmentsAnswer is what the verb assignments answers.
|
||||||
type placementAnswer struct {
|
type assignmentsAnswer struct {
|
||||||
Keys map[string]keyed `json:"keys,omitempty"`
|
Keys map[string]keyed `json:"keys,omitempty"`
|
||||||
Nodes []string `json:"nodes,omitempty"`
|
Nodes []string `json:"nodes,omitempty"`
|
||||||
Count int `json:"count"`
|
Count int `json:"count"`
|
||||||
@@ -340,18 +340,18 @@ type historyEntry struct {
|
|||||||
Deleted bool `json:"deleted,omitempty"`
|
Deleted bool `json:"deleted,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// doPlacement is the verb placement (§10, §6): one node's key, one module's nodes, or every key; with history, the
|
// doAssignments is the verb assignments (§10, §6): one node's key, one module's nodes, or every key; with history, the
|
||||||
// changes in order from the revisions the state keeps.
|
// changes in order from the revisions the state keeps.
|
||||||
func (s *seatTools) doPlacement(args map[string]any) (any, error) {
|
func (s *seatTools) doAssignments(args map[string]any) (any, error) {
|
||||||
if arg(args, "seats") == "true" {
|
if arg(args, "seats") == "true" {
|
||||||
return s.seats(arg(args, "node"))
|
return s.seats(arg(args, "node"))
|
||||||
}
|
}
|
||||||
all, revs, err := s.placements()
|
all, revs, err := s.assignments()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
node, module := arg(args, "node"), arg(args, "module")
|
node, module := arg(args, "node"), arg(args, "module")
|
||||||
out := placementAnswer{Keys: map[string]keyed{}, Count: len(all), History: plc.PlacementHistory}
|
out := assignmentsAnswer{Keys: map[string]keyed{}, Count: len(all), History: plc.AssignmentsHistory}
|
||||||
for n, p := range all {
|
for n, p := range all {
|
||||||
if node != "" && n != node {
|
if node != "" && n != node {
|
||||||
continue
|
continue
|
||||||
@@ -359,12 +359,12 @@ func (s *seatTools) doPlacement(args map[string]any) (any, error) {
|
|||||||
if module != "" && !strings.Contains(" "+strings.Join(mmac.Assigned(p), " ")+" ", " "+module+" ") {
|
if module != "" && !strings.Contains(" "+strings.Join(mmac.Assigned(p), " ")+" ", " "+module+" ") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
out.Keys[n] = keyed{Placement: p, Revision: revs[n]}
|
out.Keys[n] = keyed{Assignments: p, Revision: revs[n]}
|
||||||
out.Nodes = append(out.Nodes, n)
|
out.Nodes = append(out.Nodes, n)
|
||||||
}
|
}
|
||||||
sort.Strings(out.Nodes)
|
sort.Strings(out.Nodes)
|
||||||
if node != "" && len(out.Keys) == 0 && arg(args, "history") != "true" {
|
if node != "" && len(out.Keys) == 0 && arg(args, "history") != "true" {
|
||||||
return nil, fmt.Errorf("%s has no placement key", node)
|
return nil, fmt.Errorf("%s has no assignments key", node)
|
||||||
}
|
}
|
||||||
if arg(args, "history") != "true" {
|
if arg(args, "history") != "true" {
|
||||||
return out, nil
|
return out, nil
|
||||||
@@ -402,8 +402,8 @@ func (s *seatTools) doPlacement(args map[string]any) (any, error) {
|
|||||||
out.Changes = append(out.Changes, e)
|
out.Changes = append(out.Changes, e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
out.Older = fmt.Sprintf("the state keeps %d revisions of each key; an older change is in its placement-changed event on "+
|
out.Older = fmt.Sprintf("the state keeps %d revisions of each key; an older change is in its assignments-changed event on "+
|
||||||
"the seat's subject on the shared events stream, kept %d days", plc.PlacementHistory, plc.EventDays)
|
"the seat's subject on the shared events stream, kept %d days", plc.AssignmentsHistory, plc.EventDays)
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -483,11 +483,11 @@ func (s *seatTools) doProposals(args map[string]any) (any, error) {
|
|||||||
// only from a tool of that name), with the seat's own schemas.
|
// only from a tool of that name), with the seat's own schemas.
|
||||||
func (s *seatTools) tools() []stdio.Tool {
|
func (s *seatTools) tools() []stdio.Tool {
|
||||||
run := map[string]func(map[string]any) (any, error){
|
run := map[string]func(map[string]any) (any, error){
|
||||||
mma.VerbPlacement: s.doPlacement,
|
mma.VerbAssignments: s.doAssignments,
|
||||||
mma.VerbProposals: s.doProposals,
|
mma.VerbProposals: s.doProposals,
|
||||||
mma.VerbPropose: func(a map[string]any) (any, error) { return s.doPropose(a, stdio.CallerOf(a)) },
|
mma.VerbPropose: func(a map[string]any) (any, error) { return s.doPropose(a, stdio.CallerOf(a)) },
|
||||||
mma.VerbWithdraw: func(a map[string]any) (any, error) { return s.doWithdraw(a, stdio.CallerOf(a)) },
|
mma.VerbWithdraw: func(a map[string]any) (any, error) { return s.doWithdraw(a, stdio.CallerOf(a)) },
|
||||||
mma.VerbAgain: s.doAgain,
|
mma.VerbAgain: s.doAgain,
|
||||||
}
|
}
|
||||||
var out []stdio.Tool
|
var out []stdio.Tool
|
||||||
for _, v := range mma.Verbs() {
|
for _, v := range mma.Verbs() {
|
||||||
@@ -496,7 +496,7 @@ func (s *seatTools) tools() []stdio.Tool {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// seats is the verb placement with seats (ADR 0571 §4): who holds each seat and on which node, as the state seat-holders
|
// seats is the verb assignments with seats (ADR 0571 §4): who holds each seat and on which node, as the state seat-holders
|
||||||
// holds it; with node, the keys of seats held on that node.
|
// holds it; with node, the keys of seats held on that node.
|
||||||
func (s *seatTools) seats(node string) (any, error) {
|
func (s *seatTools) seats(node string) (any, error) {
|
||||||
keys, err := s.st.keys(mma.StateSeatHolders)
|
keys, err := s.st.keys(mma.StateSeatHolders)
|
||||||
@@ -525,71 +525,51 @@ func (s *seatTools) seats(node string) (any, error) {
|
|||||||
return map[string]any{"seats": out, "holds": len(keys)}, nil
|
return map[string]any{"seats": out, "holds": len(keys)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// keyedRevision is one revision of a node's placement key, the key it is of beside it.
|
// keyedRevision is one revision of a node's assignments key, the key it is of beside it.
|
||||||
type keyedRevision struct {
|
type keyedRevision struct {
|
||||||
stdio.StateRevision
|
stdio.StateRevision
|
||||||
Key string
|
Key string
|
||||||
}
|
}
|
||||||
|
|
||||||
// nodeHistory is the revisions of a node's placement, in the order the bus wrote them: its legacy key's
|
// nodeHistory is the revisions of a node's assignments, in the order the bus wrote them: its node key's `<node>.node`,
|
||||||
// `placement.<node>` before it was converted, then its node key's `<node>.node`, whose every revision is one write of
|
// whose every revision is one write of the node (research 072 row 37: the node key is a write's commit, so its history is
|
||||||
// the node (research 072 row 37: the node key is a write's commit, so its history is the node's placement history).
|
// the node's assignments history).
|
||||||
func (s *seatTools) nodeHistory(node string) ([]keyedRevision, error) {
|
func (s *seatTools) nodeHistory(node string) ([]keyedRevision, error) {
|
||||||
legacy, err := mmac.LegacyKey(node)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
nodeKey, err := mmac.NodeKey(node)
|
nodeKey, err := mmac.NodeKey(node)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
revisions, err := s.st.history(mma.StateAssignments, nodeKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("the history of %s cannot be read: %w", nodeKey, err)
|
||||||
|
}
|
||||||
var out []keyedRevision
|
var out []keyedRevision
|
||||||
for _, key := range []string{legacy, nodeKey} {
|
for _, r := range revisions {
|
||||||
revisions, err := s.st.history(mma.StatePlacement, key)
|
out = append(out, keyedRevision{StateRevision: r, Key: nodeKey})
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("the history of %s cannot be read: %w", key, err)
|
|
||||||
}
|
|
||||||
for _, r := range revisions {
|
|
||||||
out = append(out, keyedRevision{StateRevision: r, Key: key})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
sort.SliceStable(out, func(i, j int) bool { return out[i].Revision < out[j].Revision })
|
sort.SliceStable(out, func(i, j int) bool { return out[i].Revision < out[j].Revision })
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// sketch is a revision of a node's placement as far as its history says a change: each module's digest, and its pins
|
// sketch is a revision of a node's assignments as far as its history says a change: each module's digest, and its pins
|
||||||
// and holdings, which change every module (plc.ChangeOf).
|
// and holdings, which change every module (plc.ChangeOf).
|
||||||
type sketch struct {
|
type sketch struct {
|
||||||
modules []mma.PlacementByNodeNodeValueModulesItem
|
modules []mma.AssignmentsByNodeNodeValueModulesItem
|
||||||
nodeWide string
|
nodeWide string
|
||||||
}
|
}
|
||||||
|
|
||||||
// sketchOf reads one revision of a node key or a legacy key; a value that does not read places nothing.
|
// sketchOf reads one revision of a node key; a value that does not read places nothing.
|
||||||
func sketchOf(key string, value []byte) sketch {
|
func sketchOf(key string, value []byte) sketch {
|
||||||
_, _, kind, err := mmac.ParseKey(key)
|
_, _, kind, err := mmac.ParseKey(key)
|
||||||
|
if err != nil || kind != mmac.NodeKind {
|
||||||
|
return sketch{}
|
||||||
|
}
|
||||||
|
n, err := mmac.DecodeNode(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return sketch{}
|
return sketch{}
|
||||||
}
|
}
|
||||||
wide := func(pins map[string]mma.Provider, holds []mma.Holding) string {
|
raw, _ := json.Marshal([]any{n.Pins, n.Holds})
|
||||||
raw, _ := json.Marshal([]any{pins, holds})
|
return sketch{modules: n.Modules, nodeWide: string(raw)}
|
||||||
return string(raw)
|
|
||||||
}
|
|
||||||
if kind == mmac.NodeKind {
|
|
||||||
n, err := mmac.DecodeNode(value)
|
|
||||||
if err != nil {
|
|
||||||
return sketch{}
|
|
||||||
}
|
|
||||||
return sketch{modules: n.Modules, nodeWide: wide(n.Pins, n.Holds)}
|
|
||||||
}
|
|
||||||
p, err := mmac.DecodePlacement(value)
|
|
||||||
if err != nil {
|
|
||||||
return sketch{}
|
|
||||||
}
|
|
||||||
out := sketch{nodeWide: wide(p.Pins, p.Holds)}
|
|
||||||
for _, m := range p.Modules {
|
|
||||||
out.modules = append(out.modules, mma.PlacementByNodeNodeValueModulesItem{Module: m.Module, Digest: mmac.Digest(m)})
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// changeFrom is what changed from before to s on node, as plc.ChangeOf says it.
|
// changeFrom is what changed from before to s on node, as plc.ChangeOf says it.
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"maps"
|
||||||
"mesh-delivery-assigner/internal/buckets"
|
"mesh-delivery-assigner/internal/buckets"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -19,16 +20,16 @@ import (
|
|||||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||||
"git.novox.be/novox/mesh-sdk/go/keys"
|
"git.novox.be/novox/mesh-sdk/go/keys"
|
||||||
|
|
||||||
|
mmac "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
|
||||||
|
mma "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||||
mcc "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient"
|
mcc "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient"
|
||||||
mmac "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
|
||||||
mma "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
|
||||||
"git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient"
|
"git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient"
|
||||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||||
"github.com/nats-io/nats.go/jetstream"
|
"github.com/nats-io/nats.go/jetstream"
|
||||||
|
|
||||||
"mesh-delivery-assigner/internal/assign"
|
"mesh-delivery-assigner/internal/assign"
|
||||||
plc "mesh-delivery-assigner/internal/placement"
|
plc "mesh-delivery-assigner/internal/assignment"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s memStore) del(state, key string) error {
|
func (s memStore) del(state, key string) error {
|
||||||
@@ -45,13 +46,13 @@ func (s memStore) history(state, key string) ([]stdio.StateRevision, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ADR 0567 §2, §7, §10: the seat's five verbs over the states. propose lays a proposal and writes it with its candidates,
|
// ADR 0567 §2, §7, §10: the seat's five verbs over the states. propose lays a proposal and writes it with its candidates,
|
||||||
// or refuses naming every finding and writes nothing; withdraw and again mark it for the holder; placement and
|
// or refuses naming every finding and writes nothing; withdraw and again mark it for the holder; assignments and
|
||||||
// proposals read every state the seat keeps, and history names the stream for what the state no longer keeps.
|
// proposals read every state the seat keeps, and history names the stream for what the state no longer keeps.
|
||||||
func TestTheSeatsVerbs(t *testing.T) {
|
func TestTheSeatsVerbs(t *testing.T) {
|
||||||
anchor := mma.Placement{V: keys.Version, Node: "anchor", Modules: []mma.PlacedModule{{Module: "web"}, {Module: "mesh-enrolment"}},
|
anchor := mma.Assignments{V: keys.Version, Node: "anchor", Modules: []mma.PlacedModule{{Module: "web"}, {Module: "mesh-enrolment"}},
|
||||||
Holds: []mma.Holding{{Claim: "mesh-node-enroller", Scope: "mesh", Module: "mesh-enrolment"}}}
|
Holds: []mma.Holding{{Claim: "mesh-node-enroller", Scope: "mesh", Module: "mesh-enrolment"}}}
|
||||||
st := memStore{}
|
st := memStore{}
|
||||||
for _, state := range []string{mma.StatePlacement, mma.StateProposals, mma.StateCandidates, modulesState, haveState} {
|
for _, state := range []string{mma.StateAssignments, mma.StateProposals, mma.StateCandidates, modulesState, haveState} {
|
||||||
st[state] = &memKV{values: map[string][]byte{}}
|
st[state] = &memKV{values: map[string][]byte{}}
|
||||||
}
|
}
|
||||||
for _, m := range []string{"web", "shell", "mesh-enrolment"} {
|
for _, m := range []string{"web", "shell", "mesh-enrolment"} {
|
||||||
@@ -68,8 +69,7 @@ func TestTheSeatsVerbs(t *testing.T) {
|
|||||||
})
|
})
|
||||||
s.now = func() time.Time { return time.Date(2026, 10, 28, 20, 0, 0, 0, time.UTC) }
|
s.now = func() time.Time { return time.Date(2026, 10, 28, 20, 0, 0, 0, time.UTC) }
|
||||||
|
|
||||||
raw, _ := mmac.EncodePlacement(anchor)
|
setAssignments(st[mma.StateAssignments], anchor)
|
||||||
st[mma.StatePlacement].values["placement.anchor"] = raw
|
|
||||||
|
|
||||||
// withdraw and again mark the proposal; the holder ends or judges it.
|
// withdraw and again mark the proposal; the holder ends or judges it.
|
||||||
got, err := s.doPropose(map[string]any{"why": "hold", "changes": `[{"op":"hold","node":"anchor","module":"web","claim":"node-shell","scope":"node"}]`}, "op")
|
got, err := s.doPropose(map[string]any{"why": "hold", "changes": `[{"op":"hold","node":"anchor","module":"web","claim":"node-shell","scope":"node"}]`}, "op")
|
||||||
@@ -106,19 +106,19 @@ func TestTheSeatsVerbs(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
move := got.(proposed).Proposal
|
move := got.(proposed).Proposal
|
||||||
if move.By != "operator@laptop" || move.Base["anchor"] != int64(len(raw)) || move.Base["laptop"] != 0 || len(st[mma.StateCandidates].values) != 2 {
|
if move.By != "operator@laptop" || move.Base["anchor"] != int64(len(st[mma.StateAssignments].values["anchor.node"])) || move.Base["laptop"] != 0 || len(st[mma.StateCandidates].values) != 2 {
|
||||||
t.Fatalf("propose = %+v", move)
|
t.Fatalf("propose = %+v", move)
|
||||||
}
|
}
|
||||||
|
|
||||||
// placement and proposals read every state the seat keeps (§10); history names the stream for what is older.
|
// assignments and proposals read every state the seat keeps (§10); history names the stream for what is older.
|
||||||
reads := map[string]bool{}
|
reads := map[string]bool{}
|
||||||
pa, err := s.doPlacement(map[string]any{"module": "web", "history": "true"})
|
pa, err := s.doAssignments(map[string]any{"module": "web", "history": "true"})
|
||||||
if a := pa.(placementAnswer); err != nil || !slices.Equal(a.Nodes, []string{"anchor"}) || len(a.Changes) != 1 || !strings.Contains(a.Older, "shared events stream") {
|
if a := pa.(assignmentsAnswer); err != nil || !slices.Equal(a.Nodes, []string{"anchor"}) || len(a.Changes) != 1 || !strings.Contains(a.Older, "shared events stream") {
|
||||||
t.Errorf("placement of web = %+v, %v", pa, err)
|
t.Errorf("assignments of web = %+v, %v", pa, err)
|
||||||
} else {
|
} else {
|
||||||
reads[mma.StatePlacement], reads["events"] = true, true
|
reads[mma.StateAssignments], reads["events"] = true, true
|
||||||
}
|
}
|
||||||
if _, err := s.doPlacement(map[string]any{"node": "laptop"}); err == nil {
|
if _, err := s.doAssignments(map[string]any{"node": "laptop"}); err == nil {
|
||||||
t.Error("a node with no key was answered")
|
t.Error("a node with no key was answered")
|
||||||
}
|
}
|
||||||
ps, err := s.doProposals(map[string]any{"id": move.ID})
|
ps, err := s.doProposals(map[string]any{"id": move.ID})
|
||||||
@@ -132,7 +132,7 @@ func TestTheSeatsVerbs(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
for _, state := range raw0.State {
|
for _, state := range raw0.State {
|
||||||
if slices.Contains([]string{mma.StatePlacement, mma.StateProposals, mma.StateCandidates}, state.Name) && !reads[state.Name] {
|
if slices.Contains([]string{mma.StateAssignments, mma.StateProposals, mma.StateCandidates}, state.Name) && !reads[state.Name] {
|
||||||
t.Errorf("no verb reads the seat's state %s", state.Name)
|
t.Errorf("no verb reads the seat's state %s", state.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,7 +142,7 @@ func TestTheSeatsVerbs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Issue 774 (ADR 0567 §8): every kind of change is proposed, and there is no controller to hand placement back to.
|
// Issue 774 (ADR 0567 §8): every kind of change is proposed, and there is no controller to hand assignments back to.
|
||||||
if _, err := s.doPropose(map[string]any{"why": "hold", "changes": `[{"op":"hold","node":"anchor","module":"web","claim":"node-shell","scope":"node"}]`}, "op"); err != nil {
|
if _, err := s.doPropose(map[string]any{"why": "hold", "changes": `[{"op":"hold","node":"anchor","module":"web","claim":"node-shell","scope":"node"}]`}, "op"); err != nil {
|
||||||
t.Errorf("a hold: %v", err)
|
t.Errorf("a hold: %v", err)
|
||||||
}
|
}
|
||||||
@@ -168,11 +168,10 @@ func TestTheSeatsVerbs(t *testing.T) {
|
|||||||
if err := addNode("novox.mesh-enrolment", good); err != nil {
|
if err := addNode("novox.mesh-enrolment", good); err != nil {
|
||||||
t.Errorf("an add-node from mesh-enrolment: %v", err)
|
t.Errorf("an add-node from mesh-enrolment: %v", err)
|
||||||
}
|
}
|
||||||
// A remove-node (the operator's decision of 2026-11-02 01:05) takes a joining node that never confirmed off placement: from
|
// A remove-node (the operator's decision of 2026-11-02 01:05) takes a joining node that never confirmed off assignments: from
|
||||||
// mesh-enrolment, on the warrant naming that node, of a node running nothing, it is proposed with the node's key to delete;
|
// mesh-enrolment, on the warrant naming that node, of a node running nothing, it is proposed with the node's key to delete;
|
||||||
// of a node running modules, or with another node's warrant, it is refused.
|
// of a node running modules, or with another node's warrant, it is refused.
|
||||||
joiner, _ := mmac.EncodePlacement(mma.Placement{V: keys.Version, Node: "joiner", Decided: mma.NodeDecided{Address: "10.9.0.7"}, Modules: []mma.PlacedModule{}})
|
setAssignments(st[mma.StateAssignments], mma.Assignments{V: keys.Version, Node: "joiner", Decided: mma.NodeDecided{Address: "10.9.0.7"}, Modules: []mma.PlacedModule{}})
|
||||||
st[mma.StatePlacement].values["placement.joiner"] = joiner
|
|
||||||
removeNode := func(node, warrant string) (any, error) {
|
removeNode := func(node, warrant string) (any, error) {
|
||||||
return s.doPropose(map[string]any{"why": "enrolment ended", "warrant": warrant,
|
return s.doPropose(map[string]any{"why": "enrolment ended", "warrant": warrant,
|
||||||
"changes": []any{map[string]any{"op": "remove-node", "node": node}}}, "novox.mesh-enrolment")
|
"changes": []any{map[string]any{"op": "remove-node", "node": node}}}, "novox.mesh-enrolment")
|
||||||
@@ -198,9 +197,7 @@ func TestTheSeatsVerbs(t *testing.T) {
|
|||||||
t.Run("1955/L7 (ADR 0571 §6): rule 7 reads the node's own composition; an unplaced node is said", func(t *testing.T) {
|
t.Run("1955/L7 (ADR 0571 §6): rule 7 reads the node's own composition; an unplaced node is said", func(t *testing.T) {
|
||||||
w := newWorld()
|
w := newWorld()
|
||||||
fp := meshcatalogclient.SourcePrefix2 + strings.Repeat("2", 64)
|
fp := meshcatalogclient.SourcePrefix2 + strings.Repeat("2", 64)
|
||||||
pl, _ := mmac.EncodePlacement(mma.Placement{V: keys.Version, Node: "ace", Modules: []mma.PlacedModule{{Module: "web"}}})
|
putAssignments(w, mma.Assignments{V: keys.Version, Node: "ace", Modules: []mma.PlacedModule{{Module: "web"}}})
|
||||||
pkey, _ := mmac.LegacyKey("ace")
|
|
||||||
w.putEntry(plc.PlacementBucket, pkey, pl, 1, time.Now())
|
|
||||||
for node, digest := range map[string]string{"ace": "sha256:" + strings.Repeat("a", 64), "g14": ""} {
|
for node, digest := range map[string]string{"ace": "sha256:" + strings.Repeat("a", 64), "g14": ""} {
|
||||||
hn, _ := nodeengineclient.EncodeHaveNode(nodeengine.HaveByNodeNodeValue{Composed: digest})
|
hn, _ := nodeengineclient.EncodeHaveNode(nodeengine.HaveByNodeNodeValue{Composed: digest})
|
||||||
hk, _ := nodeengineclient.HaveNodeKey(node)
|
hk, _ := nodeengineclient.HaveNodeKey(node)
|
||||||
@@ -220,25 +217,18 @@ func TestTheSeatsVerbs(t *testing.T) {
|
|||||||
if _, ok := findings[assign.UnplacedKey("g14")]; !ok || len(findings) != 1 {
|
if _, ok := findings[assign.UnplacedKey("g14")]; !ok || len(findings) != 1 {
|
||||||
t.Fatalf("findings = %v", findings)
|
t.Fatalf("findings = %v", findings)
|
||||||
}
|
}
|
||||||
pl2, _ := mmac.EncodePlacement(mma.Placement{V: keys.Version, Node: "g14", Modules: []mma.PlacedModule{{Module: "web"}}})
|
putAssignments(w, mma.Assignments{V: keys.Version, Node: "g14", Modules: []mma.PlacedModule{{Module: "web"}}})
|
||||||
pkey2, _ := mmac.LegacyKey("g14")
|
|
||||||
w.putEntry(plc.PlacementBucket, pkey2, pl2, 1, time.Now())
|
|
||||||
p.unplacedFindings()
|
p.unplacedFindings()
|
||||||
if len(findings) != 0 {
|
if len(findings) != 0 {
|
||||||
t.Errorf("placed again: findings %v", findings)
|
t.Errorf("placed again: findings %v", findings)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
t.Run("1955/L7 (ADR 0571 §4): seat-holders is written from placement, only the keys a change moves, and answered by placement seats", func(t *testing.T) {
|
t.Run("1955/L7 (ADR 0571 §4): seat-holders is written from assignments, only the keys a change moves, and answered by assignments seats", func(t *testing.T) {
|
||||||
writes := 0
|
writes := 0
|
||||||
kv := &countingKV{memKV: memKV{values: map[string][]byte{}}, writes: &writes}
|
kv := &countingKV{memKV: memKV{values: map[string][]byte{}}, writes: &writes}
|
||||||
p := &passer{w: newWorld(), logf: t.Logf, kvs: kvSet{plc.SeatHoldersBucket: kv}}
|
p := &passer{w: newWorld(), logf: t.Logf, kvs: kvSet{plc.SeatHoldersBucket: kv}}
|
||||||
place := func(node string, holds ...mma.Holding) {
|
place := func(node string, holds ...mma.Holding) {
|
||||||
pl, err := mmac.EncodePlacement(mma.Placement{V: keys.Version, Node: node, Modules: []mma.PlacedModule{{Module: "web"}, {Module: "nats"}}, Holds: holds})
|
putAssignments(p.w, mma.Assignments{V: keys.Version, Node: node, Modules: []mma.PlacedModule{{Module: "web"}, {Module: "nats"}}, Holds: holds})
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
k, _ := mmac.LegacyKey(node)
|
|
||||||
p.w.putEntry(plc.PlacementBucket, k, pl, 1, time.Now())
|
|
||||||
}
|
}
|
||||||
place("ace", mma.Holding{Claim: "mesh-broker", Scope: "mesh", Module: "nats"}, mma.Holding{Claim: "node-shell", Scope: "node", Module: "web"})
|
place("ace", mma.Holding{Claim: "mesh-broker", Scope: "mesh", Module: "nats"}, mma.Holding{Claim: "node-shell", Scope: "node", Module: "web"})
|
||||||
place("g14", mma.Holding{Claim: "node-shell", Scope: "node", Module: "web"})
|
place("g14", mma.Holding{Claim: "node-shell", Scope: "node", Module: "web"})
|
||||||
@@ -249,7 +239,7 @@ func TestTheSeatsVerbs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
p.seatHolders(ctx)
|
p.seatHolders(ctx)
|
||||||
if writes != 3 {
|
if writes != 3 {
|
||||||
t.Errorf("an unchanged placement wrote %d keys", writes-3)
|
t.Errorf("an unchanged assignments wrote %d keys", writes-3)
|
||||||
}
|
}
|
||||||
place("g14") // g14 holds nothing now
|
place("g14") // g14 holds nothing now
|
||||||
p.seatHolders(ctx)
|
p.seatHolders(ctx)
|
||||||
@@ -262,68 +252,122 @@ func TestTheSeatsVerbs(t *testing.T) {
|
|||||||
t.Errorf("seats on ace = %+v, %v", out, err)
|
t.Errorf("seats on ace = %+v, %v", out, err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
t.Run("research 072 row 37: the form before is converted once, a node is written as its keys, and history reads both", func(t *testing.T) {
|
t.Run("research 072 row 37: a node is written as its keys, and history reads the node key", func(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
kv := &revKV{values: map[string][]byte{}, revs: map[string]uint64{}}
|
kv := &revKV{values: map[string][]byte{}, revs: map[string]uint64{}}
|
||||||
var logs []string
|
p := &passer{w: newWorld(), kvs: kvSet{plc.AssignmentsBucket: kv}, now: time.Now, logf: t.Logf}
|
||||||
p := &passer{w: newWorld(), kvs: kvSet{plc.PlacementBucket: kv}, now: time.Now,
|
first := mma.Assignments{Node: "anchor", Modules: []mma.PlacedModule{{Module: "web"}}}
|
||||||
logf: func(f string, a ...any) { logs = append(logs, fmt.Sprintf(f, a...)) }}
|
if _, err := p.writeNode(ctx, "anchor", &first); err != nil || strings.Join(kv.log, " ") != "create anchor.web create anchor.node" {
|
||||||
put := func(key string, value []byte, rev uint64) {
|
t.Fatalf("the first write = %v, %v", kv.log, err)
|
||||||
p.w.putEntry(plc.PlacementBucket, key, value, rev, time.Now())
|
|
||||||
}
|
}
|
||||||
was := mma.Placement{Node: "anchor", Modules: []mma.PlacedModule{{Module: "web"}}}
|
raw := kv.values["anchor.node"]
|
||||||
raw, _ := mmac.EncodePlacement(was)
|
// A write: the module keys, the node key (its commit), then the module key no longer placed.
|
||||||
put("placement.anchor", raw, kv.write("placement.anchor", raw))
|
after := mma.Assignments{Node: "anchor", Modules: []mma.PlacedModule{{Module: "shell", Ports: map[string]int64{"22": 2222}}}}
|
||||||
p.convertPlacement(ctx)
|
|
||||||
if strings.Join(kv.log, " ") != "create anchor.web create anchor.node" || !p.convertedAll ||
|
|
||||||
!strings.Contains(strings.Join(logs, "\n"), "anchor converted from placement.anchor") {
|
|
||||||
t.Fatalf("the conversion wrote %v, said %v", kv.log, logs)
|
|
||||||
}
|
|
||||||
p.convertPlacement(ctx)
|
|
||||||
if len(kv.log) != 2 {
|
|
||||||
t.Errorf("a converted node is converted again: %v", kv.log)
|
|
||||||
}
|
|
||||||
// A write: the module keys, the node key (its commit), the mirror, then the module key no longer placed.
|
|
||||||
after := mma.Placement{Node: "anchor", Modules: []mma.PlacedModule{{Module: "shell", Ports: map[string]int64{"22": 2222}}}}
|
|
||||||
kv.log = nil
|
kv.log = nil
|
||||||
rev, err := p.writeNode(ctx, "anchor", &after)
|
rev, err := p.writeNode(ctx, "anchor", &after)
|
||||||
if err != nil || strings.Join(kv.log, " ") != "create anchor.shell update anchor.node update placement.anchor delete anchor.web" || rev != kv.revs["anchor.node"] {
|
if err != nil || strings.Join(kv.log, " ") != "create anchor.shell update anchor.node delete anchor.web" || rev != kv.revs["anchor.node"] {
|
||||||
t.Fatalf("the write = %v, %d, %v", kv.log, rev, err)
|
t.Fatalf("the write = %v, %d, %v", kv.log, rev, err)
|
||||||
}
|
}
|
||||||
if got, _ := p.seatPlacements(); !mmac.SamePlacement(got["anchor"], after) {
|
if got, _ := p.seatAssignments(); !mmac.SameAssignments(got["anchor"], after) {
|
||||||
t.Errorf("the write is not read back at once: %+v", got)
|
t.Errorf("the write is not read back at once: %+v", got)
|
||||||
}
|
}
|
||||||
// A commit refused leaves every reader on what stood: the module key written before it carries the value it replaced.
|
// A commit refused leaves every reader on what stood: the module key written before it carries the value it replaced.
|
||||||
kv.refuse, kv.log = "anchor.node", nil
|
kv.refuse, kv.log = "anchor.node", nil
|
||||||
moved := mma.Placement{Node: "anchor", Modules: []mma.PlacedModule{{Module: "shell", Ports: map[string]int64{"22": 2223}}}}
|
moved := mma.Assignments{Node: "anchor", Modules: []mma.PlacedModule{{Module: "shell", Ports: map[string]int64{"22": 2223}}}}
|
||||||
if _, err := p.writeNode(ctx, "anchor", &moved); err == nil || strings.Join(kv.log, " ") != "update anchor.shell" {
|
if _, err := p.writeNode(ctx, "anchor", &moved); err == nil || strings.Join(kv.log, " ") != "update anchor.shell" {
|
||||||
t.Fatalf("a refused commit = %v, %v", kv.log, err)
|
t.Fatalf("a refused commit = %v, %v", kv.log, err)
|
||||||
}
|
}
|
||||||
fresh, err := mmac.PlacementsOf(kv.values)
|
fresh, err := mmac.AssignmentsOf(kv.values)
|
||||||
if err != nil || !mmac.SamePlacement(fresh["anchor"], after) {
|
if err != nil || !mmac.SameAssignments(fresh["anchor"], after) {
|
||||||
t.Errorf("a reader of the keys after a refused commit reads %+v, %v", fresh["anchor"], err)
|
t.Errorf("a reader of the keys after a refused commit reads %+v, %v", fresh["anchor"], err)
|
||||||
}
|
}
|
||||||
kv.refuse = ""
|
kv.refuse = ""
|
||||||
// The verb's history reads the form before, then the node key.
|
// The verb's history reads the node key.
|
||||||
st := memStore{mma.StatePlacement: &memKV{values: kv.values}}
|
st := memStore{mma.StateAssignments: &memKV{values: kv.values}}
|
||||||
hs := newSeatTools(st, nil)
|
hs := newSeatTools(st, nil)
|
||||||
hs.st = historyStore{memStore: st, revisions: map[string][]stdio.StateRevision{
|
hs.st = historyStore{memStore: st, revisions: map[string][]stdio.StateRevision{
|
||||||
"placement.anchor": {{Key: "placement.anchor", Value: raw, Revision: 1, Op: "put"}},
|
"anchor.node": {{Key: "anchor.node", Value: raw, Revision: 1, Op: "put"}, {Key: "anchor.node", Value: kv.values["anchor.node"], Revision: 5, Op: "put"}, {Key: "anchor.node", Revision: 9, Op: "del"}},
|
||||||
"anchor.node": {{Key: "anchor.node", Value: kv.values["anchor.node"], Revision: 5, Op: "put"}, {Key: "anchor.node", Revision: 9, Op: "del"}},
|
|
||||||
}}
|
}}
|
||||||
pa, err := hs.doPlacement(map[string]any{"node": "anchor", "history": "true"})
|
pa, err := hs.doAssignments(map[string]any{"node": "anchor", "history": "true"})
|
||||||
a, _ := pa.(placementAnswer)
|
a, _ := pa.(assignmentsAnswer)
|
||||||
if err != nil || len(a.Changes) != 3 || !slices.Equal(a.Changes[0].Change.Added, []string{"web"}) ||
|
if err != nil || len(a.Changes) != 3 || !slices.Equal(a.Changes[0].Change.Added, []string{"web"}) ||
|
||||||
!slices.Equal(a.Changes[1].Change.Added, []string{"shell"}) || !slices.Equal(a.Changes[1].Change.Removed, []string{"web"}) ||
|
!slices.Equal(a.Changes[1].Change.Added, []string{"shell"}) || !slices.Equal(a.Changes[1].Change.Removed, []string{"web"}) ||
|
||||||
!a.Changes[2].Deleted || !slices.Equal(a.Changes[2].Change.Removed, []string{"shell"}) {
|
!a.Changes[2].Deleted || !slices.Equal(a.Changes[2].Change.Removed, []string{"shell"}) {
|
||||||
t.Errorf("history = %+v, %v", pa, err)
|
t.Errorf("history = %+v, %v", pa, err)
|
||||||
}
|
}
|
||||||
// No placement: the mirror first, then the node key, then the module keys.
|
// No assignments: the node key, then the module keys.
|
||||||
kv.log = nil
|
kv.log = nil
|
||||||
if _, err := p.writeNode(ctx, "anchor", nil); err != nil || strings.Join(kv.log, " ") != "delete placement.anchor delete anchor.node delete anchor.shell" {
|
if _, err := p.writeNode(ctx, "anchor", nil); err != nil || strings.Join(kv.log, " ") != "delete anchor.node delete anchor.shell" {
|
||||||
t.Errorf("the delete = %v, %v", kv.log, err)
|
t.Errorf("the delete = %v, %v", kv.log, err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
t.Run("ADR 0641: the retired state is copied into assignments once, key by key, with no gap", func(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
web, _ := mmac.AssignmentsValues(mma.Assignments{Node: "anchor", Modules: []mma.PlacedModule{{Module: "web"}}})
|
||||||
|
shell, _ := mmac.AssignmentsValues(mma.Assignments{Node: "anchor", Modules: []mma.PlacedModule{{Module: "shell"}}})
|
||||||
|
for _, c := range []struct {
|
||||||
|
name string
|
||||||
|
old, present map[string][]byte // the retired state, and what assignments holds already
|
||||||
|
wantLog string // the writes into assignments, in order
|
||||||
|
wantValues map[string][]byte // assignments afterwards
|
||||||
|
wantLeft []string // what the retired state still holds
|
||||||
|
said copiedSaid // the event; none when zero
|
||||||
|
}{
|
||||||
|
{"an empty new state takes every key, the node key last, and the old form is dropped",
|
||||||
|
map[string][]byte{"anchor.node": web["anchor.node"], "anchor.web": web["anchor.web"], "placement.anchor": []byte(`{"whole":1}`)},
|
||||||
|
nil, "create anchor.web create anchor.node", web, nil, copiedSaid{Copied: 2, Dropped: 1}},
|
||||||
|
{"a key already in the new state is kept as it is",
|
||||||
|
map[string][]byte{"anchor.node": web["anchor.node"], "anchor.web": web["anchor.web"]},
|
||||||
|
map[string][]byte{"anchor.node": shell["anchor.node"], "anchor.shell": shell["anchor.shell"]},
|
||||||
|
"create anchor.web", map[string][]byte{"anchor.node": shell["anchor.node"], "anchor.shell": shell["anchor.shell"], "anchor.web": web["anchor.web"]}, nil, copiedSaid{Copied: 1, Present: 1}},
|
||||||
|
{"a key that fits no form is left and counted",
|
||||||
|
map[string][]byte{"anchor.node": web["anchor.node"], "anchor.web": web["anchor.web"], "a.b.c": []byte("x")},
|
||||||
|
nil, "create anchor.web create anchor.node", web, []string{"a.b.c"}, copiedSaid{Copied: 2, Skipped: 1}},
|
||||||
|
{"an empty old state copies nothing and says nothing", nil, nil, "", nil, nil, copiedSaid{}},
|
||||||
|
} {
|
||||||
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
next := &revKV{values: map[string][]byte{}, revs: map[string]uint64{}}
|
||||||
|
for k, v := range c.present {
|
||||||
|
next.write(k, v)
|
||||||
|
}
|
||||||
|
old := &retiredKV{values: map[string][]byte{}}
|
||||||
|
maps.Copy(old.values, c.old)
|
||||||
|
var logs []string
|
||||||
|
bus := &emitted{}
|
||||||
|
p := &passer{w: newWorld(), kvs: kvSet{retiredBucket: old, plc.AssignmentsBucket: next}, now: time.Now, seat: &seatActs{bus: bus},
|
||||||
|
logf: func(f string, a ...any) { logs = append(logs, fmt.Sprintf(f, a...)) }}
|
||||||
|
if !p.copyRetired(ctx) || !p.copied {
|
||||||
|
t.Fatalf("the copy did not end: %v", logs)
|
||||||
|
}
|
||||||
|
if got := strings.Join(next.log, " "); got != c.wantLog {
|
||||||
|
t.Errorf("writes = %q, want %q", got, c.wantLog)
|
||||||
|
}
|
||||||
|
if c.wantValues != nil && !maps.EqualFunc(next.values, c.wantValues, slices.Equal[[]byte]) {
|
||||||
|
t.Errorf("assignments = %v, want %v", next.values, c.wantValues)
|
||||||
|
}
|
||||||
|
if left := slices.Sorted(maps.Keys(old.values)); !slices.Equal(left, c.wantLeft) && (len(left) != 0 || len(c.wantLeft) != 0) {
|
||||||
|
t.Errorf("the retired state still holds %v, want %v", left, c.wantLeft)
|
||||||
|
}
|
||||||
|
if c.said == (copiedSaid{}) {
|
||||||
|
if len(bus.events) != 0 || len(logs) != 0 {
|
||||||
|
t.Errorf("a copy of nothing said %v, %v", bus.events, logs)
|
||||||
|
}
|
||||||
|
} else if len(bus.events) != 1 || bus.events[0] != c.said || len(logs) != 1 {
|
||||||
|
t.Errorf("said %v (journal %v), want the event %+v once", bus.events, logs, c.said)
|
||||||
|
}
|
||||||
|
// A restart finds the retired state emptied: nothing comes back that assignments has dropped since.
|
||||||
|
before := len(next.log)
|
||||||
|
p.copied = false
|
||||||
|
if !p.copyRetired(ctx) || len(next.log) != before || len(bus.events) > 1 {
|
||||||
|
t.Errorf("a second copy wrote %v, said %v", next.log[before:], bus.events)
|
||||||
|
}
|
||||||
|
// The copied keys are in the world at once, so the seat's next step reads them.
|
||||||
|
if got := len(p.w.read(plc.AssignmentsBucket)); got != c.said.Copied {
|
||||||
|
t.Errorf("%d copied keys are in the world, want %d", got, c.said.Copied)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
t.Run("1954/D1: a failed provision is the assigner's finding until it is provided", func(t *testing.T) {
|
t.Run("1954/D1: a failed provision is the assigner's finding until it is provided", func(t *testing.T) {
|
||||||
findings := map[string][]byte{}
|
findings := map[string][]byte{}
|
||||||
p := &passer{w: newWorld(), logf: func(string, ...any) {}, kvs: kvSet{buckets.Provisions: nil}, findings: assign.Findings{
|
p := &passer{w: newWorld(), logf: func(string, ...any) {}, kvs: kvSet{buckets.Provisions: nil}, findings: assign.Findings{
|
||||||
@@ -547,8 +591,11 @@ func (k *revKV) write(key string, value []byte) uint64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (k *revKV) Create(_ context.Context, key string, value []byte, _ ...jetstream.KVCreateOpt) (uint64, error) {
|
func (k *revKV) Create(_ context.Context, key string, value []byte, _ ...jetstream.KVCreateOpt) (uint64, error) {
|
||||||
if _, ok := k.values[key]; ok || key == k.refuse {
|
if _, ok := k.values[key]; ok {
|
||||||
return 0, errors.New("key exists")
|
return 0, jetstream.ErrKeyExists
|
||||||
|
}
|
||||||
|
if key == k.refuse {
|
||||||
|
return 0, errors.New("refused")
|
||||||
}
|
}
|
||||||
k.log = append(k.log, "create "+key)
|
k.log = append(k.log, "create "+key)
|
||||||
return k.write(key, value), nil
|
return k.write(key, value), nil
|
||||||
@@ -581,3 +628,71 @@ type historyStore struct {
|
|||||||
func (s historyStore) history(_, key string) ([]stdio.StateRevision, error) {
|
func (s historyStore) history(_, key string) ([]stdio.StateRevision, error) {
|
||||||
return s.revisions[key], nil
|
return s.revisions[key], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// putAssignments takes a node's assignments into the world as the keys of the state: a key per module, then the node key.
|
||||||
|
func putAssignments(w *world, a mma.Assignments) {
|
||||||
|
values, err := mmac.AssignmentsValues(a)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
for _, k := range slices.Sorted(maps.Keys(values)) {
|
||||||
|
w.putEntry(plc.AssignmentsBucket, k, values[k], 1, time.Now())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setAssignments holds a node's assignments in a state as its keys: a key per module, and the node key.
|
||||||
|
func setAssignments(kv *memKV, a mma.Assignments) {
|
||||||
|
values, err := mmac.AssignmentsValues(a)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
maps.Copy(kv.values, values)
|
||||||
|
}
|
||||||
|
|
||||||
|
// retiredKV is the retired state: its keys, its values, and deletes of them.
|
||||||
|
type retiredKV struct {
|
||||||
|
jetstream.KeyValue
|
||||||
|
values map[string][]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *retiredKV) Keys(context.Context, ...jetstream.WatchOpt) ([]string, error) {
|
||||||
|
if len(k.values) == 0 {
|
||||||
|
return nil, jetstream.ErrNoKeysFound
|
||||||
|
}
|
||||||
|
return slices.Sorted(maps.Keys(k.values)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *retiredKV) Get(_ context.Context, key string) (jetstream.KeyValueEntry, error) {
|
||||||
|
v, ok := k.values[key]
|
||||||
|
if !ok {
|
||||||
|
return nil, jetstream.ErrKeyNotFound
|
||||||
|
}
|
||||||
|
return retiredEntry{key: key, value: v}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *retiredKV) Delete(_ context.Context, key string, _ ...jetstream.KVDeleteOpt) error {
|
||||||
|
delete(k.values, key)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type retiredEntry struct {
|
||||||
|
jetstream.KeyValueEntry
|
||||||
|
key string
|
||||||
|
value []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e retiredEntry) Value() []byte { return e.value }
|
||||||
|
func (e retiredEntry) Operation() jetstream.KeyValueOp { return jetstream.KeyValuePut }
|
||||||
|
|
||||||
|
// emitted is a bus that keeps the module events it was asked to say.
|
||||||
|
type emitted struct {
|
||||||
|
caller
|
||||||
|
events []copiedSaid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *emitted) Emit(eventType string, body any) error {
|
||||||
|
if eventType == CopiedEvent {
|
||||||
|
e.events = append(e.events, body.(copiedSaid))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,16 +3,16 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"mesh-delivery-assigner/internal/assignment"
|
||||||
"mesh-delivery-assigner/internal/buckets"
|
"mesh-delivery-assigner/internal/buckets"
|
||||||
"mesh-delivery-assigner/internal/placement"
|
|
||||||
"slices"
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/nats-io/nats.go/jetstream"
|
"github.com/nats-io/nats.go/jetstream"
|
||||||
|
|
||||||
|
mma "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||||
mcc "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient"
|
mcc "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient"
|
||||||
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient"
|
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient"
|
||||||
mma "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
|
||||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||||
"git.novox.be/novox/mesh-sdk/go/process"
|
"git.novox.be/novox/mesh-sdk/go/process"
|
||||||
|
|
||||||
@@ -23,11 +23,11 @@ import (
|
|||||||
"git.novox.be/novox/mesh-delivery/sdk/graph"
|
"git.novox.be/novox/mesh-delivery/sdk/graph"
|
||||||
|
|
||||||
"mesh-delivery-assigner/internal/assign"
|
"mesh-delivery-assigner/internal/assign"
|
||||||
plc "mesh-delivery-assigner/internal/placement"
|
plc "mesh-delivery-assigner/internal/assignment"
|
||||||
)
|
)
|
||||||
|
|
||||||
// marksBucket is the assigner's state of what its views family said each state up to.
|
// marksBucket is the assigner's state of what its views family said each state up to.
|
||||||
var marksBucket = placement.Module + "_" + views.MarksState
|
var marksBucket = assignment.Module + "_" + views.MarksState
|
||||||
|
|
||||||
// viewedTool is one tool over one state of the assigner's.
|
// viewedTool is one tool over one state of the assigner's.
|
||||||
type viewedTool struct{ tool, state, words string }
|
type viewedTool struct{ tool, state, words string }
|
||||||
@@ -35,14 +35,14 @@ type viewedTool struct{ tool, state, words string }
|
|||||||
// firstViewTools are the assigner's first-view tools (ADR 0621 §1): each answers only its callers list
|
// firstViewTools are the assigner's first-view tools (ADR 0621 §1): each answers only its callers list
|
||||||
// (views.CallersEnv), which the manifest writes.
|
// (views.CallersEnv), which the manifest writes.
|
||||||
var firstViewTools = []viewedTool{
|
var firstViewTools = []viewedTool{
|
||||||
{"placement", mma.StatePlacement, "where each module is placed: the seat mesh-module-assigner's placement, by module."},
|
{"assignments", mma.StateAssignments, "where each module is placed: the seat mesh-module-assigner's assignments, by module."},
|
||||||
{"seat-holders", mma.StateSeatHolders, "who holds which seat on which node."},
|
{"seat-holders", mma.StateSeatHolders, "who holds which seat on which node."},
|
||||||
{"wants", "wants", "what each node should run: want.<node>.<module>, its fingerprint and the rule that gave it."},
|
{"wants", "wants", "what each node should run: want.<node>.<module>, its fingerprint and the rule that gave it."},
|
||||||
}
|
}
|
||||||
|
|
||||||
// readTools are the assigner's ordinary read tools, one per state its readers read (design 25 § 12.2).
|
// readTools are the assigner's ordinary read tools, one per state its readers read (design 25 § 12.2).
|
||||||
var readTools = []viewedTool{
|
var readTools = []viewedTool{
|
||||||
{"assigner_read_placement", mma.StatePlacement, "where each module is placed, by module."},
|
{"assigner_read_assignments", mma.StateAssignments, "where each module is placed, by module."},
|
||||||
{"assigner_read_seat_holders", mma.StateSeatHolders, "who holds which seat on which node."},
|
{"assigner_read_seat_holders", mma.StateSeatHolders, "who holds which seat on which node."},
|
||||||
{"assigner_read_wants", "wants", "what each node should run: want.<node>.<module>."},
|
{"assigner_read_wants", "wants", "what each node should run: want.<node>.<module>."},
|
||||||
{"assigner_read_findings", mcc.FindingsState, "the assigner's findings, which the condition keeper raises and clears."},
|
{"assigner_read_findings", mcc.FindingsState, "the assigner's findings, which the condition keeper raises and clears."},
|
||||||
@@ -52,7 +52,7 @@ var readTools = []viewedTool{
|
|||||||
|
|
||||||
// viewedStates are the states the views family says, each by its bucket: the six its readers read.
|
// viewedStates are the states the views family says, each by its bucket: the six its readers read.
|
||||||
var viewedStates = map[string]string{
|
var viewedStates = map[string]string{
|
||||||
mma.StatePlacement: plc.PlacementBucket,
|
mma.StateAssignments: plc.AssignmentsBucket,
|
||||||
mma.StateSeatHolders: plc.SeatHoldersBucket,
|
mma.StateSeatHolders: plc.SeatHoldersBucket,
|
||||||
"wants": buckets.Wants,
|
"wants": buckets.Wants,
|
||||||
mcc.FindingsState: assign.FindingsBucket,
|
mcc.FindingsState: assign.FindingsBucket,
|
||||||
@@ -66,13 +66,13 @@ func viewTools(getenv func(string) string, state func(string) views.Source, now
|
|||||||
marks := state(views.MarksState)
|
marks := state(views.MarksState)
|
||||||
var out []stdio.Tool
|
var out []stdio.Tool
|
||||||
for _, t := range firstViewTools {
|
for _, t := range firstViewTools {
|
||||||
out = append(out, views.FirstViewTool(placement.Module, t.tool, t.state, t.words, views.ParseCallers(getenv(views.CallersEnv(t.tool))), state(t.state), marks, now))
|
out = append(out, views.FirstViewTool(assignment.Module, t.tool, t.state, t.words, views.ParseCallers(getenv(views.CallersEnv(t.tool))), state(t.state), marks, now))
|
||||||
}
|
}
|
||||||
for _, t := range readTools {
|
for _, t := range readTools {
|
||||||
out = append(out, views.ReadTool(placement.Module, t.tool, t.state, t.words, state(t.state), marks, now))
|
out = append(out, views.ReadTool(assignment.Module, t.tool, t.state, t.words, state(t.state), marks, now))
|
||||||
}
|
}
|
||||||
// Its own first views, of the indexer's manifests, modules and graph (DLV-F3r, ADR 0631 §2): read, and asked again by hand.
|
// Its own first views, of the indexer's manifests, modules and graph (DLV-F3r, ADR 0631 §2): read, and asked again by hand.
|
||||||
return append(out, views.StatusTools(placement.Module, "assigner_first_views", "assigner_first_view_again", stdio.State(views.StatusState), meshhandactsclient.Recorder{Ask: stdio.Ask}, now)...)
|
return append(out, views.StatusTools(assignment.Module, "assigner_first_views", "assigner_first_view_again", stdio.State(views.StatusState), meshhandactsclient.Recorder{Ask: stdio.Ask}, now)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func keptSource(name string) views.Source { return views.KeptSource{State: stdio.State(name)} }
|
func keptSource(name string) views.Source { return views.KeptSource{State: stdio.State(name)} }
|
||||||
@@ -84,13 +84,13 @@ func startViews(ctx context.Context, js jetstream.JetStream, nc process.Subscrib
|
|||||||
if findings.Put == nil {
|
if findings.Put == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := viewFindings(findings).Put(views.TooLargeFinding(placement.Module, state, key, size, time.Now())); err != nil {
|
if err := viewFindings(findings).Put(views.TooLargeFinding(assignment.Module, state, key, size, time.Now())); err != nil {
|
||||||
logf("views: the finding of %s.%s too large is not written: %v", state, key, err)
|
logf("views: the finding of %s.%s too large is not written: %v", state, key, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
process.AwaitBucket(ctx, js, nc, marksBucket, "no change is said on the views family, so every reader waits on its first view", logf).Ready(func(mk jetstream.KeyValue) {
|
process.AwaitBucket(ctx, js, nc, marksBucket, "no change is said on the views family, so every reader waits on its first view", logf).Ready(func(mk jetstream.KeyValue) {
|
||||||
em := viewsEmitter(js, account, mk, findings, tooLarge, logf)
|
em := viewsEmitter(js, account, mk, findings, tooLarge, logf)
|
||||||
for _, state := range []string{"asks", "differences", mcc.FindingsState, mma.StatePlacement, mma.StateSeatHolders, "wants"} {
|
for _, state := range []string{"asks", "differences", mcc.FindingsState, mma.StateAssignments, mma.StateSeatHolders, "wants"} {
|
||||||
state := state
|
state := state
|
||||||
start := func(kv jetstream.KeyValue) {
|
start := func(kv jetstream.KeyValue) {
|
||||||
if err := em.Start(ctx, state, kv, errs); err != nil {
|
if err := em.Start(ctx, state, kv, errs); err != nil {
|
||||||
@@ -109,7 +109,7 @@ func startViews(ctx context.Context, js jetstream.JetStream, nc process.Subscrib
|
|||||||
// viewsEmitter is the assigner's emitter of its views family: a change not said past views.Bound is its finding
|
// viewsEmitter is the assigner's emitter of its views family: a change not said past views.Bound is its finding
|
||||||
// view-unsaid, in its findings (review round 2); with no findings state it is said in the log only.
|
// view-unsaid, in its findings (review round 2); with no findings state it is said in the log only.
|
||||||
func viewsEmitter(js jetstream.JetStream, account process.Account, mk jetstream.KeyValue, findings assign.Findings, tooLarge func(string, string, int), logf func(string, ...any)) views.Emitter {
|
func viewsEmitter(js jetstream.JetStream, account process.Account, mk jetstream.KeyValue, findings assign.Findings, tooLarge func(string, string, int), logf func(string, ...any)) views.Emitter {
|
||||||
em := views.Emitter{Owner: placement.Module, Subject: account.EventSubject, Publish: views.JetStreamPublish(js), Marks: mk, TooLarge: tooLarge, Logf: logf}
|
em := views.Emitter{Owner: assignment.Module, Subject: account.EventSubject, Publish: views.JetStreamPublish(js), Marks: mk, TooLarge: tooLarge, Logf: logf}
|
||||||
if findings.Put != nil && findings.Delete != nil {
|
if findings.Put != nil && findings.Delete != nil {
|
||||||
em.Findings = viewFindings(findings)
|
em.Findings = viewFindings(findings)
|
||||||
}
|
}
|
||||||
@@ -122,7 +122,7 @@ func viewFindings(f assign.Findings) views.FindingWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// firstViewsBucket is the assigner's own state of its first views (views.StatusState).
|
// firstViewsBucket is the assigner's own state of its first views (views.StatusState).
|
||||||
var firstViewsBucket = placement.Module + "_" + views.StatusState
|
var firstViewsBucket = assignment.Module + "_" + views.StatusState
|
||||||
|
|
||||||
// indexerViews are the assigner's first views of the indexer's manifests, modules and module graph (ADR 0615 §1,
|
// indexerViews are the assigner's first views of the indexer's manifests, modules and module graph (ADR 0615 §1,
|
||||||
// ADR 0621 §2, ADR 0631; DLV-F2r): asked at the start through the indexer's tools, then followed on indexer-views, asking once more on a gap;
|
// ADR 0621 §2, ADR 0631; DLV-F2r): asked at the start through the indexer's tools, then followed on indexer-views, asking once more on a gap;
|
||||||
@@ -154,7 +154,7 @@ func (iv *indexerViews) start(ctx context.Context, w *world, wake func(), settle
|
|||||||
if iv.findings.Put == nil {
|
if iv.findings.Put == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
f := views.UnansweredFinding(placement.Module, s, time.Now())
|
f := views.UnansweredFinding(assignment.Module, s, time.Now())
|
||||||
var err error
|
var err error
|
||||||
if raised {
|
if raised {
|
||||||
err = viewFindings(iv.findings).Put(f)
|
err = viewFindings(iv.findings).Put(f)
|
||||||
@@ -209,7 +209,7 @@ func (iv *indexerViews) start(ctx context.Context, w *world, wake func(), settle
|
|||||||
// notHeardWriter writes the assigner's finding of a family it does not hear into its findings (heard.Writer); with no
|
// notHeardWriter writes the assigner's finding of a family it does not hear into its findings (heard.Writer); with no
|
||||||
// findings state it is said in the log alone.
|
// findings state it is said in the log alone.
|
||||||
func notHeardWriter(findings assign.Findings, logf func(string, ...any)) heard.Writer {
|
func notHeardWriter(findings assign.Findings, logf func(string, ...any)) heard.Writer {
|
||||||
w := heard.Writer{Observer: placement.Module, Logf: logf, Put: findings.Put}
|
w := heard.Writer{Observer: assignment.Module, Logf: logf, Put: findings.Put}
|
||||||
if findings.Delete != nil {
|
if findings.Delete != nil {
|
||||||
w.Delete = func(key string) error {
|
w.Delete = func(key string) error {
|
||||||
if keys, err := findings.Keys(); err == nil && !slices.Contains(keys, key) {
|
if keys, err := findings.Keys(); err == nil && !slices.Contains(keys, key) {
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ import (
|
|||||||
|
|
||||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||||
meshhandacts "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshhandacts"
|
meshhandacts "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshhandacts"
|
||||||
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants"
|
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||||
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants"
|
||||||
|
|
||||||
mcc "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient"
|
mcc "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient"
|
||||||
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient"
|
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient"
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ import (
|
|||||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||||
wdc "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshwatchdogsclient"
|
wdc "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshwatchdogsclient"
|
||||||
mwc "git.novox.be/novox/mesh-contracts/placement/go/clients/meshwantsclient"
|
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||||
mdc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
mdc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||||
|
mwc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshwantsclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||||
"git.novox.be/novox/mesh-delivery/sdk/trunk"
|
"git.novox.be/novox/mesh-delivery/sdk/trunk"
|
||||||
@@ -23,12 +23,12 @@ import (
|
|||||||
|
|
||||||
"github.com/nats-io/nats.go/jetstream"
|
"github.com/nats-io/nats.go/jetstream"
|
||||||
|
|
||||||
mmac "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
mmac "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
|
||||||
mma "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
mma "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||||
|
|
||||||
"mesh-delivery-assigner/internal/assign"
|
"mesh-delivery-assigner/internal/assign"
|
||||||
plc "mesh-delivery-assigner/internal/placement"
|
plc "mesh-delivery-assigner/internal/assignment"
|
||||||
)
|
)
|
||||||
|
|
||||||
// world is what the assigner watched, as it stands now: every value by bucket and key, and the manifests read by
|
// world is what the assigner watched, as it stands now: every value by bucket and key, and the manifests read by
|
||||||
@@ -45,9 +45,9 @@ type world struct {
|
|||||||
meta map[string]entryMeta
|
meta map[string]entryMeta
|
||||||
// trunkBuilt is, by module, its newest done trunk build in the builder's log (putBuilt).
|
// trunkBuilt is, by module, its newest done trunk build in the builder's log (putBuilt).
|
||||||
trunkBuilt map[string]builds.BuildLogEntry
|
trunkBuilt map[string]builds.BuildLogEntry
|
||||||
// placement is the seat's state placement assembled node by node from its keys in either form (mesh-sdk
|
// assignments is the seat's state assignments assembled node by node from its keys in either form (mesh-sdk
|
||||||
// mmac.Keys; research 072 row 37): the one reader of its keys in the assigner.
|
// mmac.Keys; research 072 row 37): the one reader of its keys in the assigner.
|
||||||
placement *mmac.Keys
|
assignmentKeys *mmac.Keys
|
||||||
// sourceGroups is, by revision of the gate's source.main, the release (group) that moved it, from every revision its
|
// sourceGroups is, by revision of the gate's source.main, the release (group) that moved it, from every revision its
|
||||||
// history keeps (watched with history): a build's source revision names the release that carries it (ADR 0601 §1).
|
// history keeps (watched with history): a build's source revision names the release that carries it (ADR 0601 §1).
|
||||||
sourceGroups map[uint64]string
|
sourceGroups map[uint64]string
|
||||||
@@ -68,7 +68,7 @@ type parsed struct {
|
|||||||
|
|
||||||
func newWorld() *world {
|
func newWorld() *world {
|
||||||
return &world{values: map[string]map[string][]byte{}, manifests: map[string]parsed{}, healthy: map[string][]string{}, changes: map[string]uint64{},
|
return &world{values: map[string]map[string][]byte{}, manifests: map[string]parsed{}, healthy: map[string][]string{}, changes: map[string]uint64{},
|
||||||
meta: map[string]entryMeta{}, trunkBuilt: map[string]builds.BuildLogEntry{}, placement: mmac.NewKeys(),
|
meta: map[string]entryMeta{}, trunkBuilt: map[string]builds.BuildLogEntry{}, assignmentKeys: mmac.NewKeys(),
|
||||||
sourceGroups: map[uint64]string{}}
|
sourceGroups: map[uint64]string{}}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,8 +82,8 @@ func (w *world) putEntry(bucket, key string, value []byte, revision uint64, at t
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.meta[bucket+"|"+key] = entryMeta{revision: revision, at: at}
|
w.meta[bucket+"|"+key] = entryMeta{revision: revision, at: at}
|
||||||
if bucket == plc.PlacementBucket {
|
if bucket == plc.AssignmentsBucket {
|
||||||
_, _ = w.placement.Put(key, value, revision, false) // put took it already; this is its revision
|
_, _ = w.assignmentKeys.Put(key, value, revision, false) // put took it already; this is its revision
|
||||||
}
|
}
|
||||||
if bucket == buckets.Source && key == mdc.SourceMainKey {
|
if bucket == buckets.Source && key == mdc.SourceMainKey {
|
||||||
if s, err := mdc.DecodeSource(value); err == nil && s.Group != "" {
|
if s, err := mdc.DecodeSource(value); err == nil && s.Group != "" {
|
||||||
@@ -142,16 +142,16 @@ func (w *world) put(bucket, key string, value []byte) {
|
|||||||
w.changes[bucket]++ // the bucket's generation (bucketGen), a delete included
|
w.changes[bucket]++ // the bucket's generation (bucketGen), a delete included
|
||||||
if value == nil {
|
if value == nil {
|
||||||
delete(w.values[bucket], key)
|
delete(w.values[bucket], key)
|
||||||
if bucket == plc.PlacementBucket {
|
if bucket == plc.AssignmentsBucket {
|
||||||
_, _ = w.placement.Put(key, nil, 0, true)
|
_, _ = w.assignmentKeys.Put(key, nil, 0, true)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.values[bucket][key] = value
|
w.values[bucket][key] = value
|
||||||
w.changes[bucket+"|"+key]++
|
w.changes[bucket+"|"+key]++
|
||||||
if bucket == plc.PlacementBucket {
|
if bucket == plc.AssignmentsBucket {
|
||||||
// A key or value that does not read places nothing new on its node; the indexer's read tool says it.
|
// A key or value that does not read places nothing new on its node; the indexer's read tool says it.
|
||||||
_, _ = w.placement.Put(key, value, 0, false)
|
_, _ = w.assignmentKeys.Put(key, value, 0, false)
|
||||||
}
|
}
|
||||||
if bucket == buckets.Have {
|
if bucket == buckets.Have {
|
||||||
if h, err := nodeengineclient.DecodeHave(value); err == nil && h.Fp != "" && h.Phase == nodeengineclient.PhaseRunning && h.Health == nodeengineclient.HealthOK {
|
if h, err := nodeengineclient.DecodeHave(value); err == nil && h.Fp != "" && h.Phase == nodeengineclient.PhaseRunning && h.Health == nodeengineclient.HealthOK {
|
||||||
@@ -256,8 +256,8 @@ func (w *world) manifest(ctx context.Context, get getter, digest string) parsed
|
|||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
// assigned is each node's modules the seat mesh-module-assigner's placement names, in name order (ADR 0549 §5, ADR 0567 §8): the
|
// assigned is each node's modules the seat mesh-module-assigner's assignments names, in name order (ADR 0549 §5, ADR 0567 §8): the
|
||||||
// placement is the one source of where modules run. The controller's assignments are read no more (issue 774).
|
// assignments is the one source of where modules run. The controller's assignments are read no more (issue 774).
|
||||||
func (w *world) assigned() map[string][]string {
|
func (w *world) assigned() map[string][]string {
|
||||||
placed, _ := w.placed()
|
placed, _ := w.placed()
|
||||||
out := make(map[string][]string, len(placed))
|
out := make(map[string][]string, len(placed))
|
||||||
@@ -267,23 +267,23 @@ func (w *world) assigned() map[string][]string {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// placed is each node's modules in the seat's placement (ADR 0567 §1), and whether any placement key was read: a key
|
// placed is each node's modules in the seat's assignments (ADR 0567 §1), and whether any assignments key was read: a key
|
||||||
// that does not read places nothing on its node and is said by the indexer's own read tool.
|
// that does not read places nothing on its node and is said by the indexer's own read tool.
|
||||||
func (w *world) placed() (map[string][]string, bool) {
|
func (w *world) placed() (map[string][]string, bool) {
|
||||||
out := map[string][]string{}
|
out := map[string][]string{}
|
||||||
for node, p := range w.placements() {
|
for node, p := range w.assignments() {
|
||||||
out[node] = mmac.Assigned(p)
|
out[node] = mmac.Assigned(p)
|
||||||
}
|
}
|
||||||
return out, len(w.read(plc.PlacementBucket)) > 0
|
return out, len(w.read(plc.AssignmentsBucket)) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// placements is every node's placement, assembled from its keys in either form (mmac.Keys).
|
// assignments is every node's assignments, assembled from its keys in either form (mmac.Keys).
|
||||||
func (w *world) placements() map[string]mma.Placement {
|
func (w *world) assignments() map[string]mma.Assignments {
|
||||||
out, _ := w.placement.AllRevisions()
|
out, _ := w.assignmentKeys.AllRevisions()
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// placedNodes is every node the seat's placement names (ADR 0567 §1): those compose their own modules (ADR 0547 §2),
|
// placedNodes is every node the seat's assignments names (ADR 0567 §1): those compose their own modules (ADR 0547 §2),
|
||||||
// so their wants name the build alone and the seventh rule does not hold them.
|
// so their wants name the build alone and the seventh rule does not hold them.
|
||||||
func (w *world) placedNodes() map[string]bool {
|
func (w *world) placedNodes() map[string]bool {
|
||||||
placed, _ := w.placed()
|
placed, _ := w.placed()
|
||||||
@@ -309,8 +309,8 @@ func (w *world) trunk() map[string]meshcatalog.ModulesByModuleValue {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// records is each node's record in its own have key (`<node>.node`): the nodes a placement proposal is answered by,
|
// records is each node's record in its own have key (`<node>.node`): the nodes an assignments proposal is answered by,
|
||||||
// with those placement names (ADR 0593 §8).
|
// with those assignments names (ADR 0593 §8).
|
||||||
func (w *world) records() map[string]nodeengine.NodeRecord {
|
func (w *world) records() map[string]nodeengine.NodeRecord {
|
||||||
out := map[string]nodeengine.NodeRecord{}
|
out := map[string]nodeengine.NodeRecord{}
|
||||||
for key, value := range w.read(buckets.Have) {
|
for key, value := range w.read(buckets.Have) {
|
||||||
@@ -542,7 +542,7 @@ func (w *world) ownComposed(node string) (assign.ComposedDigest, bool) {
|
|||||||
Words: "the node composes its own"}, true
|
Words: "the node composes its own"}, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// unplacedNodes are the nodes whose engine has a have and that no placement names: they get no
|
// unplacedNodes are the nodes whose engine has a have and that no assignments names: they get no
|
||||||
// want, since nothing composes them.
|
// want, since nothing composes them.
|
||||||
func (w *world) unplacedNodes() []string {
|
func (w *world) unplacedNodes() []string {
|
||||||
placed := w.placedNodes()
|
placed := w.placedNodes()
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ go 1.26.0
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshhandacts v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshhandacts v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-sdk/go v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-sdk/go v0.0.0-00010101000000-000000000000
|
||||||
@@ -65,7 +65,7 @@ replace git.novox.be/novox/mesh-delivery/modules/mesh-delivery-assigner/asks =>
|
|||||||
require git.novox.be/novox/mesh-delivery/modules/mesh-delivery-view/viewstate v0.0.0-00010101000000-000000000000
|
require git.novox.be/novox/mesh-delivery/modules/mesh-delivery-view/viewstate v0.0.0-00010101000000-000000000000
|
||||||
replace git.novox.be/novox/mesh-delivery/modules/mesh-delivery-view/viewstate => ../mesh-delivery-view/viewstate
|
replace git.novox.be/novox/mesh-delivery/modules/mesh-delivery-view/viewstate => ../mesh-delivery-view/viewstate
|
||||||
|
|
||||||
require git.novox.be/novox/mesh-contracts/placement/go/clients/meshwantsclient v0.0.0-00010101000000-000000000000
|
require git.novox.be/novox/mesh-delivery/contracts/go/clients/meshwantsclient v0.0.0-00010101000000-000000000000
|
||||||
|
|
||||||
require git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient v0.0.0-00010101000000-000000000000
|
require git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient v0.0.0-00010101000000-000000000000
|
||||||
replace git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient => ../../contracts/go/clients/meshdeliverycheckerclient
|
replace git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient => ../../contracts/go/clients/meshdeliverycheckerclient
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ package assign
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"mesh-delivery-assigner/internal/placement"
|
"mesh-delivery-assigner/internal/assignment"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
mwc "git.novox.be/novox/mesh-contracts/placement/go/clients/meshwantsclient"
|
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||||
|
mwc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshwantsclient"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-assigner/asks"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-assigner/asks"
|
||||||
|
|
||||||
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
|
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
|
||||||
@@ -69,7 +69,7 @@ func RestoreAsk(key, node, module, fp, words string, now time.Time) asks.Ask {
|
|||||||
// words, under the assigner's own name (ADR 0437 clause 9), keyed by the held fingerprint. needsYou is true for a module holding
|
// words, under the assigner's own name (ADR 0437 clause 9), keyed by the held fingerprint. needsYou is true for a module holding
|
||||||
// data, whose restore ask waits for the operator.
|
// data, whose restore ask waits for the operator.
|
||||||
func HeldCondition(module, fp, node, words string, raised, needsYou bool) oc.Condition {
|
func HeldCondition(module, fp, node, words string, raised, needsYou bool) oc.Condition {
|
||||||
key := placement.Module + ".held." + module + "." + strings.TrimPrefix(fp, meshcatalogclient.SourcePrefix2)
|
key := assignment.Module + ".held." + module + "." + strings.TrimPrefix(fp, meshcatalogclient.SourcePrefix2)
|
||||||
subject := oc.ConditionSubject{Scope: "module", ID: module, Machine: node}
|
subject := oc.ConditionSubject{Scope: "module", ID: module, Machine: node}
|
||||||
if !raised {
|
if !raised {
|
||||||
return oc.Condition{Key: key, Subject: subject, State: occ.StateCleared}
|
return oc.Condition{Key: key, Subject: subject, State: occ.StateCleared}
|
||||||
@@ -92,7 +92,7 @@ func HeldCondition(module, fp, node, words string, raised, needsYou bool) oc.Con
|
|||||||
// the node keeps its want. It names the node, the module, fp and the have's words, keyed by node and module, and is cleared
|
// the node keeps its want. It names the node, the module, fp and the have's words, keyed by node and module, and is cleared
|
||||||
// (raised false) once the node no longer fails alone.
|
// (raised false) once the node no longer fails alone.
|
||||||
func NodeFailedCondition(node, module, fp string, f NodeFailure, raised bool) oc.Condition {
|
func NodeFailedCondition(node, module, fp string, f NodeFailure, raised bool) oc.Condition {
|
||||||
key := placement.Module + ".failed-alone." + node + "." + module
|
key := assignment.Module + ".failed-alone." + node + "." + module
|
||||||
subject := oc.ConditionSubject{Scope: "module", ID: module, Machine: node}
|
subject := oc.ConditionSubject{Scope: "module", ID: module, Machine: node}
|
||||||
if !raised {
|
if !raised {
|
||||||
return oc.Condition{Key: key, Subject: subject, State: occ.StateCleared,
|
return oc.Condition{Key: key, Subject: subject, State: occ.StateCleared,
|
||||||
@@ -115,7 +115,7 @@ func NodeFailedCondition(node, module, fp string, f NodeFailure, raised bool) oc
|
|||||||
// naming the module, the node and the rule holding its first want back with the rule's words, under the assigner's own name keyed by
|
// naming the module, the node and the rule holding its first want back with the rule's words, under the assigner's own name keyed by
|
||||||
// node and module, and cleared (raised false) when the node has a want for it again.
|
// node and module, and cleared (raised false) when the node has a want for it again.
|
||||||
func NeitherCondition(node, module string, h Holding, raised bool) oc.Condition {
|
func NeitherCondition(node, module string, h Holding, raised bool) oc.Condition {
|
||||||
key := placement.Module + ".neither." + node + "." + module
|
key := assignment.Module + ".neither." + node + "." + module
|
||||||
subject := oc.ConditionSubject{Scope: "module", ID: module, Machine: node}
|
subject := oc.ConditionSubject{Scope: "module", ID: module, Machine: node}
|
||||||
if !raised {
|
if !raised {
|
||||||
return oc.Condition{Key: key, Subject: subject, State: occ.StateCleared,
|
return oc.Condition{Key: key, Subject: subject, State: occ.StateCleared,
|
||||||
@@ -237,7 +237,7 @@ func BackupAsk(key, node, module, fp, digest, words string, asking int, now time
|
|||||||
// BackupCondition is the condition raised when a backup before an apply fails and the module is held on the node (ADR 0539 §16),
|
// BackupCondition is the condition raised when a backup before an apply fails and the module is held on the node (ADR 0539 §16),
|
||||||
// naming the node, the module and the backup's words; cleared under the same key.
|
// naming the node, the module and the backup's words; cleared under the same key.
|
||||||
func BackupCondition(node, module, fp, words string, raised bool) oc.Condition {
|
func BackupCondition(node, module, fp, words string, raised bool) oc.Condition {
|
||||||
key := placement.Module + ".backup." + node + "." + module + "." + strings.TrimPrefix(fp, meshcatalogclient.SourcePrefix2)
|
key := assignment.Module + ".backup." + node + "." + module + "." + strings.TrimPrefix(fp, meshcatalogclient.SourcePrefix2)
|
||||||
subject := oc.ConditionSubject{Scope: "module", ID: module, Machine: node}
|
subject := oc.ConditionSubject{Scope: "module", ID: module, Machine: node}
|
||||||
if !raised {
|
if !raised {
|
||||||
return oc.Condition{Key: key, Subject: subject, State: occ.StateCleared}
|
return oc.Condition{Key: key, Subject: subject, State: occ.StateCleared}
|
||||||
|
|||||||
@@ -2,16 +2,16 @@ package assign
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"mesh-delivery-assigner/internal/placement"
|
"mesh-delivery-assigner/internal/assignment"
|
||||||
"mesh-delivery-assigner/internal/puts"
|
"mesh-delivery-assigner/internal/puts"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
mwc "git.novox.be/novox/mesh-contracts/placement/go/clients/meshwantsclient"
|
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||||
|
mwc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshwantsclient"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-assigner/asks"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-assigner/asks"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-view/viewstate"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-view/viewstate"
|
||||||
|
|
||||||
@@ -148,7 +148,7 @@ func saidConditionsAreFindings(t *testing.T) {
|
|||||||
{HeldCondition("mail", fNew, "ace", "refused", true, true), LiftedCondition("mail", fNew), "module.mail." + strings.TrimPrefix(fNew, meshcatalogclient.SourcePrefix2) + ".held"},
|
{HeldCondition("mail", fNew, "ace", "refused", true, true), LiftedCondition("mail", fNew), "module.mail." + strings.TrimPrefix(fNew, meshcatalogclient.SourcePrefix2) + ".held"},
|
||||||
{NodeFailedCondition("ace", "mail", fNew, NodeFailure{Words: "x", Others: []string{"g14"}}, true), NodeFailedCondition("ace", "mail", fNew, NodeFailure{}, false), "module.ace.mail.failed-alone"},
|
{NodeFailedCondition("ace", "mail", fNew, NodeFailure{Words: "x", Others: []string{"g14"}}, true), NodeFailedCondition("ace", "mail", fNew, NodeFailure{}, false), "module.ace.mail.failed-alone"},
|
||||||
{NeitherCondition("ace", "mail", Holding{Rule: 3, Words: "w"}, true), NeitherCondition("ace", "mail", Holding{}, false), "module.ace.mail.neither"},
|
{NeitherCondition("ace", "mail", Holding{Rule: 3, Words: "w"}, true), NeitherCondition("ace", "mail", Holding{}, false), "module.ace.mail.neither"},
|
||||||
{SeatWaitsCondition([]string{"placement"}), SeatWaitsCondition(nil), "module.mesh-delivery-assigner.seat-states"},
|
{SeatWaitsCondition([]string{"assignments"}), SeatWaitsCondition(nil), "module.mesh-delivery-assigner.seat-states"},
|
||||||
{BackupCondition("ace", "mail", fNew, "disk full", true), BackupCondition("ace", "mail", fNew, "", false), "module.ace.mail." + strings.TrimPrefix(fNew, meshcatalogclient.SourcePrefix2) + ".backup"},
|
{BackupCondition("ace", "mail", fNew, "disk full", true), BackupCondition("ace", "mail", fNew, "", false), "module.ace.mail." + strings.TrimPrefix(fNew, meshcatalogclient.SourcePrefix2) + ".backup"},
|
||||||
} {
|
} {
|
||||||
puts, deletes, said := map[string]mcseat.FindingsByKeyValue{}, []string{}, 0
|
puts, deletes, said := map[string]mcseat.FindingsByKeyValue{}, []string{}, 0
|
||||||
@@ -162,7 +162,7 @@ func saidConditionsAreFindings(t *testing.T) {
|
|||||||
t.Fatalf("%s: %v", c.key, err)
|
t.Fatalf("%s: %v", c.key, err)
|
||||||
}
|
}
|
||||||
got, ok := puts[c.key]
|
got, ok := puts[c.key]
|
||||||
if !ok || got.Severity != c.raised.Severity || got.Headline != c.raised.Headline || got.Observer != placement.Module ||
|
if !ok || got.Severity != c.raised.Severity || got.Headline != c.raised.Headline || got.Observer != assignment.Module ||
|
||||||
got.Subject.Machine != c.raised.Subject.Machine || said != 0 {
|
got.Subject.Machine != c.raised.Subject.Machine || said != 0 {
|
||||||
t.Fatalf("%s: finding %+v (written %v), said %d", c.key, got, puts, said)
|
t.Fatalf("%s: finding %+v (written %v), said %d", c.key, got, puts, said)
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -1,7 +1,7 @@
|
|||||||
package assign
|
package assign
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"mesh-delivery-assigner/internal/placement"
|
"mesh-delivery-assigner/internal/assignment"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
|
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
|
||||||
@@ -11,8 +11,8 @@ import (
|
|||||||
// SeatWaitsCondition is the assigner's word on the seat mesh-module-assigner while a state of the seat is not bound (ADR 0567
|
// SeatWaitsCondition is the assigner's word on the seat mesh-module-assigner while a state of the seat is not bound (ADR 0567
|
||||||
// §1): raised naming each, cleared when none is left.
|
// §1): raised naming each, cleared when none is left.
|
||||||
func SeatWaitsCondition(unbound []string) oc.Condition {
|
func SeatWaitsCondition(unbound []string) oc.Condition {
|
||||||
key := placement.Module + ".seat-states"
|
key := assignment.Module + ".seat-states"
|
||||||
subject := oc.ConditionSubject{Scope: "module", ID: placement.Module}
|
subject := oc.ConditionSubject{Scope: "module", ID: assignment.Module}
|
||||||
if len(unbound) == 0 {
|
if len(unbound) == 0 {
|
||||||
return oc.Condition{Key: key, Subject: subject, State: occ.StateCleared, Resolved: "the seat mesh-module-assigner holds all its states."}
|
return oc.Condition{Key: key, Subject: subject, State: occ.StateCleared, Resolved: "the seat mesh-module-assigner holds all its states."}
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,7 @@ package assign
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"mesh-delivery-assigner/internal/placement"
|
"mesh-delivery-assigner/internal/assignment"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -21,13 +21,13 @@ import (
|
|||||||
// assigner no longer says it straight (never both).
|
// assigner no longer says it straight (never both).
|
||||||
|
|
||||||
// FindingsBucket is the assigner's findings.
|
// FindingsBucket is the assigner's findings.
|
||||||
var FindingsBucket = mcc.FindingsBucket(placement.Module)
|
var FindingsBucket = mcc.FindingsBucket(assignment.Module)
|
||||||
|
|
||||||
// FindingOf is a said condition as a finding: its key `<scope>.<id>.<kind>`, from the condition's own key
|
// FindingOf is a said condition as a finding: its key `<scope>.<id>.<kind>`, from the condition's own key
|
||||||
// `mesh-delivery-assigner.<kind>.<id…>` (the subject's id when the key names none), its words unchanged. raised is false for
|
// `mesh-delivery-assigner.<kind>.<id…>` (the subject's id when the key names none), its words unchanged. raised is false for
|
||||||
// a clear, whose key is to be deleted; value is nil then.
|
// a clear, whose key is to be deleted; value is nil then.
|
||||||
func FindingOf(c oc.Condition, at time.Time) (key string, value []byte, raised bool, err error) {
|
func FindingOf(c oc.Condition, at time.Time) (key string, value []byte, raised bool, err error) {
|
||||||
rest, ok := strings.CutPrefix(c.Key, placement.Module+".")
|
rest, ok := strings.CutPrefix(c.Key, assignment.Module+".")
|
||||||
if !ok || rest == "" {
|
if !ok || rest == "" {
|
||||||
return "", nil, false, fmt.Errorf("the condition %q is not the assigner's", c.Key)
|
return "", nil, false, fmt.Errorf("the condition %q is not the assigner's", c.Key)
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,7 @@ func FindingOf(c oc.Condition, at time.Time) (key string, value []byte, raised b
|
|||||||
}
|
}
|
||||||
f := mcc.Observation{Scope: scope, ID: id, Kind: kind, Machine: c.Subject.Machine, Severity: string(c.Severity), Summary: summary,
|
f := mcc.Observation{Scope: scope, ID: id, Kind: kind, Machine: c.Subject.Machine, Severity: string(c.Severity), Summary: summary,
|
||||||
Headline: c.Headline, Explanation: c.Explanation, Resolved: c.Resolved, Needs: c.Needs, Actions: actions,
|
Headline: c.Headline, Explanation: c.Explanation, Resolved: c.Resolved, Needs: c.Needs, Actions: actions,
|
||||||
Source: "assigner." + kind}.Finding(placement.Module, "", at)
|
Source: "assigner." + kind}.Finding(assignment.Module, "", at)
|
||||||
value, err = mcc.EncodeFinding(f)
|
value, err = mcc.EncodeFinding(f)
|
||||||
return key, value, true, err
|
return key, value, true, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ package assign
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"mesh-delivery-assigner/internal/assignment"
|
||||||
"mesh-delivery-assigner/internal/buckets"
|
"mesh-delivery-assigner/internal/buckets"
|
||||||
"mesh-delivery-assigner/internal/placement"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -20,7 +20,7 @@ import (
|
|||||||
// catalogue.Upgrade: a person's choice over the module's word, its irreplaceable data and the default, the bus as any module (ADR 0601 §1).
|
// catalogue.Upgrade: a person's choice over the module's word, its irreplaceable data and the default, the bus as any module (ADR 0601 §1).
|
||||||
|
|
||||||
// PoliciesBucket is the assigner's state `policies`.
|
// PoliciesBucket is the assigner's state `policies`.
|
||||||
var PoliciesBucket = placement.Module + "_" + buckets.PoliciesState
|
var PoliciesBucket = assignment.Module + "_" + buckets.PoliciesState
|
||||||
|
|
||||||
// DecodePolicies reads the policies state's values by key (the module); a value that cannot be read is left out, named.
|
// DecodePolicies reads the policies state's values by key (the module); a value that cannot be read is left out, named.
|
||||||
func DecodePolicies(values map[string][]byte) (map[string]catalogue.PersonPolicy, []string) {
|
func DecodePolicies(values map[string][]byte) (map[string]catalogue.PersonPolicy, []string) {
|
||||||
@@ -76,7 +76,7 @@ func ProvisionFailedFinding(key string, p meshprovisionerclient.Provision, at ti
|
|||||||
|
|
||||||
// encode is the observation as the assigner's finding at at, in the seat's layout.
|
// encode is the observation as the assigner's finding at at, in the seat's layout.
|
||||||
func encode(o mcc.Observation, at time.Time) ([]byte, error) {
|
func encode(o mcc.Observation, at time.Time) ([]byte, error) {
|
||||||
return mcc.EncodeFinding(o.Finding(placement.Module, "", at))
|
return mcc.EncodeFinding(o.Finding(assignment.Module, "", at))
|
||||||
}
|
}
|
||||||
|
|
||||||
func also(provider, consumer string) []string {
|
func also(provider, consumer string) []string {
|
||||||
@@ -109,7 +109,7 @@ func ProvisionMissingFinding(key string, bound time.Duration, at time.Time) ([]b
|
|||||||
Resolved: provision + " for " + module + " on " + node + " has its key", Source: "assigner." + KindProvisionMissing}, at)
|
Resolved: provision + " for " + module + " on " + node + " has its key", Source: "assigner." + KindProvisionMissing}, at)
|
||||||
}
|
}
|
||||||
|
|
||||||
// KindUnplaced is, from the lease write, a node that runs but no placement names (ADR 0571 §6).
|
// KindUnplaced is, from the lease write, a node that runs but no assignments names (ADR 0571 §6).
|
||||||
const KindUnplaced = "unplaced"
|
const KindUnplaced = "unplaced"
|
||||||
|
|
||||||
// UnplacedKey is its finding's key, `machine.<node>.unplaced`.
|
// UnplacedKey is its finding's key, `machine.<node>.unplaced`.
|
||||||
@@ -117,12 +117,12 @@ func UnplacedKey(node string) string {
|
|||||||
return mcc.Key(mcc.ScopeMachine, node, KindUnplaced)
|
return mcc.Key(mcc.ScopeMachine, node, KindUnplaced)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnplacedFinding is the finding of a node no placement names since the lease write.
|
// UnplacedFinding is the finding of a node no assignments names since the lease write.
|
||||||
func UnplacedFinding(node string, at time.Time) ([]byte, error) {
|
func UnplacedFinding(node string, at time.Time) ([]byte, error) {
|
||||||
summary := fmt.Sprintf("%s runs, but no placement names it: since the controller handed its lease away nothing composes it, "+
|
summary := fmt.Sprintf("%s runs, but no assignments names it: since the controller handed its lease away nothing composes it, "+
|
||||||
"so the assigner writes it no want", node)
|
"so the assigner writes it no want", node)
|
||||||
return encode(mcc.Observation{Scope: mcc.ScopeMachine, ID: node, Kind: KindUnplaced, Machine: node, Summary: summary,
|
return encode(mcc.Observation{Scope: mcc.ScopeMachine, ID: node, Kind: KindUnplaced, Machine: node, Summary: summary,
|
||||||
Headline: clipHeadline(node + " has no placement"), Needs: "place it with mesh-module-assigner.propose.",
|
Headline: clipHeadline(node + " has no assignments"), Needs: "place it with mesh-module-assigner.propose.",
|
||||||
Explanation: occ.NeedsYou + "place it with mesh-module-assigner.propose. " + summary + ".",
|
Explanation: occ.NeedsYou + "place it with mesh-module-assigner.propose. " + summary + ".",
|
||||||
Resolved: node + " is placed", Source: "assigner." + KindUnplaced}, at)
|
Resolved: node + " is placed", Source: "assigner." + KindUnplaced}, at)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||||
mwc "git.novox.be/novox/mesh-contracts/placement/go/clients/meshwantsclient"
|
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||||
|
mwc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshwantsclient"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-assigner/asks"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-assigner/asks"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||||
"mesh-delivery-assigner/internal/puts"
|
"mesh-delivery-assigner/internal/puts"
|
||||||
@@ -75,7 +75,7 @@ func StepPut(w PutWorld) PutNext {
|
|||||||
}
|
}
|
||||||
fp := short(p.FP)
|
fp := short(p.FP)
|
||||||
if !w.Assigned {
|
if !w.Assigned {
|
||||||
return say(puts.Refused, "%s is not assigned to %s: a put moves a module where it stands assigned (propose its placement first)", w.Module, w.Node)
|
return say(puts.Refused, "%s is not assigned to %s: a put moves a module where it stands assigned (propose its assignments first)", w.Module, w.Node)
|
||||||
}
|
}
|
||||||
switch w.Build {
|
switch w.Build {
|
||||||
case builds.BuildDone:
|
case builds.BuildDone:
|
||||||
|
|||||||
@@ -10,27 +10,27 @@ import (
|
|||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-assigner/asks"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-assigner/asks"
|
||||||
|
|
||||||
|
mmac "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
|
||||||
|
mma "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||||
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
|
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
|
||||||
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
|
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
|
||||||
mmac "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
|
||||||
mma "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
|
||||||
|
|
||||||
"mesh-delivery-assigner/internal/placement"
|
"mesh-delivery-assigner/internal/assignment"
|
||||||
plc "mesh-delivery-assigner/internal/placement"
|
plc "mesh-delivery-assigner/internal/assignment"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The seat mesh-module-assigner (novox/hq ADR 0567), held by the assigner beside its wants: placement is its state
|
// The seat mesh-module-assigner (novox/hq ADR 0567), held by the assigner beside its wants: assignments is its state
|
||||||
// `placement`, changed only by a proposal (§2) the nodes it touches answer (§3), judged (§4) and written on the
|
// `assignments`, changed only by a proposal (§2) the nodes it touches answer (§3), judged (§4) and written on the
|
||||||
// operator's Approve at the revision it was laid over (§5). What here decides; the tools and the passes do.
|
// operator's Approve at the revision it was laid over (§5). What here decides; the tools and the passes do.
|
||||||
|
|
||||||
// ProposeIn is what a proposal is laid over: its id, who and why, its changes; every node's current placement and the
|
// ProposeIn is what a proposal is laid over: its id, who and why, its changes; every node's current assignments and the
|
||||||
// revision of its key; which modules the indexer holds and which nodes have a record; and, for the seed, the export's
|
// revision of its key; which modules the indexer holds and which nodes have a record; and, for the seed, the export's
|
||||||
// digest.
|
// digest.
|
||||||
type ProposeIn struct {
|
type ProposeIn struct {
|
||||||
ID, By, Why string
|
ID, By, Why string
|
||||||
At time.Time
|
At time.Time
|
||||||
Changes []mma.ProposalChange
|
Changes []mma.ProposalChange
|
||||||
Current map[string]mma.Placement
|
Current map[string]mma.Assignments
|
||||||
Revisions map[string]uint64
|
Revisions map[string]uint64
|
||||||
Held func(module string) bool
|
Held func(module string) bool
|
||||||
Recorded func(node string) bool
|
Recorded func(node string) bool
|
||||||
@@ -39,11 +39,11 @@ type ProposeIn struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Propose lays a proposal over the current keys (§2): the proposal (state answering, each changed node's base revision),
|
// Propose lays a proposal over the current keys (§2): the proposal (state answering, each changed node's base revision),
|
||||||
// the laid placement of every node whose key it changes, and every finding, which refuses it whole: a change that does not fit its node, and what
|
// the laid assignments of every node whose key it changes, and every finding, which refuses it whole: a change that does not fit its node, and what
|
||||||
// CheckPlacements refuses of the result laid over every placement (two modules on one host port, a provider not placed,
|
// CheckAssignments refuses of the result laid over every assignments (two modules on one host port, a provider not placed,
|
||||||
// a seat held once held twice, two nodes with one address, two hubs, a module the indexer does not hold, a node with no
|
// a seat held once held twice, two nodes with one address, two hubs, a module the indexer does not hold, a node with no
|
||||||
// record). A key whose value the proposal leaves as it is changes nothing and is not laid.
|
// record). A key whose value the proposal leaves as it is changes nothing and is not laid.
|
||||||
func Propose(in ProposeIn) (mma.Proposal, map[string]*mma.Placement, []string) {
|
func Propose(in ProposeIn) (mma.Proposal, map[string]*mma.Assignments, []string) {
|
||||||
p := mma.Proposal{ID: in.ID, By: strings.TrimSpace(in.By), Why: strings.TrimSpace(in.Why), At: in.At.UTC(),
|
p := mma.Proposal{ID: in.ID, By: strings.TrimSpace(in.By), Why: strings.TrimSpace(in.Why), At: in.At.UTC(),
|
||||||
Changes: in.Changes, Warrant: in.Warrant, Base: map[string]int64{}, State: mmac.ProposalAnswering}
|
Changes: in.Changes, Warrant: in.Warrant, Base: map[string]int64{}, State: mmac.ProposalAnswering}
|
||||||
var findings []string
|
var findings []string
|
||||||
@@ -64,13 +64,13 @@ func Propose(in ProposeIn) (mma.Proposal, map[string]*mma.Placement, []string) {
|
|||||||
laid, lay := plc.LayChanges(in.Current, in.Changes)
|
laid, lay := plc.LayChanges(in.Current, in.Changes)
|
||||||
findings = append(findings, lay...)
|
findings = append(findings, lay...)
|
||||||
findings = append(findings, ControllerRefusals(in.Changes, laid)...)
|
findings = append(findings, ControllerRefusals(in.Changes, laid)...)
|
||||||
all := map[string]mma.Placement{}
|
all := map[string]mma.Assignments{}
|
||||||
for node, pl := range in.Current {
|
for node, pl := range in.Current {
|
||||||
all[node] = pl
|
all[node] = pl
|
||||||
}
|
}
|
||||||
out := map[string]*mma.Placement{}
|
out := map[string]*mma.Assignments{}
|
||||||
for node, pl := range laid {
|
for node, pl := range laid {
|
||||||
if before, ok := in.Current[node]; ok && mmac.SamePlacement(before, pl) {
|
if before, ok := in.Current[node]; ok && mmac.SameAssignments(before, pl) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
pl := pl
|
pl := pl
|
||||||
@@ -79,7 +79,7 @@ func Propose(in ProposeIn) (mma.Proposal, map[string]*mma.Placement, []string) {
|
|||||||
p.Base[node] = int64(in.Revisions[node])
|
p.Base[node] = int64(in.Revisions[node])
|
||||||
}
|
}
|
||||||
// A remove-node deletes the key of a joining node that never confirmed (the operator's decision of 2026-11-02 01:05): its
|
// A remove-node deletes the key of a joining node that never confirmed (the operator's decision of 2026-11-02 01:05): its
|
||||||
// candidate is no placement, and the checks across nodes read the mesh without it. LayChanges said why one cannot go.
|
// candidate is no assignments, and the checks across nodes read the mesh without it. LayChanges said why one cannot go.
|
||||||
for _, node := range plc.RemovedNodes(p) {
|
for _, node := range plc.RemovedNodes(p) {
|
||||||
if _, ok := in.Current[node]; !ok {
|
if _, ok := in.Current[node]; !ok {
|
||||||
continue
|
continue
|
||||||
@@ -89,7 +89,7 @@ func Propose(in ProposeIn) (mma.Proposal, map[string]*mma.Placement, []string) {
|
|||||||
p.Base[node] = int64(in.Revisions[node])
|
p.Base[node] = int64(in.Revisions[node])
|
||||||
}
|
}
|
||||||
if len(findings) == 0 && len(out) == 0 {
|
if len(findings) == 0 && len(out) == 0 {
|
||||||
findings = append(findings, "the changes leave every placement as it is")
|
findings = append(findings, "the changes leave every assignments as it is")
|
||||||
}
|
}
|
||||||
held, recorded := in.Held, in.Recorded
|
held, recorded := in.Held, in.Recorded
|
||||||
if held == nil {
|
if held == nil {
|
||||||
@@ -110,7 +110,7 @@ func Propose(in ProposeIn) (mma.Proposal, map[string]*mma.Placement, []string) {
|
|||||||
was := recorded
|
was := recorded
|
||||||
recorded = func(node string) bool { return joining[node] || was(node) }
|
recorded = func(node string) bool { return joining[node] || was(node) }
|
||||||
}
|
}
|
||||||
for _, r := range plc.CheckPlacements(all, held, recorded) {
|
for _, r := range plc.CheckAssignments(all, held, recorded) {
|
||||||
findings = append(findings, r)
|
findings = append(findings, r)
|
||||||
}
|
}
|
||||||
return p, out, findings
|
return p, out, findings
|
||||||
@@ -119,14 +119,14 @@ func Propose(in ProposeIn) (mma.Proposal, map[string]*mma.Placement, []string) {
|
|||||||
// ControllerModule is the controller's module, which nothing places again (issue 1126).
|
// ControllerModule is the controller's module, which nothing places again (issue 1126).
|
||||||
const ControllerModule = "mesh-controller"
|
const ControllerModule = "mesh-controller"
|
||||||
|
|
||||||
// ControllerEnded is why a placement of the controller is refused: the parts that acted only once its lease was handed
|
// ControllerEnded is why assignments of the controller is refused: the parts that acted only once its lease was handed
|
||||||
// (consumers, the user list, enrolment, dead letters) now act without reading the lease, so a controller placed again
|
// (consumers, the user list, enrolment, dead letters) now act without reading the lease, so a controller placed again
|
||||||
// would write beside them, silently.
|
// would write beside them, silently.
|
||||||
const ControllerEnded = "the controller ended by its unassignment on 2026-11-02; placing it again would make two writers (issue 1126)"
|
const ControllerEnded = "the controller ended by its unassignment on 2026-11-02; placing it again would make two writers (issue 1126)"
|
||||||
|
|
||||||
// ControllerRefusals names every change, and every node as laid, that places the controller: by a place, by a seed's
|
// ControllerRefusals names every change, and every node as laid, that places the controller: by a place, by a seed's
|
||||||
// whole placement, or in what the changes lay. None when nothing does.
|
// whole assignments, or in what the changes lay. None when nothing does.
|
||||||
func ControllerRefusals(changes []mma.ProposalChange, laid map[string]mma.Placement) []string {
|
func ControllerRefusals(changes []mma.ProposalChange, laid map[string]mma.Assignments) []string {
|
||||||
var out []string
|
var out []string
|
||||||
nodes := map[string]bool{}
|
nodes := map[string]bool{}
|
||||||
for i, c := range changes {
|
for i, c := range changes {
|
||||||
@@ -148,16 +148,16 @@ func ControllerRefusals(changes []mma.ProposalChange, laid map[string]mma.Placem
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write is one node a warrant writes (§5): the node, its node key `<node>.node` (the commit of the node's keys,
|
// Write is one node a warrant writes (§5): the node, its node key `<node>.node` (the commit of the node's keys,
|
||||||
// mesh-sdk mmac.Keys.Writes), the revision of the node's placement it is written at (0: it has none, so
|
// mesh-sdk mmac.Keys.Writes), the revision of the node's assignments it is written at (0: it has none, so
|
||||||
// it is created) and its value (nil: it is deleted).
|
// it is created) and its value (nil: it is deleted).
|
||||||
type Write struct {
|
type Write struct {
|
||||||
Node, Key string
|
Node, Key string
|
||||||
Base uint64
|
Base uint64
|
||||||
Value *mma.Placement
|
Value *mma.Assignments
|
||||||
}
|
}
|
||||||
|
|
||||||
// Writes is every key a proposal writes, by node order.
|
// Writes is every key a proposal writes, by node order.
|
||||||
func Writes(p mma.Proposal, laid map[string]*mma.Placement) ([]Write, error) {
|
func Writes(p mma.Proposal, laid map[string]*mma.Assignments) ([]Write, error) {
|
||||||
var out []Write
|
var out []Write
|
||||||
for _, node := range plc.Nodes(p) {
|
for _, node := range plc.Nodes(p) {
|
||||||
key, err := mmac.NodeKey(node)
|
key, err := mmac.NodeKey(node)
|
||||||
@@ -179,40 +179,40 @@ const (
|
|||||||
// CallClearedCondition clears the condition of a refused controller call a previous process raised, for its pair (node and
|
// CallClearedCondition clears the condition of a refused controller call a previous process raised, for its pair (node and
|
||||||
// module, or node and provision for a pin).
|
// module, or node and provision for a pin).
|
||||||
func CallClearedCondition(kind, node, module string) oc.Condition {
|
func CallClearedCondition(kind, node, module string) oc.Condition {
|
||||||
return oc.Condition{Key: placement.Module + "." + kind + "." + node + "." + module,
|
return oc.Condition{Key: assignment.Module + "." + kind + "." + node + "." + module,
|
||||||
Subject: oc.ConditionSubject{Scope: "module", ID: module, Machine: node}, State: occ.StateCleared,
|
Subject: oc.ConditionSubject{Scope: "module", ID: module, Machine: node}, State: occ.StateCleared,
|
||||||
Resolved: "the assigner calls no verb of the controller any more: placement is the one source."}
|
Resolved: "the assigner calls no verb of the controller any more: assignments is the one source."}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PlacementAct is the act an approved proposal's yes binds: writing exactly its changes at its base revisions.
|
// AssignmentsAct is the act an approved proposal's yes binds: writing exactly its changes at its base revisions.
|
||||||
func PlacementAct(p mma.Proposal) occ.Act {
|
func AssignmentsAct(p mma.Proposal) occ.Act {
|
||||||
return occ.Act{"placement": "write", "proposal": p.ID, "changes": plc.ChangesDigest(p)}
|
return occ.Act{"assignments": "write", "proposal": p.ID, "changes": plc.ChangesDigest(p)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PlacementAsking is a proposal's asking number: each judging that raises its ask again (again, a key moved) is
|
// AssignmentsAsking is a proposal's asking number: each judging that raises its ask again (again, a key moved) is
|
||||||
// another asking, under an id of its own.
|
// another asking, under an id of its own.
|
||||||
func PlacementAsking(p mma.Proposal, at time.Time) int {
|
func AssignmentsAsking(p mma.Proposal, at time.Time) int {
|
||||||
return int(at.Unix())
|
return int(at.Unix())
|
||||||
}
|
}
|
||||||
|
|
||||||
// PlacementAsk is the authorising ask about a proposal (§5): an Approve that binds its act, so only a channel that
|
// AssignmentsAsk is the authorising ask about a proposal (§5): an Approve that binds its act, so only a channel that
|
||||||
// proves who answered takes it, with the brief of the verdict by node (judge.Brief) as its explanation.
|
// proves who answered takes it, with the brief of the verdict by node (judge.Brief) as its explanation.
|
||||||
func PlacementAsk(key string, p mma.Proposal, brief string, asking int, now time.Time) (asks.Ask, error) {
|
func AssignmentsAsk(key string, p mma.Proposal, brief string, asking int, now time.Time) (asks.Ask, error) {
|
||||||
binds, err := occ.ActDigest(PlacementAct(p))
|
binds, err := occ.ActDigest(AssignmentsAct(p))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return asks.Ask{}, err
|
return asks.Ask{}, err
|
||||||
}
|
}
|
||||||
nodes := plc.Nodes(p)
|
nodes := plc.Nodes(p)
|
||||||
headline := fmt.Sprintf("Change placement on %s?", strings.Join(nodes, ", "))
|
headline := fmt.Sprintf("Change assignments on %s?", strings.Join(nodes, ", "))
|
||||||
if n := len([]rune(headline)); n > occ.HeadlineLength {
|
if n := len([]rune(headline)); n > occ.HeadlineLength {
|
||||||
headline = fmt.Sprintf("Change placement on %d machines?", len(nodes))
|
headline = fmt.Sprintf("Change assignments on %d machines?", len(nodes))
|
||||||
}
|
}
|
||||||
return assignerAsk(asking, oc.Ask{
|
return assignerAsk(asking, oc.Ask{
|
||||||
ID: meshdeliveryclient.AskingID(key, asking),
|
ID: meshdeliveryclient.AskingID(key, asking),
|
||||||
Headline: headline,
|
Headline: headline,
|
||||||
Explanation: brief,
|
Explanation: brief,
|
||||||
Options: []oc.Option{
|
Options: []oc.Option{
|
||||||
{ID: OptionYes, Label: "Approve", Does: "the placement of " + strings.Join(nodes, ", ") + " is written as proposed",
|
{ID: OptionYes, Label: "Approve", Does: "the assignments of " + strings.Join(nodes, ", ") + " is written as proposed",
|
||||||
Level: occ.Approve, Binds: binds},
|
Level: occ.Approve, Binds: binds},
|
||||||
{ID: OptionNo, Label: "Decline", Does: "nothing is written, and the proposal ends", Level: occ.Acknowledge},
|
{ID: OptionNo, Label: "Decline", Does: "nothing is written, and the proposal ends", Level: occ.Acknowledge},
|
||||||
},
|
},
|
||||||
@@ -225,7 +225,7 @@ func PlacementAsk(key string, p mma.Proposal, brief string, asking int, now time
|
|||||||
|
|
||||||
// Rest is what a proposal leaves to write once the keys of written are: its changes of the other nodes, to be laid again
|
// Rest is what a proposal leaves to write once the keys of written are: its changes of the other nodes, to be laid again
|
||||||
// over the keys as the write left them (§5: a key moved while it was written). False when nothing is left.
|
// over the keys as the write left them (§5: a key moved while it was written). False when nothing is left.
|
||||||
func Rest(p mma.Proposal, written map[string]*mma.Placement) (mma.Proposal, bool) {
|
func Rest(p mma.Proposal, written map[string]*mma.Assignments) (mma.Proposal, bool) {
|
||||||
rest := p
|
rest := p
|
||||||
rest.Changes, rest.Base = nil, map[string]int64{}
|
rest.Changes, rest.Base = nil, map[string]int64{}
|
||||||
for _, c := range p.Changes {
|
for _, c := range p.Changes {
|
||||||
|
|||||||
@@ -8,23 +8,23 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
mmac "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
|
||||||
|
mma "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||||
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
|
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
|
||||||
mmac "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
|
||||||
mma "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
|
||||||
|
|
||||||
plc "mesh-delivery-assigner/internal/placement"
|
plc "mesh-delivery-assigner/internal/assignment"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ADR 0567 §2, §5, §7, §8: what the seat's holder decides of a proposal: every refusal named at once, a valid one laid
|
// ADR 0567 §2, §5, §7, §8: what the seat's holder decides of a proposal: every refusal named at once, a valid one laid
|
||||||
// with the base revision of each key it changes, the writes at those revisions, the controller's calls that keep its
|
// with the base revision of each key it changes, the writes at those revisions, the controller's calls that keep its
|
||||||
// assignments in step, the authorising ask bound to the changes, and the export read as the controller answers it.
|
// assignments in step, the authorising ask bound to the changes, and the export read as the controller answers it.
|
||||||
func TestThePlacementSeatDecides(t *testing.T) {
|
func TestTheAssignmentsSeatDecides(t *testing.T) {
|
||||||
anchor := mma.Placement{V: keys.Version, Node: "anchor", Decided: mma.NodeDecided{Hub: true, Address: "10.0.0.1"},
|
anchor := mma.Assignments{V: keys.Version, Node: "anchor", Decided: mma.NodeDecided{Hub: true, Address: "10.0.0.1"},
|
||||||
Modules: []mma.PlacedModule{{Module: "store", Ports: map[string]int64{"5432": 5432}}, {Module: "web"}}}
|
Modules: []mma.PlacedModule{{Module: "store", Ports: map[string]int64{"5432": 5432}}, {Module: "web"}}}
|
||||||
laptop := mma.Placement{V: keys.Version, Node: "laptop", Decided: mma.NodeDecided{Address: "10.0.0.2"}, Modules: []mma.PlacedModule{{Module: "shell"}}}
|
laptop := mma.Assignments{V: keys.Version, Node: "laptop", Decided: mma.NodeDecided{Address: "10.0.0.2"}, Modules: []mma.PlacedModule{{Module: "shell"}}}
|
||||||
in := func(changes ...mma.ProposalChange) ProposeIn {
|
in := func(changes ...mma.ProposalChange) ProposeIn {
|
||||||
return ProposeIn{ID: "p-one", By: "operator@laptop", Why: "move web", At: time.Unix(1, 0), Changes: changes,
|
return ProposeIn{ID: "p-one", By: "operator@laptop", Why: "move web", At: time.Unix(1, 0), Changes: changes,
|
||||||
Current: map[string]mma.Placement{"anchor": anchor, "laptop": laptop}, Revisions: map[string]uint64{"anchor": 4, "laptop": 9},
|
Current: map[string]mma.Assignments{"anchor": anchor, "laptop": laptop}, Revisions: map[string]uint64{"anchor": 4, "laptop": 9},
|
||||||
Held: func(m string) bool { return m != "ghost" },
|
Held: func(m string) bool { return m != "ghost" },
|
||||||
Recorded: func(n string) bool { return n == "anchor" || n == "laptop" }}
|
Recorded: func(n string) bool { return n == "anchor" || n == "laptop" }}
|
||||||
}
|
}
|
||||||
@@ -43,14 +43,14 @@ func TestThePlacementSeatDecides(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
"a module not held": {in(mma.ProposalChange{Op: mmac.OpPlace, Node: "laptop", Module: "ghost"}), "the indexer holds no module ghost"},
|
"a module not held": {in(mma.ProposalChange{Op: mmac.OpPlace, Node: "laptop", Module: "ghost"}), "the indexer holds no module ghost"},
|
||||||
"a node with no record": {in(mma.ProposalChange{Op: mmac.OpPlace, Node: "stranger", Module: "web"}), "stranger is a node with no record"},
|
"a node with no record": {in(mma.ProposalChange{Op: mmac.OpPlace, Node: "stranger", Module: "web"}), "stranger is a node with no record"},
|
||||||
"nothing changes": {in(mma.ProposalChange{Op: mmac.OpPlace, Node: "anchor", Module: "web"}), "leave every placement as it is"},
|
"nothing changes": {in(mma.ProposalChange{Op: mmac.OpPlace, Node: "anchor", Module: "web"}), "leave every assignments as it is"},
|
||||||
"no why": {func() ProposeIn { i := in(move...); i.Why = " "; return i }(), "says why"},
|
"no why": {func() ProposeIn { i := in(move...); i.Why = " "; return i }(), "says why"},
|
||||||
"no caller": {func() ProposeIn { i := in(move...); i.By = ""; return i }(), "names no caller"},
|
"no caller": {func() ProposeIn { i := in(move...); i.By = ""; return i }(), "names no caller"},
|
||||||
"a seed by hand": {in(mma.ProposalChange{Op: mmac.OpSeed, Node: "laptop", Whole: &laptop}), "gone with adopt"},
|
"a seed by hand": {in(mma.ProposalChange{Op: mmac.OpSeed, Node: "laptop", Whole: &laptop}), "gone with adopt"},
|
||||||
// 1126: the controller ended by its unassignment; placed again it would be a second writer beside the parts that
|
// 1126: the controller ended by its unassignment; placed again it would be a second writer beside the parts that
|
||||||
// act without its lease.
|
// act without its lease.
|
||||||
"the controller placed again": {in(mma.ProposalChange{Op: mmac.OpPlace, Node: "laptop", Module: "mesh-controller"}), "ended by its unassignment"},
|
"the controller placed again": {in(mma.ProposalChange{Op: mmac.OpPlace, Node: "laptop", Module: "mesh-controller"}), "ended by its unassignment"},
|
||||||
"the controller seeded whole": {in(mma.ProposalChange{Op: mmac.OpSeed, Node: "anchor", Whole: &mma.Placement{V: keys.Version, Node: "anchor",
|
"the controller seeded whole": {in(mma.ProposalChange{Op: mmac.OpSeed, Node: "anchor", Whole: &mma.Assignments{V: keys.Version, Node: "anchor",
|
||||||
Decided: anchor.Decided, Modules: []mma.PlacedModule{{Module: "mesh-controller"}}}}), "two writers (issue 1126)"},
|
Decided: anchor.Decided, Modules: []mma.PlacedModule{{Module: "mesh-controller"}}}}), "two writers (issue 1126)"},
|
||||||
"a pin of nobody placed": {in(mma.ProposalChange{Op: mmac.OpPin, Node: "laptop", Provision: "db", Provider: &mma.Provider{Node: "laptop", Module: "store"}}), "not placed there"},
|
"a pin of nobody placed": {in(mma.ProposalChange{Op: mmac.OpPin, Node: "laptop", Provision: "db", Provider: &mma.Provider{Node: "laptop", Module: "store"}}), "not placed there"},
|
||||||
} {
|
} {
|
||||||
@@ -59,7 +59,7 @@ func TestThePlacementSeatDecides(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Every kind of change is taken: the placement is the one source, and the controller's verbs limit nothing (ADR 0567 §8).
|
// Every kind of change is taken: the assignments is the one source, and the controller's verbs limit nothing (ADR 0567 §8).
|
||||||
for _, c := range []mma.ProposalChange{
|
for _, c := range []mma.ProposalChange{
|
||||||
{Op: mmac.OpConfig, Node: "anchor", Module: "web", Values: map[string]any{"puid": 1}},
|
{Op: mmac.OpConfig, Node: "anchor", Module: "web", Values: map[string]any{"puid": 1}},
|
||||||
{Op: mmac.OpPlace, Node: "laptop", Module: "web", Ports: map[string]int64{"80": 8080}, Bindings: map[string]mma.Provider{"db": {Node: "anchor", Module: "store"}}},
|
{Op: mmac.OpPlace, Node: "laptop", Module: "web", Ports: map[string]int64{"80": 8080}, Bindings: map[string]mma.Provider{"db": {Node: "anchor", Module: "store"}}},
|
||||||
@@ -74,7 +74,7 @@ func TestThePlacementSeatDecides(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// A key moved while the warrant wrote: what is left to write is the other nodes' changes, to be laid and asked again.
|
// A key moved while the warrant wrote: what is left to write is the other nodes' changes, to be laid and asked again.
|
||||||
if rest, ok := Rest(p, map[string]*mma.Placement{"anchor": laid["anchor"]}); !ok || len(rest.Changes) != 1 || rest.Changes[0].Node != "laptop" ||
|
if rest, ok := Rest(p, map[string]*mma.Assignments{"anchor": laid["anchor"]}); !ok || len(rest.Changes) != 1 || rest.Changes[0].Node != "laptop" ||
|
||||||
len(rest.Base) != 1 || rest.State != mmac.ProposalAnswering || rest.Must != nil {
|
len(rest.Base) != 1 || rest.State != mmac.ProposalAnswering || rest.Must != nil {
|
||||||
t.Errorf("the rest = %+v, %v", rest, ok)
|
t.Errorf("the rest = %+v, %v", rest, ok)
|
||||||
}
|
}
|
||||||
@@ -95,13 +95,13 @@ func TestThePlacementSeatDecides(t *testing.T) {
|
|||||||
if err != nil || len(writes) != 2 || writes[0].Key != "anchor.node" || writes[0].Base != 4 || writes[1].Base != 9 {
|
if err != nil || len(writes) != 2 || writes[0].Key != "anchor.node" || writes[0].Base != 4 || writes[1].Base != 9 {
|
||||||
t.Fatalf("writes = %+v, %v", writes, err)
|
t.Fatalf("writes = %+v, %v", writes, err)
|
||||||
}
|
}
|
||||||
a, err := PlacementAsk("placement.p-one", p, "move web (proposed by operator@laptop)", 7, time.Unix(100, 0))
|
a, err := AssignmentsAsk("assignments.p-one", p, "move web (proposed by operator@laptop)", 7, time.Unix(100, 0))
|
||||||
if err != nil || occ.Check(mustAsk(t, a), time.Unix(100, 0)) != nil || mustAsk(t, a).Options[0].Level != occ.Approve || occ.Performs(mustAsk(t, a).Options[0], PlacementAct(p)) != nil {
|
if err != nil || occ.Check(mustAsk(t, a), time.Unix(100, 0)) != nil || mustAsk(t, a).Options[0].Level != occ.Approve || occ.Performs(mustAsk(t, a).Options[0], AssignmentsAct(p)) != nil {
|
||||||
t.Fatalf("the ask = %+v, %v", a, err)
|
t.Fatalf("the ask = %+v, %v", a, err)
|
||||||
}
|
}
|
||||||
q := p
|
q := p
|
||||||
q.Changes = append(slices.Clone(p.Changes), mma.ProposalChange{Op: mmac.OpUnpin, Node: "laptop", Provision: "x"})
|
q.Changes = append(slices.Clone(p.Changes), mma.ProposalChange{Op: mmac.OpUnpin, Node: "laptop", Provision: "x"})
|
||||||
if occ.Performs(mustAsk(t, a).Options[0], PlacementAct(q)) == nil {
|
if occ.Performs(mustAsk(t, a).Options[0], AssignmentsAct(q)) == nil {
|
||||||
t.Error("the ask's yes binds changes it was not asked about")
|
t.Error("the ask's yes binds changes it was not asked about")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||||
mwc "git.novox.be/novox/mesh-contracts/placement/go/clients/meshwantsclient"
|
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||||
|
mwc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshwantsclient"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-grants/grantsstate"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-grants/grantsstate"
|
||||||
"maps"
|
"maps"
|
||||||
"mesh-delivery-assigner/internal/puts"
|
"mesh-delivery-assigner/internal/puts"
|
||||||
@@ -90,9 +90,9 @@ type Module struct {
|
|||||||
// needs, ADR 0618 §1): a node's want is held only while that node's engine reads none of them (ADR 0624 §2). Nil: the
|
// needs, ADR 0618 §1): a node's want is held only while that node's engine reads none of them (ADR 0624 §2). Nil: the
|
||||||
// build names none, and holds nothing.
|
// build names none, and holds nothing.
|
||||||
FormNeeds []string
|
FormNeeds []string
|
||||||
// Self says whether node composes the module itself: the catalogue's placement names the node (ADR 0547 §2). It is the one
|
// Self says whether node composes the module itself: the catalogue's assignments names the node (ADR 0547 §2). It is the one
|
||||||
// source of a want's Self: every want the rules write or keep is derived from it (selfDerived), so a want written Self while
|
// source of a want's Self: every want the rules write or keep is derived from it (selfDerived), so a want written Self while
|
||||||
// the node was placed does not outlive the placement (issue 1126). Nil: no node does.
|
// the node was placed does not outlive the assignments (issue 1126). Nil: no node does.
|
||||||
Self func(node string) bool
|
Self func(node string) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -587,11 +587,11 @@ func Wants(m Module, n Nodes, wants map[string]mwc.Want, lastHealthy map[string]
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// selfDerived is next with its Self derived from the placement now (Module.Self), whichever rule made it, and whether it is
|
// selfDerived is next with its Self derived from the assignments now (Module.Self), whichever rule made it, and whether it is
|
||||||
// written. A placed node's want names the build alone (ADR 0547 §2). An unplaced node's want that a rule kept from the want
|
// written. A placed node's want names the build alone (ADR 0547 §2). An unplaced node's want that a rule kept from the want
|
||||||
// written while the node was placed (held at F, held back by rule 3 or 5, a data module asked about) names the controller's
|
// written while the node was placed (held at F, held back by rule 3 or 5, a data module asked about) names the controller's
|
||||||
// composition of the same fingerprint, asked as any move of a record module is (ADR 0539 §14): on 2026-10-27, after the
|
// composition of the same fingerprint, asked as any move of a record module is (ADR 0539 §14): on 2026-10-27, after the
|
||||||
// placement seed's revert, g14's systemd-resolved (held) and forticlient (rule 5), and shanks's forticlient (rule 3), kept
|
// assignments seed's revert, g14's systemd-resolved (held) and forticlient (rule 5), and shanks's forticlient (rule 3), kept
|
||||||
// their Self wants, which their node agents, no longer placed, could not compose (issue 1126). With no such composition, or
|
// their Self wants, which their node agents, no longer placed, could not compose (issue 1126). With no such composition, or
|
||||||
// while its ask waits, the want written stays and the node's holding says why.
|
// while its ask waits, the want written stays and the node's holding says why.
|
||||||
func (m Module) selfDerived(node string, before mwc.Want, had bool, next mwc.Want, n Nodes, out *Outcome) (mwc.Want, bool) {
|
func (m Module) selfDerived(node string, before mwc.Want, had bool, next mwc.Want, n Nodes, out *Outcome) (mwc.Want, bool) {
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ package assign
|
|||||||
import (
|
import (
|
||||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||||
mwc "git.novox.be/novox/mesh-contracts/placement/go/clients/meshwantsclient"
|
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||||
|
mwc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshwantsclient"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-assigner/asks"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-assigner/asks"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-grants/grantsstate"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-grants/grantsstate"
|
||||||
@@ -348,7 +348,7 @@ func TestEachRuleHoldsTheWantBack(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1126/AC-SW1, AC-SW2, AC-SW3: a want written Self while g14 was placed, kept by a rule after the placement is gone, names
|
// 1126/AC-SW1, AC-SW2, AC-SW3: a want written Self while g14 was placed, kept by a rule after the assignments is gone, names
|
||||||
// the controller's composition of the fingerprint the rule keeps (on 2026-10-27 g14's systemd-resolved, held, and forticlient,
|
// the controller's composition of the fingerprint the rule keeps (on 2026-10-27 g14's systemd-resolved, held, and forticlient,
|
||||||
// rule 5, kept their Self wants, which g14 could no longer compose); with no such composition, or while a record ask waits, it
|
// rule 5, kept their Self wants, which g14 could no longer compose); with no such composition, or while a record ask waits, it
|
||||||
// stays and is said; a placed node's want is Self with no composition.
|
// stays and is said; a placed node's want is Self with no composition.
|
||||||
|
|||||||
+23
-23
@@ -1,4 +1,4 @@
|
|||||||
package placement
|
package assignment
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -9,16 +9,16 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
client "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
client "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
|
||||||
seat "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
seat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||||
"git.novox.be/novox/mesh-sdk/go/keys"
|
"git.novox.be/novox/mesh-sdk/go/keys"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ADR 0567 §2, §4: what a change says, the holder's check of every placement, a proposal's changes laid over the keys,
|
// ADR 0567 §2, §4: what a change says, the holder's check of every assignments, a proposal's changes laid over the keys,
|
||||||
// its digests (held to the values the earlier holder's code made, in the field order it wrote), its refusals, and who holds
|
// its digests (held to the values the earlier holder's code made, in the field order it wrote), its refusals, and who holds
|
||||||
// which seat.
|
// which seat.
|
||||||
func TestPlacement(t *testing.T) {
|
func TestAssignments(t *testing.T) {
|
||||||
g14 := seat.Placement{Node: "g14", Revision: "abc",
|
g14 := seat.Assignments{Node: "g14", Revision: "abc",
|
||||||
Decided: seat.NodeDecided{Endpoint: "g14.example:51820", Site: "home", Address: "10.77.0.2", Account: "op", Taken: []string{"zsh"},
|
Decided: seat.NodeDecided{Endpoint: "g14.example:51820", Site: "home", Address: "10.77.0.2", Account: "op", Taken: []string{"zsh"},
|
||||||
Tunnel: &seat.FoundTunnel{Interface: "wg0", Config: "c", Port: 51820, Mtu: 1420, PublicKey: "K", Peers: []seat.FoundTunnelPeer{{PublicKey: "P", Address: "10.0.0.3"}}},
|
Tunnel: &seat.FoundTunnel{Interface: "wg0", Config: "c", Port: 51820, Mtu: 1420, PublicKey: "K", Peers: []seat.FoundTunnelPeer{{PublicKey: "P", Address: "10.0.0.3"}}},
|
||||||
PeerNames: map[string]string{"P": "pi"}},
|
PeerNames: map[string]string{"P": "pi"}},
|
||||||
@@ -29,13 +29,13 @@ func TestPlacement(t *testing.T) {
|
|||||||
Pins: map[string]seat.Provider{"shell": {Node: "g14", Module: "zsh"}},
|
Pins: map[string]seat.Provider{"shell": {Node: "g14", Module: "zsh"}},
|
||||||
Holds: []seat.Holding{{Claim: "node-shell", Scope: "node", Module: "zsh"}},
|
Holds: []seat.Holding{{Claim: "node-shell", Scope: "node", Module: "zsh"}},
|
||||||
}
|
}
|
||||||
novox := seat.Placement{Node: "novox", Decided: seat.NodeDecided{Hub: true, Address: "10.77.0.1"}, Modules: []seat.PlacedModule{{Module: "postgres", Ports: map[string]int64{"5432": 5432}}},
|
novox := seat.Assignments{Node: "novox", Decided: seat.NodeDecided{Hub: true, Address: "10.77.0.1"}, Modules: []seat.PlacedModule{{Module: "postgres", Ports: map[string]int64{"5432": 5432}}},
|
||||||
Holds: []seat.Holding{{Claim: "mesh-store", Scope: "mesh", Module: "postgres"}}}
|
Holds: []seat.Holding{{Claim: "mesh-store", Scope: "mesh", Module: "postgres"}}}
|
||||||
current := map[string]seat.Placement{"g14": g14, "novox": novox}
|
current := map[string]seat.Assignments{"g14": g14, "novox": novox}
|
||||||
const id = "p-20261028t183000-1a2b3c"
|
const id = "p-20261028t183000-1a2b3c"
|
||||||
// the enroller is whichever module holds the seat mesh-node-enroller: no module is named for it
|
// the enroller is whichever module holds the seat mesh-node-enroller: no module is named for it
|
||||||
const enroller = "enroll-lab"
|
const enroller = "enroll-lab"
|
||||||
enrollers := EnrollersOf(map[string]seat.Placement{"ace": {Node: "ace",
|
enrollers := EnrollersOf(map[string]seat.Assignments{"ace": {Node: "ace",
|
||||||
Holds: []seat.Holding{{Claim: "mesh-node-enroller", Scope: "mesh", Module: enroller}}}})
|
Holds: []seat.Holding{{Claim: "mesh-node-enroller", Scope: "mesh", Module: enroller}}}})
|
||||||
if !slices.Equal(enrollers, []string{enroller}) {
|
if !slices.Equal(enrollers, []string{enroller}) {
|
||||||
t.Fatalf("the holders of the enroller seat = %v", enrollers)
|
t.Fatalf("the holders of the enroller seat = %v", enrollers)
|
||||||
@@ -54,7 +54,7 @@ func TestPlacement(t *testing.T) {
|
|||||||
if got := ChangeOf(g14, pinned); !reflect.DeepEqual(got.Changed, []string{"app", "zsh"}) {
|
if got := ChangeOf(g14, pinned); !reflect.DeepEqual(got.Changed, []string{"app", "zsh"}) {
|
||||||
t.Errorf("a pin change changes every module on the node: %+v", got)
|
t.Errorf("a pin change changes every module on the node: %+v", got)
|
||||||
}
|
}
|
||||||
if got := ChangeOf(seat.Placement{}, novox); !reflect.DeepEqual(got.Added, []string{"postgres"}) || got.Node != "novox" {
|
if got := ChangeOf(seat.Assignments{}, novox); !reflect.DeepEqual(got.Added, []string{"postgres"}) || got.Node != "novox" {
|
||||||
t.Errorf("a new node: %+v", got)
|
t.Errorf("a new node: %+v", got)
|
||||||
}
|
}
|
||||||
c := []Change{{Node: "novox"}, {Node: "g14", Before: 1, After: 2}}
|
c := []Change{{Node: "novox"}, {Node: "g14", Before: 1, After: 2}}
|
||||||
@@ -64,18 +64,18 @@ func TestPlacement(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("the check of every placement", func(t *testing.T) {
|
t.Run("the check of every assignments", func(t *testing.T) {
|
||||||
known := func(m string) bool { return m != "ghost" }
|
known := func(m string) bool { return m != "ghost" }
|
||||||
enrolled := func(n string) bool { return n != "stranger" }
|
enrolled := func(n string) bool { return n != "stranger" }
|
||||||
if r := CheckPlacements(current, known, enrolled); len(r) != 0 {
|
if r := CheckAssignments(current, known, enrolled); len(r) != 0 {
|
||||||
t.Fatalf("a sound placement is refused: %v", r)
|
t.Fatalf("a sound assignments is refused: %v", r)
|
||||||
}
|
}
|
||||||
// A replicated mesh seat (ADR 0223) is held on two nodes; a seat that is not replicated is refused there.
|
// A replicated mesh seat (ADR 0223) is held on two nodes; a seat that is not replicated is refused there.
|
||||||
for name, refused := range map[string]bool{"mesh-dns-resolver": false, "mesh-broker": true} {
|
for name, refused := range map[string]bool{"mesh-dns-resolver": false, "mesh-broker": true} {
|
||||||
a := seat.Placement{Node: "ace", Modules: []seat.PlacedModule{{Module: "dns"}}, Holds: []seat.Holding{{Claim: name, Scope: "mesh", Module: "dns"}}}
|
a := seat.Assignments{Node: "ace", Modules: []seat.PlacedModule{{Module: "dns"}}, Holds: []seat.Holding{{Claim: name, Scope: "mesh", Module: "dns"}}}
|
||||||
b := a
|
b := a
|
||||||
b.Node = "novox"
|
b.Node = "novox"
|
||||||
r := CheckPlacements(map[string]seat.Placement{"ace": a, "novox": b}, known, enrolled)
|
r := CheckAssignments(map[string]seat.Assignments{"ace": a, "novox": b}, known, enrolled)
|
||||||
if (len(r) != 0) != refused {
|
if (len(r) != 0) != refused {
|
||||||
t.Errorf("%s on two nodes: refusals %v, want refused %v", name, r, refused)
|
t.Errorf("%s on two nodes: refusals %v, want refused %v", name, r, refused)
|
||||||
}
|
}
|
||||||
@@ -89,7 +89,7 @@ func TestPlacement(t *testing.T) {
|
|||||||
twice := novox
|
twice := novox
|
||||||
twice.Node = "stranger"
|
twice.Node = "stranger"
|
||||||
twice.Decided.Address = "10.77.0.2"
|
twice.Decided.Address = "10.77.0.2"
|
||||||
r := CheckPlacements(map[string]seat.Placement{"g14": clash, "novox": novox, "stranger": twice}, known, enrolled)
|
r := CheckAssignments(map[string]seat.Assignments{"g14": clash, "novox": novox, "stranger": twice}, known, enrolled)
|
||||||
for _, want := range []string{"holds no module ghost", "ghost and app are both published on host port 18080",
|
for _, want := range []string{"holds no module ghost", "ghost and app are both published on host port 18080",
|
||||||
"bound to ace/postgres", "pin of shell names g14/bash", "stranger is a node with no record",
|
"bound to ace/postgres", "pin of shell names g14/bash", "stranger is a node with no record",
|
||||||
"mesh-store, held once for the mesh, is held on novox and stranger",
|
"mesh-store, held once for the mesh, is held on novox and stranger",
|
||||||
@@ -113,7 +113,7 @@ func TestPlacement(t *testing.T) {
|
|||||||
t.Fatalf("laid %+v, %v", laid, findings)
|
t.Fatalf("laid %+v, %v", laid, findings)
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(client.Assigned(current["g14"]), []string{"zsh", "app"}) {
|
if !reflect.DeepEqual(client.Assigned(current["g14"]), []string{"zsh", "app"}) {
|
||||||
t.Error("laying changed the current placement")
|
t.Error("laying changed the current assignments")
|
||||||
}
|
}
|
||||||
if fresh, f := LayChanges(current, []seat.ProposalChange{{Op: client.OpPlace, Node: "pi", Module: "zsh"}}); len(f) != 0 || fresh["pi"].Node != "pi" {
|
if fresh, f := LayChanges(current, []seat.ProposalChange{{Op: client.OpPlace, Node: "pi", Module: "zsh"}}); len(f) != 0 || fresh["pi"].Node != "pi" {
|
||||||
t.Errorf("a node with no key: %+v, %v", fresh, f)
|
t.Errorf("a node with no key: %+v, %v", fresh, f)
|
||||||
@@ -125,7 +125,7 @@ func TestPlacement(t *testing.T) {
|
|||||||
"not placed": {seat.ProposalChange{Op: client.OpTakeOff, Node: "g14", Module: "nope"}, "not placed"},
|
"not placed": {seat.ProposalChange{Op: client.OpTakeOff, Node: "g14", Module: "nope"}, "not placed"},
|
||||||
"no such field": {seat.ProposalChange{Op: client.OpDecide, Node: "g14", Field: "colour", Value: "x"}, "no decided field"},
|
"no such field": {seat.ProposalChange{Op: client.OpDecide, Node: "g14", Field: "colour", Value: "x"}, "no decided field"},
|
||||||
"wrong type": {seat.ProposalChange{Op: client.OpDecide, Node: "g14", Field: "hub", Value: "x"}, "does not take"},
|
"wrong type": {seat.ProposalChange{Op: client.OpDecide, Node: "g14", Field: "hub", Value: "x"}, "does not take"},
|
||||||
"unknown op": {seat.ProposalChange{Op: "move", Node: "g14"}, "no change of placement"},
|
"unknown op": {seat.ProposalChange{Op: "move", Node: "g14"}, "no change of assignments"},
|
||||||
"pin of nobody": {seat.ProposalChange{Op: client.OpPin, Node: "g14", Provision: "x"}, "provider"},
|
"pin of nobody": {seat.ProposalChange{Op: client.OpPin, Node: "g14", Provision: "x"}, "provider"},
|
||||||
"not held": {seat.ProposalChange{Op: client.OpUnhold, Node: "g14", Module: "app", Claim: "x"}, "holds no"},
|
"not held": {seat.ProposalChange{Op: client.OpUnhold, Node: "g14", Module: "app", Claim: "x"}, "holds no"},
|
||||||
"bad port": {seat.ProposalChange{Op: client.OpPlace, Node: "g14", Module: "x", Ports: map[string]int64{"a": 1}}, "port"},
|
"bad port": {seat.ProposalChange{Op: client.OpPlace, Node: "g14", Module: "x", Ports: map[string]int64{"a": 1}}, "port"},
|
||||||
@@ -190,8 +190,8 @@ func TestPlacement(t *testing.T) {
|
|||||||
t.Errorf("the digest of %s is %s, and the earlier holder made %s", name, c.got, c.want)
|
t.Errorf("the digest of %s is %s, and the earlier holder made %s", name, c.got, c.want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ExportDigest(current) != ExportDigest(map[string]seat.Placement{"novox": novox, "g14": g14}) || ExportDigest(current) == ExportDigest(nil) {
|
if ExportDigest(current) != ExportDigest(map[string]seat.Assignments{"novox": novox, "g14": g14}) || ExportDigest(current) == ExportDigest(nil) {
|
||||||
t.Error("the export digest depends on order or not on the placements")
|
t.Error("the export digest depends on order or not on the assignments")
|
||||||
}
|
}
|
||||||
// 774/S42/AC1 (ADR 0587 §7): an add-node carries its enrolment's warrant naming its node and address, as its one
|
// 774/S42/AC1 (ADR 0587 §7): an add-node carries its enrolment's warrant naming its node and address, as its one
|
||||||
// change, from the enroller alone; a warrant naming another node or address is refused.
|
// change, from the enroller alone; a warrant naming another node or address is refused.
|
||||||
@@ -267,7 +267,7 @@ func TestPlacement(t *testing.T) {
|
|||||||
AddNodeRefusal(remove, enroller, enrollers) != "" || !slices.Equal(RemovedNodes(remove), []string{"lab"}) {
|
AddNodeRefusal(remove, enroller, enrollers) != "" || !slices.Equal(RemovedNodes(remove), []string{"lab"}) {
|
||||||
t.Errorf("a remove-node: %v, %q", err, AddNodeRefusal(remove, "mesh-delivery-assigner", enrollers))
|
t.Errorf("a remove-node: %v, %q", err, AddNodeRefusal(remove, "mesh-delivery-assigner", enrollers))
|
||||||
}
|
}
|
||||||
joined := map[string]seat.Placement{"lab": {V: keys.Version, Node: "lab", Decided: seat.NodeDecided{Address: "10.0.0.7"}, Modules: []seat.PlacedModule{}}}
|
joined := map[string]seat.Assignments{"lab": {V: keys.Version, Node: "lab", Decided: seat.NodeDecided{Address: "10.0.0.7"}, Modules: []seat.PlacedModule{}}}
|
||||||
if laid, f := LayChanges(joined, remove.Changes); len(f) != 0 || len(laid) != 0 {
|
if laid, f := LayChanges(joined, remove.Changes); len(f) != 0 || len(laid) != 0 {
|
||||||
t.Errorf("a joining node removed: %v, %v", laid, f)
|
t.Errorf("a joining node removed: %v, %v", laid, f)
|
||||||
}
|
}
|
||||||
@@ -301,9 +301,9 @@ func TestPlacement(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// ADR 0571 clause 4: who holds which seat, from placement; a node seat by its node.
|
// ADR 0571 clause 4: who holds which seat, from assignments; a node seat by its node.
|
||||||
t.Run("who holds which seat", func(t *testing.T) {
|
t.Run("who holds which seat", func(t *testing.T) {
|
||||||
held := SeatHoldersOf(map[string]seat.Placement{
|
held := SeatHoldersOf(map[string]seat.Assignments{
|
||||||
"ace": {Node: "ace", Holds: []seat.Holding{{Claim: "mesh-dns-resolver", Scope: "mesh", Module: "dnsmasq"}, {Claim: "node-files", Scope: "node", Module: "mesh-files"}}},
|
"ace": {Node: "ace", Holds: []seat.Holding{{Claim: "mesh-dns-resolver", Scope: "mesh", Module: "dnsmasq"}, {Claim: "node-files", Scope: "node", Module: "mesh-files"}}},
|
||||||
"novox": {Node: "novox", Holds: []seat.Holding{{Claim: "mesh-dns-resolver", Scope: "mesh", Module: "dnsmasq"}, {Claim: "mesh-broker", Scope: "mesh", Module: "nats"},
|
"novox": {Node: "novox", Holds: []seat.Holding{{Claim: "mesh-dns-resolver", Scope: "mesh", Module: "dnsmasq"}, {Claim: "mesh-broker", Scope: "mesh", Module: "nats"},
|
||||||
{Claim: "photo-gallery", Scope: "mesh", Module: "photos"}}},
|
{Claim: "photo-gallery", Scope: "mesh", Module: "photos"}}},
|
||||||
+13
-13
@@ -1,17 +1,17 @@
|
|||||||
package placement
|
package assignment
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
client "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
client "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
|
||||||
seat "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
seat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||||
"git.novox.be/novox/mesh-sdk/go/keys"
|
"git.novox.be/novox/mesh-sdk/go/keys"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Placement candidates (novox/hq ADR 0567 §3): for each node a proposal must hear from, the holder writes one key
|
// Assignments candidates (novox/hq ADR 0567 §3): for each node a proposal must hear from, the holder writes one key
|
||||||
// `<proposal>.<node>` in its state `candidates`: the node's placement with the proposal laid over it for a node whose key
|
// `<proposal>.<node>` in its state `candidates`: the node's assignments with the proposal laid over it for a node whose key
|
||||||
// it changes, or the node's current key for one whose composition it changes only through another node (Reached). The
|
// it changes, or the node's current key for one whose composition it changes only through another node (Reached). The
|
||||||
// layouts are the seat's contract's (CandidatesByProposalNodeValue); what refuses a candidate is here.
|
// layouts are the seat's contract's (CandidatesByProposalNodeValue); what refuses a candidate is here.
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ func CandidateKey(proposal, node string) (string, error) {
|
|||||||
func ParseCandidateKey(key string) (proposal, node string, err error) {
|
func ParseCandidateKey(key string) (proposal, node string, err error) {
|
||||||
proposal, node, ok := strings.Cut(key, ".")
|
proposal, node, ok := strings.Cut(key, ".")
|
||||||
if !ok || keys.CheckName("node", node) != nil || client.CheckProposalID(proposal) != nil {
|
if !ok || keys.CheckName("node", node) != nil || client.CheckProposalID(proposal) != nil {
|
||||||
return "", "", fmt.Errorf("placement: %q is not a placement candidate key, <proposal>.<node>", key)
|
return "", "", fmt.Errorf("assignments: %q is not an assignments candidate key, <proposal>.<node>", key)
|
||||||
}
|
}
|
||||||
return proposal, node, nil
|
return proposal, node, nil
|
||||||
}
|
}
|
||||||
@@ -40,8 +40,8 @@ func checkCandidate(c seat.CandidatesByProposalNodeValue) error {
|
|||||||
if err := client.CheckProposalID(c.Proposal); err != nil {
|
if err := client.CheckProposalID(c.Proposal); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if c.Placement != nil {
|
if c.Assignments != nil {
|
||||||
return client.CheckPlacement(*c.Placement)
|
return client.CheckAssignments(*c.Assignments)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -52,15 +52,15 @@ func EncodeCandidate(c seat.CandidatesByProposalNodeValue) ([]byte, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
c.V = keys.Version
|
c.V = keys.Version
|
||||||
if c.Placement != nil {
|
if c.Assignments != nil {
|
||||||
p := *c.Placement
|
p := *c.Assignments
|
||||||
p.V = keys.Version
|
p.V = keys.Version
|
||||||
c.Placement = &p
|
c.Assignments = &p
|
||||||
}
|
}
|
||||||
return json.Marshal(c)
|
return json.Marshal(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeCandidate reads a candidate; it refuses an unknown version, a proposal id that is not one, and a placement the
|
// DecodeCandidate reads a candidate; it refuses an unknown version, a proposal id that is not one, and assignments the
|
||||||
// client refuses.
|
// client refuses.
|
||||||
func DecodeCandidate(data []byte) (seat.CandidatesByProposalNodeValue, error) {
|
func DecodeCandidate(data []byte) (seat.CandidatesByProposalNodeValue, error) {
|
||||||
if err := keys.CheckVersion(data); err != nil {
|
if err := keys.CheckVersion(data); err != nil {
|
||||||
@@ -68,7 +68,7 @@ func DecodeCandidate(data []byte) (seat.CandidatesByProposalNodeValue, error) {
|
|||||||
}
|
}
|
||||||
c, err := seat.DecodeCandidatesByProposalNodeValue(data)
|
c, err := seat.DecodeCandidatesByProposalNodeValue(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return seat.CandidatesByProposalNodeValue{}, fmt.Errorf("placement: a candidate does not fit its shape: %w", err)
|
return seat.CandidatesByProposalNodeValue{}, fmt.Errorf("assignments: a candidate does not fit its shape: %w", err)
|
||||||
}
|
}
|
||||||
if err := checkCandidate(c); err != nil {
|
if err := checkCandidate(c); err != nil {
|
||||||
return seat.CandidatesByProposalNodeValue{}, err
|
return seat.CandidatesByProposalNodeValue{}, err
|
||||||
+21
-21
@@ -1,9 +1,9 @@
|
|||||||
// Package placement is the assigner's own check of a placement and of a proposal that changes it (novox/hq ADR 0567 §2,
|
// Package assignments is the assigner's own check of assignments and of a proposal that changes it (novox/hq ADR 0567 §2,
|
||||||
// ADR 0634 §1 on where a seat's code goes): what the holder of the seat mesh-module-assigner decides before it writes
|
// ADR 0634 §1 on where a seat's code goes): what the holder of the seat mesh-module-assigner decides before it writes
|
||||||
// anything. The layouts it works on are the seat's contract's (package meshmoduleassigner); what every reader of the
|
// anything. The layouts it works on are the seat's contract's (package meshmoduleassigner); what every reader of the
|
||||||
// placement shares (the keys assembled into a node's placement, the node key's digest, who holds a seat) is the seat's
|
// assignments shares (the keys assembled into a node's assignments, the node key's digest, who holds a seat) is the seat's
|
||||||
// client's (package meshmoduleassignerclient). Nothing here is read by another module.
|
// client's (package meshmoduleassignerclient). Nothing here is read by another module.
|
||||||
package placement
|
package assignment
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -11,8 +11,8 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
client "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
client "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
|
||||||
seat "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
seat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||||
"git.novox.be/novox/mesh-sdk/go/keys"
|
"git.novox.be/novox/mesh-sdk/go/keys"
|
||||||
)
|
)
|
||||||
@@ -25,19 +25,19 @@ var KeptApart = []struct{ A, B, Record string }{
|
|||||||
{A: "mesh-watcher", B: "mesh-self-check", Record: "ADR 0227 §6, ADR 0564 clause 6"},
|
{A: "mesh-watcher", B: "mesh-self-check", Record: "ADR 0227 §6, ADR 0564 clause 6"},
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckPlacements is the holder's check of a proposal laid over every placement (ADR 0567 §2), over the placements by
|
// CheckAssignments is the holder's check of a proposal laid over every assignments (ADR 0567 §2), over the assignments by
|
||||||
// node, the modules that are known and the nodes with a record. It returns every refusal, in node order, never only the
|
// node, the modules that are known and the nodes with a record. It returns every refusal, in node order, never only the
|
||||||
// first: a placement naming a node not enrolled or a module that is not known (held by the indexer, or provided by the
|
// first: assignments naming a node not enrolled or a module that is not known (held by the indexer, or provided by the
|
||||||
// mesh itself: the caller's `known` says both); two modules of one node on the same host port; a pin, binding or holding
|
// mesh itself: the caller's `known` says both); two modules of one node on the same host port; a pin, binding or holding
|
||||||
// naming a provider that is not placed where it says; a seat of scope mesh held twice, unless the seat is replicated
|
// naming a provider that is not placed where it says; a seat of scope mesh held twice, unless the seat is replicated
|
||||||
// (catalogue.Seat's Replicated, ADR 0223), which is held once per node; two nodes with one address on the private
|
// (catalogue.Seat's Replicated, ADR 0223), which is held once per node; two nodes with one address on the private
|
||||||
// network; more than one hub; two modules KeptApart on one node. A host port a manifest declares without a number and a
|
// network; more than one hub; two modules KeptApart on one node. A host port a manifest declares without a number and a
|
||||||
// requirement left unbound are the composition's to find (its ForeseenPorts and Unbound).
|
// requirement left unbound are the composition's to find (its ForeseenPorts and Unbound).
|
||||||
func CheckPlacements(placements map[string]seat.Placement, known func(module string) bool, enrolled func(node string) bool) []string {
|
func CheckAssignments(assignments map[string]seat.Assignments, known func(module string) bool, enrolled func(node string) bool) []string {
|
||||||
var refusals []string
|
var refusals []string
|
||||||
nodes := sortedKeys(placements)
|
nodes := sortedKeys(assignments)
|
||||||
placed := func(p seat.Provider) bool {
|
placed := func(p seat.Provider) bool {
|
||||||
other, ok := placements[p.Node]
|
other, ok := assignments[p.Node]
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -52,9 +52,9 @@ func CheckPlacements(placements map[string]seat.Placement, known func(module str
|
|||||||
addresses := map[string]string{}
|
addresses := map[string]string{}
|
||||||
var hubs []string
|
var hubs []string
|
||||||
for _, node := range nodes {
|
for _, node := range nodes {
|
||||||
p := placements[node]
|
p := assignments[node]
|
||||||
if p.Node != node {
|
if p.Node != node {
|
||||||
refusals = append(refusals, fmt.Sprintf("the placement of %s names the node %q", node, p.Node))
|
refusals = append(refusals, fmt.Sprintf("the assignments of %s names the node %q", node, p.Node))
|
||||||
}
|
}
|
||||||
if a := strings.TrimSpace(p.Decided.Address); a != "" {
|
if a := strings.TrimSpace(p.Decided.Address); a != "" {
|
||||||
if other, twice := addresses[a]; twice {
|
if other, twice := addresses[a]; twice {
|
||||||
@@ -122,8 +122,8 @@ func CheckPlacements(placements map[string]seat.Placement, known func(module str
|
|||||||
return refusals
|
return refusals
|
||||||
}
|
}
|
||||||
|
|
||||||
// Change is what `placement-changed` says of one node (ADR 0567 §6): the node, the revision of its key before (0: it had
|
// Change is what `assignments-changed` says of one node (ADR 0567 §6): the node, the revision of its key before (0: it had
|
||||||
// none) and after (0: the key was deleted), and its modules added, removed or changed against the placement before.
|
// none) and after (0: the key was deleted), and its modules added, removed or changed against the assignments before.
|
||||||
type Change struct {
|
type Change struct {
|
||||||
Node string `json:"node"`
|
Node string `json:"node"`
|
||||||
Before uint64 `json:"before"`
|
Before uint64 `json:"before"`
|
||||||
@@ -141,10 +141,10 @@ func (c Change) Body() map[string]any {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChangeOf is what changed from before to after on one node; before is the zero Placement for a node new to placement.
|
// ChangeOf is what changed from before to after on one node; before is the zero Assignments for a node new to assignments.
|
||||||
// A change of the node's pins or holdings counts as a change of every module placed on it both times, since the
|
// A change of the node's pins or holdings counts as a change of every module placed on it both times, since the
|
||||||
// composition of each may read them.
|
// composition of each may read them.
|
||||||
func ChangeOf(before, after seat.Placement) Change {
|
func ChangeOf(before, after seat.Assignments) Change {
|
||||||
c := Change{Node: after.Node}
|
c := Change{Node: after.Node}
|
||||||
if c.Node == "" {
|
if c.Node == "" {
|
||||||
c.Node = before.Node
|
c.Node = before.Node
|
||||||
@@ -176,7 +176,7 @@ func ChangeOf(before, after seat.Placement) Change {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// SortChanges orders a placement-changed event's nodes by node.
|
// SortChanges orders assignments-changed event's nodes by node.
|
||||||
func SortChanges(c []Change) {
|
func SortChanges(c []Change) {
|
||||||
sort.Slice(c, func(i, j int) bool { return c[i].Node < c[j].Node })
|
sort.Slice(c, func(i, j int) bool { return c[i].Node < c[j].Node })
|
||||||
}
|
}
|
||||||
@@ -187,14 +187,14 @@ func jsonEqual(a, b any) bool {
|
|||||||
return string(ja) == string(jb)
|
return string(ja) == string(jb)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SeatHoldersOf is the state composed from every node's placement (ADR 0567 §1): each holding by the seat it claims,
|
// SeatHoldersOf is the state composed from every node's assignments (ADR 0567 §1): each holding by the seat it claims,
|
||||||
// keyed as the client's SeatHoldersKey says, with the module holding it and its node. A claim naming a seat's former name
|
// keyed as the client's SeatHoldersKey says, with the module holding it and its node. A claim naming a seat's former name
|
||||||
// is keyed by the seat's name (ADR 0122); one naming no seat is kept, marked Outside. Deterministic, so a writer writes
|
// is keyed by the seat's name (ADR 0122); one naming no seat is kept, marked Outside. Deterministic, so a writer writes
|
||||||
// only a key that differs.
|
// only a key that differs.
|
||||||
func SeatHoldersOf(placements map[string]seat.Placement) map[string]seat.SeatHolders {
|
func SeatHoldersOf(assignments map[string]seat.Assignments) map[string]seat.SeatHolders {
|
||||||
out := map[string]seat.SeatHolders{}
|
out := map[string]seat.SeatHolders{}
|
||||||
for _, n := range sortedKeys(placements) {
|
for _, n := range sortedKeys(assignments) {
|
||||||
for _, h := range placements[n].Holds {
|
for _, h := range assignments[n].Holds {
|
||||||
node := ""
|
node := ""
|
||||||
if h.Scope == "node" {
|
if h.Scope == "node" {
|
||||||
node = n
|
node = n
|
||||||
+14
-14
@@ -1,4 +1,4 @@
|
|||||||
package placement
|
package assignment
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
seat "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
seat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||||
"git.novox.be/novox/mesh-sdk/go/keys"
|
"git.novox.be/novox/mesh-sdk/go/keys"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ type tunnelPeerForm struct {
|
|||||||
Address string `json:"address,omitempty"`
|
Address string `json:"address,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type placementForm struct {
|
type assignmentsForm struct {
|
||||||
V int `json:"v"`
|
V int `json:"v"`
|
||||||
Node string `json:"node"`
|
Node string `json:"node"`
|
||||||
Revision string `json:"revision,omitempty"`
|
Revision string `json:"revision,omitempty"`
|
||||||
@@ -107,7 +107,7 @@ type changeForm struct {
|
|||||||
Layer map[string]any `json:"layer,omitempty"`
|
Layer map[string]any `json:"layer,omitempty"`
|
||||||
MeshValues map[string]any `json:"mesh-wide-values,omitempty"`
|
MeshValues map[string]any `json:"mesh-wide-values,omitempty"`
|
||||||
Values map[string]any `json:"values,omitempty"`
|
Values map[string]any `json:"values,omitempty"`
|
||||||
Whole *placementForm `json:"whole,omitempty"`
|
Whole *assignmentsForm `json:"whole,omitempty"`
|
||||||
Decided *decidedForm `json:"decided,omitempty"`
|
Decided *decidedForm `json:"decided,omitempty"`
|
||||||
Keys *joiningForm `json:"keys,omitempty"`
|
Keys *joiningForm `json:"keys,omitempty"`
|
||||||
}
|
}
|
||||||
@@ -142,8 +142,8 @@ func decidedOf(d seat.NodeDecided) decidedForm {
|
|||||||
Adopted: d.Adopted, Taken: d.Taken, Tunnel: tunnelOf(d.Tunnel), PeerNames: d.PeerNames}
|
Adopted: d.Adopted, Taken: d.Taken, Tunnel: tunnelOf(d.Tunnel), PeerNames: d.PeerNames}
|
||||||
}
|
}
|
||||||
|
|
||||||
func placementOf(p seat.Placement) placementForm {
|
func assignmentsOf(p seat.Assignments) assignmentsForm {
|
||||||
out := placementForm{V: int(p.V), Node: p.Node, Revision: p.Revision, Decided: decidedOf(p.Decided),
|
out := assignmentsForm{V: int(p.V), Node: p.Node, Revision: p.Revision, Decided: decidedOf(p.Decided),
|
||||||
Pins: providersOf(p.Pins)}
|
Pins: providersOf(p.Pins)}
|
||||||
if p.Modules != nil {
|
if p.Modules != nil {
|
||||||
out.Modules = []placedForm{}
|
out.Modules = []placedForm{}
|
||||||
@@ -170,7 +170,7 @@ func changeOf(c seat.ProposalChange) changeForm {
|
|||||||
out.Value, _ = json.Marshal(c.Value)
|
out.Value, _ = json.Marshal(c.Value)
|
||||||
}
|
}
|
||||||
if c.Whole != nil {
|
if c.Whole != nil {
|
||||||
w := placementOf(*c.Whole)
|
w := assignmentsOf(*c.Whole)
|
||||||
out.Whole = &w
|
out.Whole = &w
|
||||||
}
|
}
|
||||||
if c.Decided != nil {
|
if c.Decided != nil {
|
||||||
@@ -183,14 +183,14 @@ func changeOf(c seat.ProposalChange) changeForm {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExportDigest is the digest of an export's answer, every node's placement, as the seed proposes it: the same
|
// ExportDigest is the digest of an export's answer, every node's assignments, as the seed proposes it: the same
|
||||||
// placements give the same digest, whatever order they came in.
|
// assignments give the same digest, whatever order they came in.
|
||||||
func ExportDigest(placements map[string]seat.Placement) string {
|
func ExportDigest(assignments map[string]seat.Assignments) string {
|
||||||
h := sha256.New()
|
h := sha256.New()
|
||||||
for _, node := range sortedKeys(placements) {
|
for _, node := range sortedKeys(assignments) {
|
||||||
p := placements[node]
|
p := assignments[node]
|
||||||
p.V = keys.Version // the export was made with the placement encoded at the keys' version
|
p.V = keys.Version // the export was made with the assignments encoded at the keys' version
|
||||||
raw, _ := json.Marshal(placementOf(p))
|
raw, _ := json.Marshal(assignmentsOf(p))
|
||||||
fmt.Fprintf(h, "%s\n%s\n", node, raw)
|
fmt.Fprintf(h, "%s\n%s\n", node, raw)
|
||||||
}
|
}
|
||||||
return "sha256:" + hex.EncodeToString(h.Sum(nil))
|
return "sha256:" + hex.EncodeToString(h.Sum(nil))
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package placement
|
package assignment
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
+43
-43
@@ -1,4 +1,4 @@
|
|||||||
package placement
|
package assignment
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
@@ -9,13 +9,13 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
client "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
|
||||||
|
seat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||||
enroller "git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshnodeenroller"
|
enroller "git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshnodeenroller"
|
||||||
client "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
|
||||||
seat "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
|
||||||
"git.novox.be/novox/mesh-sdk/go/keys"
|
"git.novox.be/novox/mesh-sdk/go/keys"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A placement changes only by a proposal (novox/hq ADR 0567 §2): `propose` takes why and a list of changes; the holder
|
// Assignments changes only by a proposal (novox/hq ADR 0567 §2): `propose` takes why and a list of changes; the holder
|
||||||
// lays them over the current keys, names the revision of each key it laid them over, and either refuses at once with every
|
// lays them over the current keys, names the revision of each key it laid them over, and either refuses at once with every
|
||||||
// finding or writes the proposal under its id in its state `proposals` and a candidate for each node it must hear from.
|
// finding or writes the proposal under its id in its state `proposals` and a candidate for each node it must hear from.
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ func CheckChange(c seat.ProposalChange) error {
|
|||||||
}
|
}
|
||||||
needs := func(what string, ok bool) error {
|
needs := func(what string, ok bool) error {
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("placement: a %s change of %s names no %s", c.Op, c.Node, what)
|
return fmt.Errorf("assignments: a %s change of %s names no %s", c.Op, c.Node, what)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -67,50 +67,50 @@ func CheckChange(c seat.ProposalChange) error {
|
|||||||
return needs("field or value", c.Field != "" && c.Value != nil)
|
return needs("field or value", c.Field != "" && c.Value != nil)
|
||||||
case client.OpSeed:
|
case client.OpSeed:
|
||||||
if c.Whole == nil || c.Whole.Node != c.Node {
|
if c.Whole == nil || c.Whole.Node != c.Node {
|
||||||
return fmt.Errorf("placement: the seed of %s names no placement of that node", c.Node)
|
return fmt.Errorf("assignments: the seed of %s names no assignments of that node", c.Node)
|
||||||
}
|
}
|
||||||
return client.CheckPlacement(*c.Whole)
|
return client.CheckAssignments(*c.Whole)
|
||||||
case client.OpAddNode:
|
case client.OpAddNode:
|
||||||
if c.Decided == nil || c.Decided.Address == "" || c.Keys == nil || c.Keys.Overlay == "" {
|
if c.Decided == nil || c.Decided.Address == "" || c.Keys == nil || c.Keys.Overlay == "" {
|
||||||
return fmt.Errorf("placement: the add-node of %s names no address or no tunnel key", c.Node)
|
return fmt.Errorf("assignments: the add-node of %s names no address or no tunnel key", c.Node)
|
||||||
}
|
}
|
||||||
// The identity keys come at redeem, whole or not at all: an identity with no sealing key, or the reverse.
|
// The identity keys come at redeem, whole or not at all: an identity with no sealing key, or the reverse.
|
||||||
if (c.Keys.Identity == "") != (c.Keys.Sealing == "") || (c.Keys.Identity == "" && c.Keys.Serving != "") {
|
if (c.Keys.Identity == "") != (c.Keys.Sealing == "") || (c.Keys.Identity == "" && c.Keys.Serving != "") {
|
||||||
return fmt.Errorf("placement: the add-node of %s names part of its identity keys; they come whole at redeem", c.Node)
|
return fmt.Errorf("assignments: the add-node of %s names part of its identity keys; they come whole at redeem", c.Node)
|
||||||
}
|
}
|
||||||
if c.Decided.Hub {
|
if c.Decided.Hub {
|
||||||
return fmt.Errorf("placement: the add-node of %s makes it the hub, which a joining node never is", c.Node)
|
return fmt.Errorf("assignments: the add-node of %s makes it the hub, which a joining node never is", c.Node)
|
||||||
}
|
}
|
||||||
if c.Module != "" || len(c.Ports)+len(c.Bindings) > 0 || c.Whole != nil {
|
if c.Module != "" || len(c.Ports)+len(c.Bindings) > 0 || c.Whole != nil {
|
||||||
return fmt.Errorf("placement: the add-node of %s places a module; a module is placed by a proposal of its own", c.Node)
|
return fmt.Errorf("assignments: the add-node of %s places a module; a module is placed by a proposal of its own", c.Node)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
case client.OpRemoveNode:
|
case client.OpRemoveNode:
|
||||||
if c.Module != "" || c.Decided != nil || c.Keys != nil || c.Whole != nil {
|
if c.Module != "" || c.Decided != nil || c.Keys != nil || c.Whole != nil {
|
||||||
return fmt.Errorf("placement: the remove-node of %s names more than its node", c.Node)
|
return fmt.Errorf("assignments: the remove-node of %s names more than its node", c.Node)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return fmt.Errorf("placement: %q is no change of placement: place, take-off, pin, unpin, hold, unhold, decide, config, add-node or remove-node", c.Op)
|
return fmt.Errorf("assignments: %q is no change of assignments: place, take-off, pin, unpin, hold, unhold, decide, config, add-node or remove-node", c.Op)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LayChanges is the placements of the nodes the changes name with the changes laid over current, in the order given;
|
// LayChanges is the assignments of the nodes the changes name with the changes laid over current, in the order given;
|
||||||
// current is every node's placement now (absent: the node has no key). It returns, by node, the laid placement, and
|
// current is every node's assignments now (absent: the node has no key). It returns, by node, the laid assignments, and
|
||||||
// every finding: a change that does not fit its node (a module taken off that is not placed, configured or holding
|
// every finding: a change that does not fit its node (a module taken off that is not placed, configured or holding
|
||||||
// there; a field NodeDecided does not have) or a placement the client refuses. What CheckPlacements refuses across nodes
|
// there; a field NodeDecided does not have) or assignments the client refuses. What CheckAssignments refuses across nodes
|
||||||
// is the caller's to ask of the result laid over current.
|
// is the caller's to ask of the result laid over current.
|
||||||
func LayChanges(current map[string]seat.Placement, changes []seat.ProposalChange) (map[string]seat.Placement, []string) {
|
func LayChanges(current map[string]seat.Assignments, changes []seat.ProposalChange) (map[string]seat.Assignments, []string) {
|
||||||
laid := map[string]seat.Placement{}
|
laid := map[string]seat.Assignments{}
|
||||||
var findings []string
|
var findings []string
|
||||||
of := func(node string) seat.Placement {
|
of := func(node string) seat.Assignments {
|
||||||
if p, ok := laid[node]; ok {
|
if p, ok := laid[node]; ok {
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
p, ok := current[node]
|
p, ok := current[node]
|
||||||
if !ok {
|
if !ok {
|
||||||
return seat.Placement{V: keys.Version, Node: node, Modules: []seat.PlacedModule{}}
|
return seat.Assignments{V: keys.Version, Node: node, Modules: []seat.PlacedModule{}}
|
||||||
}
|
}
|
||||||
return clonePlacement(p)
|
return cloneAssignments(p)
|
||||||
}
|
}
|
||||||
for i, c := range changes {
|
for i, c := range changes {
|
||||||
if err := CheckChange(c); err != nil {
|
if err := CheckChange(c); err != nil {
|
||||||
@@ -129,14 +129,14 @@ func LayChanges(current map[string]seat.Placement, changes []seat.ProposalChange
|
|||||||
}
|
}
|
||||||
switch c.Op {
|
switch c.Op {
|
||||||
case client.OpSeed:
|
case client.OpSeed:
|
||||||
p = clonePlacement(*c.Whole)
|
p = cloneAssignments(*c.Whole)
|
||||||
case client.OpAddNode:
|
case client.OpAddNode:
|
||||||
// A joining node has no key yet (ADR 0587 §7): one that has is no joining node.
|
// A joining node has no key yet (ADR 0587 §7): one that has is no joining node.
|
||||||
if _, has := current[c.Node]; has {
|
if _, has := current[c.Node]; has {
|
||||||
findings = append(findings, fmt.Sprintf("change %d: %s is placed already, so it does not join", i+1, c.Node))
|
findings = append(findings, fmt.Sprintf("change %d: %s is placed already, so it does not join", i+1, c.Node))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
p = seat.Placement{V: keys.Version, Node: c.Node, Decided: *c.Decided, Modules: []seat.PlacedModule{}}
|
p = seat.Assignments{V: keys.Version, Node: c.Node, Decided: *c.Decided, Modules: []seat.PlacedModule{}}
|
||||||
case client.OpRemoveNode:
|
case client.OpRemoveNode:
|
||||||
// Only a joining node never confirmed goes this way: one with no module and no holding (a node that runs
|
// Only a joining node never confirmed goes this way: one with no module and no holding (a node that runs
|
||||||
// anything is taken off module by module, by a proposal the operator approves).
|
// anything is taken off module by module, by a proposal the operator approves).
|
||||||
@@ -227,7 +227,7 @@ func LayChanges(current map[string]seat.Placement, changes []seat.ProposalChange
|
|||||||
laid[c.Node] = p
|
laid[c.Node] = p
|
||||||
}
|
}
|
||||||
for _, node := range sortedKeys(laid) {
|
for _, node := range sortedKeys(laid) {
|
||||||
if err := client.CheckPlacement(laid[node]); err != nil {
|
if err := client.CheckAssignments(laid[node]); err != nil {
|
||||||
findings = append(findings, err.Error())
|
findings = append(findings, err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -275,9 +275,9 @@ var decidedFields = func() map[string]bool {
|
|||||||
return out
|
return out
|
||||||
}()
|
}()
|
||||||
|
|
||||||
func clonePlacement(p seat.Placement) seat.Placement {
|
func cloneAssignments(p seat.Assignments) seat.Assignments {
|
||||||
raw, _ := json.Marshal(p)
|
raw, _ := json.Marshal(p)
|
||||||
var out seat.Placement
|
var out seat.Assignments
|
||||||
_ = json.Unmarshal(raw, &out)
|
_ = json.Unmarshal(raw, &out)
|
||||||
if out.Modules == nil {
|
if out.Modules == nil {
|
||||||
out.Modules = []seat.PlacedModule{}
|
out.Modules = []seat.PlacedModule{}
|
||||||
@@ -301,33 +301,33 @@ func CheckProposal(p seat.Proposal) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(p.By) == "" || strings.TrimSpace(p.Why) == "" {
|
if strings.TrimSpace(p.By) == "" || strings.TrimSpace(p.Why) == "" {
|
||||||
return fmt.Errorf("placement: the proposal %s names no proposer or no why", p.ID)
|
return fmt.Errorf("assignments: the proposal %s names no proposer or no why", p.ID)
|
||||||
}
|
}
|
||||||
if len(p.Base) == 0 || (len(p.Changes) == 0) != p.HandBack || (p.HandBack && p.Seed) {
|
if len(p.Base) == 0 || (len(p.Changes) == 0) != p.HandBack || (p.HandBack && p.Seed) {
|
||||||
return fmt.Errorf("placement: the proposal %s changes nothing, or is a hand-back with changes", p.ID)
|
return fmt.Errorf("assignments: the proposal %s changes nothing, or is a hand-back with changes", p.ID)
|
||||||
}
|
}
|
||||||
for i, c := range p.Changes {
|
for i, c := range p.Changes {
|
||||||
if err := CheckChange(c); err != nil {
|
if err := CheckChange(c); err != nil {
|
||||||
return fmt.Errorf("placement: the proposal %s, change %d: %w", p.ID, i+1, err)
|
return fmt.Errorf("assignments: the proposal %s, change %d: %w", p.ID, i+1, err)
|
||||||
}
|
}
|
||||||
if (c.Op == client.OpSeed) != p.Seed {
|
if (c.Op == client.OpSeed) != p.Seed {
|
||||||
return fmt.Errorf("placement: the proposal %s mixes the seed with other changes", p.ID)
|
return fmt.Errorf("assignments: the proposal %s mixes the seed with other changes", p.ID)
|
||||||
}
|
}
|
||||||
if _, ok := p.Base[c.Node]; !ok {
|
if _, ok := p.Base[c.Node]; !ok {
|
||||||
return fmt.Errorf("placement: the proposal %s changes %s and names no base revision of its key", p.ID, c.Node)
|
return fmt.Errorf("assignments: the proposal %s changes %s and names no base revision of its key", p.ID, c.Node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch p.State {
|
switch p.State {
|
||||||
case client.ProposalAnswering, client.ProposalFailed:
|
case client.ProposalAnswering, client.ProposalFailed:
|
||||||
case client.ProposalAsked:
|
case client.ProposalAsked:
|
||||||
if p.Ask == "" || p.Verdict == nil || p.Verdict.State != VerdictPass {
|
if p.Ask == "" || p.Verdict == nil || p.Verdict.State != VerdictPass {
|
||||||
return fmt.Errorf("placement: the proposal %s is asked with no ask or no passing verdict", p.ID)
|
return fmt.Errorf("assignments: the proposal %s is asked with no ask or no passing verdict", p.ID)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("placement: the proposal %s is in no state %q", p.ID, p.State)
|
return fmt.Errorf("assignments: the proposal %s is in no state %q", p.ID, p.State)
|
||||||
}
|
}
|
||||||
if p.Seed && p.ExportDigest == "" {
|
if p.Seed && p.ExportDigest == "" {
|
||||||
return fmt.Errorf("placement: the seed %s names no export digest", p.ID)
|
return fmt.Errorf("assignments: the seed %s names no export digest", p.ID)
|
||||||
}
|
}
|
||||||
return checkAddNode(p)
|
return checkAddNode(p)
|
||||||
}
|
}
|
||||||
@@ -344,25 +344,25 @@ func checkAddNode(p seat.Proposal) error {
|
|||||||
}
|
}
|
||||||
if adds == 0 {
|
if adds == 0 {
|
||||||
if p.Warrant != nil {
|
if p.Warrant != nil {
|
||||||
return fmt.Errorf("placement: the proposal %s carries an enrolment's warrant and adds no node", p.ID)
|
return fmt.Errorf("assignments: the proposal %s carries an enrolment's warrant and adds no node", p.ID)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if adds != 1 || len(p.Changes) != 1 || p.HandBack || p.Seed {
|
if adds != 1 || len(p.Changes) != 1 || p.HandBack || p.Seed {
|
||||||
return fmt.Errorf("placement: the proposal %s adds a node beside other changes; an add-node is a proposal's one change", p.ID)
|
return fmt.Errorf("assignments: the proposal %s adds a node beside other changes; an add-node is a proposal's one change", p.ID)
|
||||||
}
|
}
|
||||||
c, w := p.Changes[0], p.Warrant
|
c, w := p.Changes[0], p.Warrant
|
||||||
if w == nil || w.Ask == "" || w.Warrant == "" {
|
if w == nil || w.Ask == "" || w.Warrant == "" {
|
||||||
return fmt.Errorf("placement: the add-node of %s carries no warrant of its enrolment", c.Node)
|
return fmt.Errorf("assignments: the add-node of %s carries no warrant of its enrolment", c.Node)
|
||||||
}
|
}
|
||||||
if c.Op == client.OpRemoveNode {
|
if c.Op == client.OpRemoveNode {
|
||||||
if w.Node != c.Node {
|
if w.Node != c.Node {
|
||||||
return fmt.Errorf("placement: the remove-node of %s carries the warrant of %s", c.Node, w.Node)
|
return fmt.Errorf("assignments: the remove-node of %s carries the warrant of %s", c.Node, w.Node)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if w.Node != c.Node || w.Address != c.Decided.Address {
|
if w.Node != c.Node || w.Address != c.Decided.Address {
|
||||||
return fmt.Errorf("placement: the add-node of %s at %s carries the warrant of %s at %s", c.Node, c.Decided.Address, w.Node, w.Address)
|
return fmt.Errorf("assignments: the add-node of %s at %s carries the warrant of %s at %s", c.Node, c.Decided.Address, w.Node, w.Address)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -383,10 +383,10 @@ func AddNodeRefusal(p seat.Proposal, caller string, enrollers []string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnrollersOf is the modules that hold the seat mesh-node-enroller on the placements given, sorted: the state seat-holders
|
// EnrollersOf is the modules that hold the seat mesh-node-enroller on the assignments given, sorted: the state seat-holders
|
||||||
// composed from them (SeatHoldersOf), read by the client's HoldersOf.
|
// composed from them (SeatHoldersOf), read by the client's HoldersOf.
|
||||||
func EnrollersOf(placements map[string]seat.Placement) []string {
|
func EnrollersOf(assignments map[string]seat.Assignments) []string {
|
||||||
holders, _ := client.HoldersOf(rawHolders(SeatHoldersOf(placements)), enroller.Seat, "")
|
holders, _ := client.HoldersOf(rawHolders(SeatHoldersOf(assignments)), enroller.Seat, "")
|
||||||
var out []string
|
var out []string
|
||||||
for _, h := range holders {
|
for _, h := range holders {
|
||||||
if !slices.Contains(out, h.Module) {
|
if !slices.Contains(out, h.Module) {
|
||||||
@@ -422,7 +422,7 @@ func DecodeProposal(data []byte) (seat.Proposal, error) {
|
|||||||
}
|
}
|
||||||
p, err := seat.DecodeProposalsByProposalValue(data)
|
p, err := seat.DecodeProposalsByProposalValue(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return seat.Proposal{}, fmt.Errorf("placement: a proposal does not fit its shape: %w", err)
|
return seat.Proposal{}, fmt.Errorf("assignments: a proposal does not fit its shape: %w", err)
|
||||||
}
|
}
|
||||||
if err := CheckProposal(p); err != nil {
|
if err := CheckProposal(p); err != nil {
|
||||||
return seat.Proposal{}, err
|
return seat.Proposal{}, err
|
||||||
+5
-5
@@ -1,6 +1,6 @@
|
|||||||
package placement
|
package assignment
|
||||||
|
|
||||||
import seat "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
import seat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||||
|
|
||||||
// Module is the module that holds the seat mesh-module-assigner: the states of the seat are named after it, as every
|
// Module is the module that holds the seat mesh-module-assigner: the states of the seat are named after it, as every
|
||||||
// module state is (a bucket is `<module>_<state>`), and its manifest declares them.
|
// module state is (a bucket is `<module>_<state>`), and its manifest declares them.
|
||||||
@@ -8,7 +8,7 @@ const Module = "mesh-delivery-assigner"
|
|||||||
|
|
||||||
// The buckets of the seat's states, which only the holder writes (ADR 0567 §1).
|
// The buckets of the seat's states, which only the holder writes (ADR 0567 §1).
|
||||||
const (
|
const (
|
||||||
PlacementBucket = Module + "_" + seat.StatePlacement
|
AssignmentsBucket = Module + "_" + seat.StateAssignments
|
||||||
ProposalsBucket = Module + "_" + seat.StateProposals
|
ProposalsBucket = Module + "_" + seat.StateProposals
|
||||||
CandidatesBucket = Module + "_" + seat.StateCandidates
|
CandidatesBucket = Module + "_" + seat.StateCandidates
|
||||||
PulsesBucket = Module + "_" + seat.StatePulses
|
PulsesBucket = Module + "_" + seat.StatePulses
|
||||||
@@ -16,8 +16,8 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// PlacementHistory is the revisions the placement bucket keeps per key: the bus's most (§6).
|
// AssignmentsHistory is the revisions the assignments bucket keeps per key: the bus's most (§6).
|
||||||
PlacementHistory = 64
|
AssignmentsHistory = 64
|
||||||
// EventDays is how long the seat's events are kept: they ride the shared events stream on the seat's own subject
|
// EventDays is how long the seat's events are kept: they ride the shared events stream on the seat's own subject
|
||||||
// (the seat's family, 7 days).
|
// (the seat's family, 7 days).
|
||||||
EventDays = 7
|
EventDays = 7
|
||||||
@@ -7,13 +7,13 @@ package buckets
|
|||||||
import (
|
import (
|
||||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||||
watchdogs "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshwatchdogs"
|
watchdogs "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshwatchdogs"
|
||||||
wants "git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants"
|
|
||||||
catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||||
delivery "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
|
delivery "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
|
||||||
|
wants "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-grants/grantsstate"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-grants/grantsstate"
|
||||||
|
|
||||||
plc "mesh-delivery-assigner/internal/placement"
|
plc "mesh-delivery-assigner/internal/assignment"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The states the assigner keeps: the wants seat's, and its own asks, puts and policies.
|
// The states the assigner keeps: the wants seat's, and its own asks, puts and policies.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Package judge is how the seat mesh-module-assigner's holder judges a proposal (novox/hq ADR 0567 §3, §4, as ADR 0593 §8
|
// Package judge is how the seat mesh-module-assigner's holder judges a proposal (novox/hq ADR 0567 §3, §4, as ADR 0593 §8
|
||||||
// changes them): what it changes on each node by its placement (Intended), every node answering it, and the verdict of
|
// changes them): what it changes on each node by its assignments (Intended), every node answering it, and the verdict of
|
||||||
// their answers (JudgeAnswers). The assigner's own (ADR 0593 §1), moved back from mesh-sdk's compose/judge: the holder
|
// their answers (JudgeAnswers). The assigner's own (ADR 0593 §1), moved back from mesh-sdk's compose/judge: the holder
|
||||||
// composes nothing, since every composition is its node's engine's, and a node answers through the candidates path
|
// composes nothing, since every composition is its node's engine's, and a node answers through the candidates path
|
||||||
// with what it composed. It holds no bus client and no timer: the holder gives it what it read.
|
// with what it composed. It holds no bus client and no timer: the holder gives it what it read.
|
||||||
@@ -17,24 +17,24 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
nec "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
nec "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||||
mma "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
mma "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||||
|
|
||||||
plc "mesh-delivery-assigner/internal/placement"
|
plc "mesh-delivery-assigner/internal/assignment"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AnswerBound is how long the holder waits for a node's answer after its candidate was written: the time to live of the
|
// AnswerBound is how long the holder waits for a node's answer after its candidate was written: the time to live of the
|
||||||
// node's pulse key (placement.PulseAnswerKey), whose expiry names it as giving no answer, never passed. The same answer
|
// node's pulse key (assignment.PulseAnswerKey), whose expiry names it as giving no answer, never passed. The same answer
|
||||||
// bound a check gives a node (ADR 0593 §8, §7: 60 seconds, nec.AnswerBound). A variable so a test on the bus
|
// bound a check gives a node (ADR 0593 §8, §7: 60 seconds, nec.AnswerBound). A variable so a test on the bus
|
||||||
// can shorten it.
|
// can shorten it.
|
||||||
var AnswerBound = nec.AnswerBound
|
var AnswerBound = nec.AnswerBound
|
||||||
|
|
||||||
// Intended is, by node, the modules whose placed entry the proposal changes there, and the nodes that answer it: every
|
// Intended is, by node, the modules whose placed entry the proposal changes there, and the nodes that answer it: every
|
||||||
// node (ADR 0593 §8, changing ADR 0567 §4's list of the nodes that must answer), sorted. current is the placement as it
|
// node (ADR 0593 §8, changing ADR 0567 §4's list of the nodes that must answer), sorted. current is the assignments as it
|
||||||
// stands, laid the proposal's placements by node (nil removes the node's key) and nodes every node with a record. A
|
// stands, laid the proposal's assignments by node (nil removes the node's key) and nodes every node with a record. A
|
||||||
// node's set holds the modules the proposal adds, removes or whose placed entry differs on that node; a node with no
|
// node's set holds the modules the proposal adds, removes or whose placed entry differs on that node; a node with no
|
||||||
// key now gets an empty set (§7: the seed is answered as the same). What a node's compositions change beyond its own
|
// key now gets an empty set (§7: the seed is answered as the same). What a node's compositions change beyond its own
|
||||||
// placement it says itself in its answer, marked in the brief as not computed.
|
// assignments it says itself in its answer, marked in the brief as not computed.
|
||||||
func Intended(current map[string]mma.Placement, laid map[string]*mma.Placement, nodes []string) (intended map[string]map[string]bool, must []string) {
|
func Intended(current map[string]mma.Assignments, laid map[string]*mma.Assignments, nodes []string) (intended map[string]map[string]bool, must []string) {
|
||||||
intended = map[string]map[string]bool{}
|
intended = map[string]map[string]bool{}
|
||||||
all := map[string]bool{}
|
all := map[string]bool{}
|
||||||
for _, n := range nodes {
|
for _, n := range nodes {
|
||||||
@@ -50,7 +50,7 @@ func Intended(current map[string]mma.Placement, laid map[string]*mma.Placement,
|
|||||||
before, placed := current[node]
|
before, placed := current[node]
|
||||||
after, changesKey := laid[node]
|
after, changesKey := laid[node]
|
||||||
if !changesKey || !placed {
|
if !changesKey || !placed {
|
||||||
continue // untouched, or new to placement: every answer must be the same
|
continue // untouched, or new to assignments: every answer must be the same
|
||||||
}
|
}
|
||||||
var afterModules []mma.PlacedModule
|
var afterModules []mma.PlacedModule
|
||||||
if after != nil {
|
if after != nil {
|
||||||
|
|||||||
@@ -8,20 +8,20 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
mma "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
mma "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||||
|
|
||||||
plc "mesh-delivery-assigner/internal/placement"
|
plc "mesh-delivery-assigner/internal/assignment"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ADR 0567 §3, §4 (rows moved from the assigner's check of a pull request's placement, ADR 0559 §5): what a proposal
|
// ADR 0567 §3, §4 (rows moved from the assigner's check of a pull request's assignments, ADR 0559 §5): what a proposal
|
||||||
// changes by node, and the verdict of the answers: differences shown and failing nothing, a refusal and a silent node
|
// changes by node, and the verdict of the answers: differences shown and failing nothing, a refusal and a silent node
|
||||||
// failing, a later answer judging again, the seed failing on any difference.
|
// failing, a later answer judging again, the seed failing on any difference.
|
||||||
func TestJudge(t *testing.T) {
|
func TestJudge(t *testing.T) {
|
||||||
current := map[string]mma.Placement{"anchor": {Node: "anchor", Modules: []mma.PlacedModule{{Module: "mail"}}}, "server": {Node: "server"}}
|
current := map[string]mma.Assignments{"anchor": {Node: "anchor", Modules: []mma.PlacedModule{{Module: "mail"}}}, "server": {Node: "server"}}
|
||||||
laid := map[string]*mma.Placement{
|
laid := map[string]*mma.Assignments{
|
||||||
"anchor": {Node: "anchor", Modules: []mma.PlacedModule{{Module: "mail", Ports: map[string]int64{"25": 2525}}, {Module: "web"}}},
|
"anchor": {Node: "anchor", Modules: []mma.PlacedModule{{Module: "mail", Ports: map[string]int64{"25": 2525}}, {Module: "web"}}},
|
||||||
"laptop": {Node: "laptop", Modules: []mma.PlacedModule{{Module: "web"}}}}
|
"laptop": {Node: "laptop", Modules: []mma.PlacedModule{{Module: "web"}}}}
|
||||||
// ADR 0593 §8: every node answers; what the placement changes is read from the placement alone.
|
// ADR 0593 §8: every node answers; what the assignments changes is read from the assignments alone.
|
||||||
got, must := Intended(current, laid, []string{"anchor", "laptop", "server"})
|
got, must := Intended(current, laid, []string{"anchor", "laptop", "server"})
|
||||||
if !got["anchor"]["mail"] || !got["anchor"]["web"] || len(got["laptop"]) != 0 || len(got["server"]) != 0 ||
|
if !got["anchor"]["mail"] || !got["anchor"]["web"] || len(got["laptop"]) != 0 || len(got["server"]) != 0 ||
|
||||||
!slices.Equal(must, []string{"anchor", "laptop", "server"}) {
|
!slices.Equal(must, []string{"anchor", "laptop", "server"}) {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package manifest
|
package manifest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||||
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants"
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"slices"
|
"slices"
|
||||||
@@ -63,7 +63,7 @@ func TestManifest(t *testing.T) {
|
|||||||
states[s.Name] = s.History
|
states[s.Name] = s.History
|
||||||
}
|
}
|
||||||
if want := map[string]int{"asks": 5, "differences": 64, "puts": 20, "wants": 20,
|
if want := map[string]int{"asks": 5, "differences": 64, "puts": 20, "wants": 20,
|
||||||
"placement": 64, "proposals": 5, "candidates": 5, "pulses": 0, "findings": 5, "policies": 20, "seat-holders": 5, views.MarksState: 1, views.StatusState: 1,
|
"assignments": 64, "placement": 64, "proposals": 5, "candidates": 5, "pulses": 0, "findings": 5, "policies": 20, "seat-holders": 5, views.MarksState: 1, views.StatusState: 1,
|
||||||
"engine-reads": 1}; !reflect.DeepEqual(states, want) {
|
"engine-reads": 1}; !reflect.DeepEqual(states, want) {
|
||||||
t.Errorf("state = %v, want %v", states, want)
|
t.Errorf("state = %v, want %v", states, want)
|
||||||
}
|
}
|
||||||
@@ -81,20 +81,20 @@ func TestManifest(t *testing.T) {
|
|||||||
// ADR 0546: it serves its own tools (put, release, puts) from its bundle through the tool runner (the artifact loads the
|
// ADR 0546: it serves its own tools (put, release, puts) from its bundle through the tool runner (the artifact loads the
|
||||||
// module), says each put's step as the event put, and calls the hand-act seat and the bus node's backup holder.
|
// module), says each put's step as the event put, and calls the hand-act seat and the bus node's backup holder.
|
||||||
// ADR 0567 §1, §6-§10: it holds the seat mesh-module-assigner, serving its five verbs (adopt and hand-back went with the
|
// ADR 0567 §1, §6-§10: it holds the seat mesh-module-assigner, serving its five verbs (adopt and hand-back went with the
|
||||||
// controller, ADR 0590); keeps the seat's events on its stream placement for 30 days; calls the hand-act log; and reads
|
// controller, ADR 0590); keeps the seat's events on its stream assignments for 30 days; calls the hand-act log; and reads
|
||||||
// mesh-config's resolved values to compose every node a proposal touches.
|
// mesh-config's resolved values to compose every node a proposal touches.
|
||||||
// ADR 0565 §7, §9 (issue 1953 D4): and the seat mesh-wants, serving upgrade and bus.
|
// ADR 0565 §7, §9 (issue 1953 D4): and the seat mesh-wants, serving upgrade and bus.
|
||||||
if len(m.Claims) != 2 || m.Claims[0].Name != meshmoduleassigner.Seat || m.Claims[0].At() != catalogue.ScopeMesh ||
|
if len(m.Claims) != 2 || m.Claims[0].Name != meshmoduleassigner.Seat || m.Claims[0].At() != catalogue.ScopeMesh ||
|
||||||
!slices.Equal(m.Claims[0].Serves, []string{"placement", "proposals", "propose", "withdraw", "again"}) ||
|
!slices.Equal(m.Claims[0].Serves, []string{"assignments", "proposals", "propose", "withdraw", "again"}) ||
|
||||||
m.Claims[1].Name != meshwants.Seat || m.Claims[1].At() != catalogue.ScopeMesh || !slices.Equal(m.Claims[1].Serves, []string{"upgrade", "bus"}) {
|
m.Claims[1].Name != meshwants.Seat || m.Claims[1].At() != catalogue.ScopeMesh || !slices.Equal(m.Claims[1].Serves, []string{"upgrade", "bus"}) {
|
||||||
t.Errorf("the assigner claims %+v, want mesh-module-assigner with its five verbs and mesh-wants with its two", m.Claims)
|
t.Errorf("the assigner claims %+v, want mesh-module-assigner with its five verbs and mesh-wants with its two", m.Claims)
|
||||||
}
|
}
|
||||||
// Whether the seats' definitions let it hold them is the manifest validator's (the judge, at the seat list).
|
// Whether the seats' definitions let it hold them is the manifest validator's (the judge, at the seat list).
|
||||||
// ADR 0621 §1: the first-view tools placement, seat-holders and wants and design 25 § 12.2's six read tools, of the
|
// ADR 0621 §1: the first-view tools assignments, seat-holders and wants and design 25 § 12.2's six read tools, of the
|
||||||
// program's bundle, which runs in a runtime of its own on the assigner's account; ADR 0618 §3: and assigner_engine_reads,
|
// program's bundle, which runs in a runtime of its own on the assigner's account; ADR 0618 §3: and assigner_engine_reads,
|
||||||
// which lists and asks again what each node's engine reads.
|
// which lists and asks again what each node's engine reads.
|
||||||
if !slices.Equal(m.Tools, []string{"assigner_put", "assigner_release", "assigner_puts", "placement", "seat-holders", "wants",
|
if !slices.Equal(m.Tools, []string{"assigner_put", "assigner_release", "assigner_puts", "assignments", "seat-holders", "wants",
|
||||||
"assigner_read_placement", "assigner_read_seat_holders", "assigner_read_wants", "assigner_read_findings", "assigner_read_asks", "assigner_read_differences",
|
"assigner_read_assignments", "assigner_read_seat_holders", "assigner_read_wants", "assigner_read_findings", "assigner_read_asks", "assigner_read_differences",
|
||||||
"assigner_first_views", "assigner_first_view_again", "assigner_engine_reads"}) {
|
"assigner_first_views", "assigner_first_view_again", "assigner_engine_reads"}) {
|
||||||
t.Errorf("the assigner serves tools %v, want its three put tools, three first views, six read tools and assigner_engine_reads", m.Tools)
|
t.Errorf("the assigner serves tools %v, want its three put tools, three first views, six read tools and assigner_engine_reads", m.Tools)
|
||||||
}
|
}
|
||||||
@@ -102,7 +102,7 @@ func TestManifest(t *testing.T) {
|
|||||||
t.Errorf("runs-as = %q: the bundle runs on the assigner's own account (ADR 0621 §1)", m.RunsAs)
|
t.Errorf("runs-as = %q: the bundle runs on the assigner's own account (ADR 0621 §1)", m.RunsAs)
|
||||||
}
|
}
|
||||||
// ADR 0621 §2: each first-view tool answers exactly its readers, never the assigner.
|
// ADR 0621 §2: each first-view tool answers exactly its readers, never the assigner.
|
||||||
callers := map[string]string{"placement": "mesh-grants,node-tools,mesh-delivery-indexer", "seat-holders": "node-tools", "wants": "mesh-grants"}
|
callers := map[string]string{"assignments": "mesh-grants,node-tools,mesh-delivery-indexer", "seat-holders": "node-tools", "wants": "mesh-grants"}
|
||||||
env := m.Build.Artifacts[0].Env
|
env := m.Build.Artifacts[0].Env
|
||||||
for tool, list := range callers {
|
for tool, list := range callers {
|
||||||
if env[views.CallersEnv(tool)] != list || views.ParseCallers(env[views.CallersEnv(tool)]).Allows("n1."+m.Module) {
|
if env[views.CallersEnv(tool)] != list || views.ParseCallers(env[views.CallersEnv(tool)]).Allows("n1."+m.Module) {
|
||||||
@@ -130,7 +130,7 @@ func TestManifest(t *testing.T) {
|
|||||||
"seat:node-engine.manifest-reads"}; !slices.Equal(slices.Sorted(slices.Values(m.Invokes)), want) { // ADR 0618 §3
|
"seat:node-engine.manifest-reads"}; !slices.Equal(slices.Sorted(slices.Values(m.Invokes)), want) { // ADR 0618 §3
|
||||||
t.Errorf("invokes = %v, want %v", m.Invokes, want)
|
t.Errorf("invokes = %v, want %v", m.Invokes, want)
|
||||||
}
|
}
|
||||||
if want := []string{"put", "views.asks", "views.differences", "views.findings", "views.placement", "views.seat-holders", "views.wants"}; !slices.Equal(m.Emits, want) {
|
if want := []string{"assignments-copied", "put", "views.asks", "views.differences", "views.findings", "views.assignments", "views.seat-holders", "views.wants"}; !slices.Equal(m.Emits, want) {
|
||||||
t.Errorf("emits = %v, want put and its six views", m.Emits)
|
t.Errorf("emits = %v, want the copy, put and its six views", m.Emits)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"name": "mesh-module-assigner",
|
"name": "mesh-module-assigner",
|
||||||
"scope": "mesh",
|
"scope": "mesh",
|
||||||
"serves": [
|
"serves": [
|
||||||
"placement",
|
"assignments",
|
||||||
"proposals",
|
"proposals",
|
||||||
"propose",
|
"propose",
|
||||||
"withdraw",
|
"withdraw",
|
||||||
@@ -56,11 +56,12 @@
|
|||||||
"seat:node-engine.manifest-reads"
|
"seat:node-engine.manifest-reads"
|
||||||
],
|
],
|
||||||
"emits": [
|
"emits": [
|
||||||
|
"assignments-copied",
|
||||||
"put",
|
"put",
|
||||||
"views.asks",
|
"views.asks",
|
||||||
"views.differences",
|
"views.differences",
|
||||||
"views.findings",
|
"views.findings",
|
||||||
"views.placement",
|
"views.assignments",
|
||||||
"views.seat-holders",
|
"views.seat-holders",
|
||||||
"views.wants"
|
"views.wants"
|
||||||
],
|
],
|
||||||
@@ -68,10 +69,10 @@
|
|||||||
"assigner_put",
|
"assigner_put",
|
||||||
"assigner_release",
|
"assigner_release",
|
||||||
"assigner_puts",
|
"assigner_puts",
|
||||||
"placement",
|
"assignments",
|
||||||
"seat-holders",
|
"seat-holders",
|
||||||
"wants",
|
"wants",
|
||||||
"assigner_read_placement",
|
"assigner_read_assignments",
|
||||||
"assigner_read_seat_holders",
|
"assigner_read_seat_holders",
|
||||||
"assigner_read_wants",
|
"assigner_read_wants",
|
||||||
"assigner_read_findings",
|
"assigner_read_findings",
|
||||||
@@ -82,6 +83,7 @@
|
|||||||
"assigner_engine_reads"
|
"assigner_engine_reads"
|
||||||
],
|
],
|
||||||
"state": [
|
"state": [
|
||||||
|
{ "name": "assignments", "history": 64 },
|
||||||
{ "name": "placement", "history": 64 },
|
{ "name": "placement", "history": 64 },
|
||||||
{ "name": "proposals", "history": 5 },
|
{ "name": "proposals", "history": 5 },
|
||||||
{ "name": "candidates", "history": 5 },
|
{ "name": "candidates", "history": 5 },
|
||||||
@@ -147,7 +149,7 @@
|
|||||||
"mesh-delivery-assigner"
|
"mesh-delivery-assigner"
|
||||||
],
|
],
|
||||||
"env": {
|
"env": {
|
||||||
"MESH_VIEW_CALLERS_PLACEMENT": "mesh-grants,node-tools,mesh-delivery-indexer",
|
"MESH_VIEW_CALLERS_ASSIGNMENTS": "mesh-grants,node-tools,mesh-delivery-indexer",
|
||||||
"MESH_VIEW_CALLERS_SEAT_HOLDERS": "node-tools",
|
"MESH_VIEW_CALLERS_SEAT_HOLDERS": "node-tools",
|
||||||
"MESH_VIEW_CALLERS_WANTS": "mesh-grants"
|
"MESH_VIEW_CALLERS_WANTS": "mesh-grants"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
// The seat the assigner holds is mesh-module-assigner (novox/hq ADR 0634 §9): no message, tool text or comment of the module
|
// The seat the assigner holds is mesh-module-assigner (novox/hq ADR 0634 §9): no message, tool text or comment of the module
|
||||||
// says the name it had before.
|
// says the name it had before.
|
||||||
func TestNoTextSaysTheOldSeatName(t *testing.T) {
|
func TestNoTextSaysTheOldSeatName(t *testing.T) {
|
||||||
old := "mesh-" + "placement"
|
old := "mesh-" + "assignments"
|
||||||
err := filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error {
|
err := filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error {
|
||||||
if err != nil || d.IsDir() || !strings.HasSuffix(path, ".go") || path == "oldseat_test.go" {
|
if err != nil || d.IsDir() || !strings.HasSuffix(path, ".go") || path == "oldseat_test.go" {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ go 1.26.0
|
|||||||
require (
|
require (
|
||||||
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/seats/meshwatchdogs 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/go/clients/nodeengineclient v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-delivery/sdk v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-delivery/sdk v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-sdk/go v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-sdk/go v0.0.0-00010101000000-000000000000
|
||||||
github.com/nats-io/nats.go v1.54.0
|
github.com/nats-io/nats.go v1.54.0
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ package buildrun
|
|||||||
// builder checks them against it) and from the build seats' states: a bucket is `<holder module>_<state>`, and no contract
|
// builder checks them against it) and from the build seats' states: a bucket is `<holder module>_<state>`, and no contract
|
||||||
// names a holder (novox/hq ADR 0625 §4, ADR 0634 §1).
|
// names a holder (novox/hq ADR 0625 §4, ADR 0634 §1).
|
||||||
const (
|
const (
|
||||||
// PlacementBucket is the assigner's state placement, which the toolchain gate reads for the placing of a build module.
|
// AssignmentsBucket is the assigner's state assignments, which the toolchain gate reads for the placing of a build module.
|
||||||
AssignerModule = "mesh-delivery-assigner"
|
AssignerModule = "mesh-delivery-assigner"
|
||||||
PlacementBucket = AssignerModule + "_placement"
|
AssignmentsBucket = AssignerModule + "_assignments"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StepsBucket is the bucket of a build module's state steps.
|
// StepsBucket is the bucket of a build module's state steps.
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/nats-io/nats.go/jetstream"
|
"github.com/nats-io/nats.go/jetstream"
|
||||||
|
|
||||||
"git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
"git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
|
||||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||||
"git.novox.be/novox/mesh-sdk/go/process"
|
"git.novox.be/novox/mesh-sdk/go/process"
|
||||||
)
|
)
|
||||||
@@ -23,12 +23,12 @@ import (
|
|||||||
// ace.typescript at v26.12.0, and every Node fingerprint said "builders disagree").
|
// ace.typescript at v26.12.0, and every Node fingerprint said "builders disagree").
|
||||||
//
|
//
|
||||||
// ToolchainGate stands between the toolchains buckets and what takes their entries in (the indexer's PutToolchain, a
|
// ToolchainGate stands between the toolchains buckets and what takes their entries in (the indexer's PutToolchain, a
|
||||||
// ToolchainSet): an entry is passed on only while the placement (the seat mesh-placement's state `placement`, held by
|
// ToolchainSet): an entry is passed on only while the assignments (the seat mesh-module-assigner's state `assignments`, held by
|
||||||
// the assigner, PlacementBucket, read by meshmoduleassignerclient.Placed; ADR 0567, ADR 0549 §3) places its build module on
|
// the assigner, AssignmentsBucket, read by meshmoduleassignerclient.Placed; ADR 0567, ADR 0549 §3) places its build module on
|
||||||
// its node, and only once it was written since that placement; it is passed on as deleted otherwise, said once, and
|
// its node, and only once it was written since that assignments; it is passed on as deleted otherwise, said once, and
|
||||||
// passed on when its node writes it again. A report written before the placement is one an earlier run of the module
|
// passed on when its node writes it again. A report written before the assignments is one an earlier run of the module
|
||||||
// left (issue 1940, 2026-10-25 20:56: mesh-build-node put on ace again brought ace.typescript v26.12.0 of 2026-10-23
|
// left (issue 1940, 2026-10-25 20:56: mesh-build-node put on ace again brought ace.typescript v26.12.0 of 2026-10-23
|
||||||
// back into the agreement before the module ran there). Until the placement is settled, and while no node's placement
|
// back into the agreement before the module ran there). Until the assignments is settled, and while no node's assignments
|
||||||
// is held (before the assigner's adopt writes the first one, or where the state cannot be read), every entry is passed
|
// is held (before the assigner's adopt writes the first one, or where the state cannot be read), every entry is passed
|
||||||
// on. Builders that run and report different versions still disagree: that refusal is the guard of novox/hq ADR 0465,
|
// on. Builders that run and report different versions still disagree: that refusal is the guard of novox/hq ADR 0465,
|
||||||
// and it stays.
|
// and it stays.
|
||||||
@@ -41,11 +41,11 @@ type ToolchainGate struct {
|
|||||||
forward func(module, key string, value []byte, deleted bool) bool // answers whether it changed what it holds
|
forward func(module, key string, value []byte, deleted bool) bool // answers whether it changed what it holds
|
||||||
logf func(format string, a ...any)
|
logf func(format string, a ...any)
|
||||||
placed *meshmoduleassignerclient.Placed
|
placed *meshmoduleassignerclient.Placed
|
||||||
known bool // the placement is settled
|
known bool // the assignments is settled
|
||||||
raw map[string]map[string][]byte // module -> toolchains key -> value, as the bucket holds it
|
raw map[string]map[string][]byte // module -> toolchains key -> value, as the bucket holds it
|
||||||
left map[string]string // <module>/<key> -> why it was said left out, so each reason is said once
|
left map[string]string // <module>/<key> -> why it was said left out, so each reason is said once
|
||||||
// OnPlaced is told, outside the gate's lock, once the placement's history is read and after each revision of it
|
// OnPlaced is told, outside the gate's lock, once the assignments's history is read and after each revision of it
|
||||||
// FollowPlacement hears from then on (the counted nodes, Nodes, may have changed). Nil tells nobody.
|
// FollowAssignments hears from then on (the counted nodes, Nodes, may have changed). Nil tells nobody.
|
||||||
OnPlaced func()
|
OnPlaced func()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,28 +75,28 @@ func (g *ToolchainGate) Toolchain(module, key string, value []byte, deleted bool
|
|||||||
return g.pass(module, key)
|
return g.pass(module, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Placement takes one revision of the seat mesh-placement's placement in (at is when the bus wrote it) and answers whether what it
|
// Assignments takes one revision of the seat mesh-module-assigner's assignments in (at is when the bus wrote it) and answers whether what it
|
||||||
// passed on changed. A value that does not read is said, and places nothing on its node.
|
// passed on changed. A value that does not read is said, and places nothing on its node.
|
||||||
func (g *ToolchainGate) Placement(key string, value []byte, deleted bool, at time.Time) bool {
|
func (g *ToolchainGate) Assignments(key string, value []byte, deleted bool, at time.Time) bool {
|
||||||
g.mu.Lock()
|
g.mu.Lock()
|
||||||
defer g.mu.Unlock()
|
defer g.mu.Unlock()
|
||||||
was := g.placed.Seeded()
|
was := g.placed.Seeded()
|
||||||
node, changed, err := g.placed.Put(key, value, deleted, at)
|
node, changed, err := g.placed.Put(key, value, deleted, at)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
g.logf("%s: %v (issue 1940)", PlacementBucket, err)
|
g.logf("%s: %v (issue 1940)", AssignmentsBucket, err)
|
||||||
}
|
}
|
||||||
if !g.known {
|
if !g.known {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if was != g.placed.Seeded() {
|
if was != g.placed.Seeded() {
|
||||||
// The first placement came, or the last went: every report's standing changed.
|
// The first assignments came, or the last went: every report's standing changed.
|
||||||
return g.all()
|
return g.all()
|
||||||
}
|
}
|
||||||
return g.again(node, changed)
|
return g.again(node, changed)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nodes are the nodes the placement places any module on, sorted: the nodes the mesh counts (a node leaves the count
|
// Nodes are the nodes the assignments places any module on, sorted: the nodes the mesh counts (a node leaves the count
|
||||||
// when the placement no longer names it; ADR 0624 §1). None until the placement is read.
|
// when the assignments no longer names it; ADR 0624 §1). None until the assignments is read.
|
||||||
func (g *ToolchainGate) Nodes() []string {
|
func (g *ToolchainGate) Nodes() []string {
|
||||||
var out []string
|
var out []string
|
||||||
for node, modules := range g.placed.ByNode() {
|
for node, modules := range g.placed.ByNode() {
|
||||||
@@ -108,8 +108,8 @@ func (g *ToolchainGate) Nodes() []string {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// Settled says the placement's history is read: from now on only the reports of placed nodes pass, once a node's
|
// Settled says the assignments's history is read: from now on only the reports of placed nodes pass, once a node's
|
||||||
// placement is held. It answers whether what it passed on changed.
|
// assignments is held. It answers whether what it passed on changed.
|
||||||
func (g *ToolchainGate) Settled() bool {
|
func (g *ToolchainGate) Settled() bool {
|
||||||
g.mu.Lock()
|
g.mu.Lock()
|
||||||
defer g.mu.Unlock()
|
defer g.mu.Unlock()
|
||||||
@@ -144,7 +144,7 @@ func (g *ToolchainGate) again(node string, modules []string) bool {
|
|||||||
return changed
|
return changed
|
||||||
}
|
}
|
||||||
|
|
||||||
// pass hands one key on as it stands: its value when its node is placed and wrote it since (or the placement is not
|
// pass hands one key on as it stands: its value when its node is placed and wrote it since (or the assignments is not
|
||||||
// known), deleted otherwise. The caller holds mu.
|
// known), deleted otherwise. The caller holds mu.
|
||||||
func (g *ToolchainGate) pass(module, key string) bool {
|
func (g *ToolchainGate) pass(module, key string) bool {
|
||||||
value, held := g.raw[module][key]
|
value, held := g.raw[module][key]
|
||||||
@@ -183,24 +183,24 @@ func (g *ToolchainGate) leftOut(module, node string, value []byte) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// FollowPlacement reads the seat mesh-placement's placement with its history into gate, says it settled, and follows it; changed
|
// FollowAssignments reads the seat mesh-module-assigner's assignments with its history into gate, says it settled, and follows it; changed
|
||||||
// is told whenever what the gate passes on changed. Where the state cannot be read or watched (not there, or this part
|
// is told whenever what the gate passes on changed. Where the state cannot be read or watched (not there, or this part
|
||||||
// may not read it) it says so and the gate passes every report on: not fatal, since an indexer, builder or checker that
|
// may not read it) it says so and the gate passes every report on: not fatal, since an indexer, builder or checker that
|
||||||
// stops for it stops every delivery (issue 1945). An error of the watch after that ends on the channel.
|
// stops for it stops every delivery (issue 1945). An error of the watch after that ends on the channel.
|
||||||
func FollowPlacement(ctx context.Context, js process.BucketOpener, gate *ToolchainGate, changed func(context.Context), logf func(string, ...any)) (<-chan error, error) {
|
func FollowAssignments(ctx context.Context, js process.BucketOpener, gate *ToolchainGate, changed func(context.Context), logf func(string, ...any)) (<-chan error, error) {
|
||||||
errs := make(chan error, 1)
|
errs := make(chan error, 1)
|
||||||
kv, err := js.KeyValue(ctx, PlacementBucket)
|
kv, err := js.KeyValue(ctx, AssignmentsBucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logf("the state %s cannot be read, so every node's toolchain report counts, a build node's the catalogue does not place too (issue 1940): %v", PlacementBucket, err)
|
logf("the state %s cannot be read, so every node's toolchain report counts, a build node's the catalogue does not place too (issue 1940): %v", AssignmentsBucket, err)
|
||||||
return errs, nil
|
return errs, nil
|
||||||
}
|
}
|
||||||
w, err := kv.WatchAll(ctx, jetstream.IncludeHistory())
|
w, err := kv.WatchAll(ctx, jetstream.IncludeHistory())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logf("the state %s cannot be watched, so every node's toolchain report counts, a build node's the catalogue does not place too (issue 1940): %v", PlacementBucket, err)
|
logf("the state %s cannot be watched, so every node's toolchain report counts, a build node's the catalogue does not place too (issue 1940): %v", AssignmentsBucket, err)
|
||||||
return errs, nil
|
return errs, nil
|
||||||
}
|
}
|
||||||
put := func(e jetstream.KeyValueEntry) bool {
|
put := func(e jetstream.KeyValueEntry) bool {
|
||||||
return gate.Placement(e.Key(), e.Value(), e.Operation() != jetstream.KeyValuePut, e.Created())
|
return gate.Assignments(e.Key(), e.Value(), e.Operation() != jetstream.KeyValuePut, e.Created())
|
||||||
}
|
}
|
||||||
for settled := false; !settled; {
|
for settled := false; !settled; {
|
||||||
select {
|
select {
|
||||||
@@ -209,7 +209,7 @@ func FollowPlacement(ctx context.Context, js process.BucketOpener, gate *Toolcha
|
|||||||
return errs, ctx.Err()
|
return errs, ctx.Err()
|
||||||
case e, ok := <-w.Updates():
|
case e, ok := <-w.Updates():
|
||||||
if !ok {
|
if !ok {
|
||||||
return errs, errors.New("the watch of " + PlacementBucket + " ended before it settled")
|
return errs, errors.New("the watch of " + AssignmentsBucket + " ended before it settled")
|
||||||
}
|
}
|
||||||
if e == nil {
|
if e == nil {
|
||||||
settled = true
|
settled = true
|
||||||
@@ -233,7 +233,7 @@ func FollowPlacement(ctx context.Context, js process.BucketOpener, gate *Toolcha
|
|||||||
return
|
return
|
||||||
case e, ok := <-w.Updates():
|
case e, ok := <-w.Updates():
|
||||||
if !ok {
|
if !ok {
|
||||||
errs <- errors.New("the watch of " + PlacementBucket + " ended")
|
errs <- errors.New("the watch of " + AssignmentsBucket + " ended")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if e != nil && put(e) {
|
if e != nil && put(e) {
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import (
|
|||||||
|
|
||||||
"github.com/nats-io/nats.go/jetstream"
|
"github.com/nats-io/nats.go/jetstream"
|
||||||
|
|
||||||
"git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
"git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
|
||||||
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -27,14 +27,14 @@ func gateReport(t *testing.T, node, version string, at time.Time) (string, []byt
|
|||||||
return key, value
|
return key, value
|
||||||
}
|
}
|
||||||
|
|
||||||
// placing is a value of the state `placement` placing modules on node.
|
// placing is a value of the state `assignments` placing modules on node.
|
||||||
func placing(t *testing.T, node string, modules ...string) (string, []byte) {
|
func placing(t *testing.T, node string, modules ...string) (string, []byte) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
p := meshmoduleassigner.Placement{Node: node}
|
p := meshmoduleassigner.Assignments{Node: node}
|
||||||
for _, m := range modules {
|
for _, m := range modules {
|
||||||
p.Modules = append(p.Modules, meshmoduleassigner.PlacedModule{Module: m})
|
p.Modules = append(p.Modules, meshmoduleassigner.PlacedModule{Module: m})
|
||||||
}
|
}
|
||||||
values, err := meshmoduleassignerclient.PlacementValues(p)
|
values, err := meshmoduleassignerclient.AssignmentsValues(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -42,29 +42,14 @@ func placing(t *testing.T, node string, modules ...string) (string, []byte) {
|
|||||||
return key, values[key]
|
return key, values[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
// placingLegacy is placing in the form before, `placement.<node>` (research 072 row 37).
|
|
||||||
func placingLegacy(t *testing.T, node string, modules ...string) (string, []byte) {
|
|
||||||
t.Helper()
|
|
||||||
p := meshmoduleassigner.Placement{Node: node}
|
|
||||||
for _, m := range modules {
|
|
||||||
p.Modules = append(p.Modules, meshmoduleassigner.PlacedModule{Module: m})
|
|
||||||
}
|
|
||||||
key, _ := meshmoduleassignerclient.LegacyKey(node)
|
|
||||||
value, err := meshmoduleassignerclient.EncodePlacement(p)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
return key, value
|
|
||||||
}
|
|
||||||
|
|
||||||
func agreedOf(set *ToolchainSet) (string, string) {
|
func agreedOf(set *ToolchainSet) (string, string) {
|
||||||
tc, none := buildstep.AgreedToolchain(set.Entries("typescript"))
|
tc, none := buildstep.AgreedToolchain(set.Entries("typescript"))
|
||||||
return tc.Version, none
|
return tc.Version, none
|
||||||
}
|
}
|
||||||
|
|
||||||
// Issue 1940, the gate over the catalogue's placement (issue 774, ADR 0549 §3): a build node's report counts only while
|
// Issue 1940, the gate over the catalogue's assignments (issue 774, ADR 0549 §3): a build node's report counts only while
|
||||||
// the placement places its build module on its node, and only once written since; before the placement is settled, and
|
// the assignments places its build module on its node, and only once written since; before the assignments is settled, and
|
||||||
// while no node's placement is held, every report counts. The live cases: mesh-build-node taken off ace left
|
// while no node's assignments is held, every report counts. The live cases: mesh-build-node taken off ace left
|
||||||
// ace.typescript at v26.12.0 against v26.12.1 on the three others; put on ace again, its report of 2026-10-23 06:29 came
|
// ace.typescript at v26.12.0 against v26.12.1 on the three others; put on ace again, its report of 2026-10-23 06:29 came
|
||||||
// back before the module ran there.
|
// back before the module ran there.
|
||||||
func TestToolchainGate(t *testing.T) {
|
func TestToolchainGate(t *testing.T) {
|
||||||
@@ -78,7 +63,7 @@ func TestToolchainGate(t *testing.T) {
|
|||||||
for _, node := range three {
|
for _, node := range three {
|
||||||
if seeded {
|
if seeded {
|
||||||
key, value := placing(t, node, NodeBuildModule)
|
key, value := placing(t, node, NodeBuildModule)
|
||||||
gate.Placement(key, value, false, old.Add(-time.Hour))
|
gate.Assignments(key, value, false, old.Add(-time.Hour))
|
||||||
}
|
}
|
||||||
key, value := gateReport(t, node, "v26.12.1", placed.Add(time.Minute))
|
key, value := gateReport(t, node, "v26.12.1", placed.Add(time.Minute))
|
||||||
gate.Toolchain(NodeBuildModule, key, value, false)
|
gate.Toolchain(NodeBuildModule, key, value, false)
|
||||||
@@ -91,10 +76,10 @@ func TestToolchainGate(t *testing.T) {
|
|||||||
key, value := gateReport(t, "ace", "v26.12.0", old)
|
key, value := gateReport(t, "ace", "v26.12.0", old)
|
||||||
gate.Toolchain(NodeBuildModule, key, value, false)
|
gate.Toolchain(NodeBuildModule, key, value, false)
|
||||||
if a := buildstep.ToolchainAgreement(set.Entries("typescript")); len(a.SittingOut) != 1 || a.SittingOut[0].Node != "ace" { // ADR 0584: the majority is agreed, ace's report counts as the one that sits out
|
if a := buildstep.ToolchainAgreement(set.Entries("typescript")); len(a.SittingOut) != 1 || a.SittingOut[0].Node != "ace" { // ADR 0584: the majority is agreed, ace's report counts as the one that sits out
|
||||||
t.Fatalf("before the placement is settled every report counts; got %+v", a)
|
t.Fatalf("before the assignments is settled every report counts; got %+v", a)
|
||||||
}
|
}
|
||||||
k, v := placing(t, "ace", GoBuildModule) // another build module on ace: no matter
|
k, v := placing(t, "ace", GoBuildModule) // another build module on ace: no matter
|
||||||
gate.Placement(k, v, false, old)
|
gate.Assignments(k, v, false, old)
|
||||||
if !gate.Settled() {
|
if !gate.Settled() {
|
||||||
t.Fatal("settling left ace's report out, so what was passed on changed")
|
t.Fatal("settling left ace's report out, so what was passed on changed")
|
||||||
}
|
}
|
||||||
@@ -109,13 +94,13 @@ func TestToolchainGate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
// Placed on ace with a report written since: it counts, and the builders really disagree.
|
// Placed on ace with a report written since: it counts, and the builders really disagree.
|
||||||
k, v = placing(t, "ace", GoBuildModule, NodeBuildModule)
|
k, v = placing(t, "ace", GoBuildModule, NodeBuildModule)
|
||||||
gate.Placement(k, v, false, old.Add(-time.Minute))
|
gate.Assignments(k, v, false, old.Add(-time.Minute))
|
||||||
if a := buildstep.ToolchainAgreement(set.Entries("typescript")); a.Toolchain.Version != "v26.12.1" || len(a.SittingOut) != 1 || a.SittingOut[0].Version != "v26.12.0" {
|
if a := buildstep.ToolchainAgreement(set.Entries("typescript")); a.Toolchain.Version != "v26.12.1" || len(a.SittingOut) != 1 || a.SittingOut[0].Version != "v26.12.0" {
|
||||||
t.Fatalf("builders that run different versions: the majority is agreed and ace sits out; got %+v", a)
|
t.Fatalf("builders that run different versions: the majority is agreed and ace sits out; got %+v", a)
|
||||||
}
|
}
|
||||||
// Taken off ace again (its key deleted): its report goes again.
|
// Taken off ace again (its key deleted): its report goes again.
|
||||||
if !gate.Placement(k, nil, true, placed) {
|
if !gate.Assignments(k, nil, true, placed) {
|
||||||
t.Fatal("taking ace's placement away passed nothing on")
|
t.Fatal("taking ace's assignments away passed nothing on")
|
||||||
}
|
}
|
||||||
if v, none := agreedOf(set); v != "v26.12.1" || none != "" {
|
if v, none := agreedOf(set); v != "v26.12.1" || none != "" {
|
||||||
t.Fatalf("got %q, %q", v, none)
|
t.Fatalf("got %q, %q", v, none)
|
||||||
@@ -132,26 +117,26 @@ func TestToolchainGate(t *testing.T) {
|
|||||||
at time.Time // when ace wrote its report
|
at time.Time // when ace wrote its report
|
||||||
counts bool
|
counts bool
|
||||||
}{
|
}{
|
||||||
{"a report left before the placement", old, false},
|
{"a report left before the assignments", old, false},
|
||||||
{"a report written since the placement", placed.Add(30 * time.Second), true},
|
{"a report written since the assignments", placed.Add(30 * time.Second), true},
|
||||||
} {
|
} {
|
||||||
t.Run(c.name, func(t *testing.T) {
|
t.Run(c.name, func(t *testing.T) {
|
||||||
gate, set, said := start(t, true)
|
gate, set, said := start(t, true)
|
||||||
k, v := placing(t, "ace") // ace's key, first heard placing nothing
|
k, v := placing(t, "ace") // ace's key, first heard placing nothing
|
||||||
gate.Placement(k, v, false, old.Add(-time.Hour))
|
gate.Assignments(k, v, false, old.Add(-time.Hour))
|
||||||
gate.Settled()
|
gate.Settled()
|
||||||
k, v = placing(t, "ace", NodeBuildModule)
|
k, v = placing(t, "ace", NodeBuildModule)
|
||||||
if gate.Placement(k, v, false, placed) {
|
if gate.Assignments(k, v, false, placed) {
|
||||||
t.Fatal("a placement with no report passed something on")
|
t.Fatal("assignments with no report passed something on")
|
||||||
}
|
}
|
||||||
key, value := gateReport(t, "ace", "v26.12.0", c.at)
|
key, value := gateReport(t, "ace", "v26.12.0", c.at)
|
||||||
if got := gate.Toolchain(NodeBuildModule, key, value, false); got != c.counts {
|
if got := gate.Toolchain(NodeBuildModule, key, value, false); got != c.counts {
|
||||||
t.Fatalf("ace's report passed on: %v, want %v", got, c.counts)
|
t.Fatalf("ace's report passed on: %v, want %v", got, c.counts)
|
||||||
}
|
}
|
||||||
// ace's placement written again with another module beside: its standing does not move, nothing is said again.
|
// ace's assignments written again with another module beside: its standing does not move, nothing is said again.
|
||||||
k, v = placing(t, "ace", GoBuildModule, NodeBuildModule)
|
k, v = placing(t, "ace", GoBuildModule, NodeBuildModule)
|
||||||
if gate.Placement(k, v, false, placed.Add(time.Minute)) {
|
if gate.Assignments(k, v, false, placed.Add(time.Minute)) {
|
||||||
t.Fatal("the same placement of mesh-build-node changed what was passed on")
|
t.Fatal("the same assignments of mesh-build-node changed what was passed on")
|
||||||
}
|
}
|
||||||
if c.counts {
|
if c.counts {
|
||||||
return
|
return
|
||||||
@@ -169,47 +154,38 @@ func TestToolchainGate(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("no placement held: every report counts", func(t *testing.T) {
|
t.Run("no assignments held: every report counts", func(t *testing.T) {
|
||||||
gate, set, _ := start(t, false)
|
gate, set, _ := start(t, false)
|
||||||
key, value := gateReport(t, "ace", "v26.12.0", old)
|
key, value := gateReport(t, "ace", "v26.12.0", old)
|
||||||
gate.Toolchain(NodeBuildModule, key, value, false)
|
gate.Toolchain(NodeBuildModule, key, value, false)
|
||||||
gate.Settled()
|
gate.Settled()
|
||||||
if n := len(set.Entries("typescript")); n != 4 {
|
if n := len(set.Entries("typescript")); n != 4 {
|
||||||
t.Fatalf("before the first placement file every report counts; got %d", n)
|
t.Fatalf("before the first assignments file every report counts; got %d", n)
|
||||||
}
|
}
|
||||||
k, v := placing(t, "g14", NodeBuildModule)
|
k, v := placing(t, "g14", NodeBuildModule)
|
||||||
if !gate.Placement(k, v, false, old.Add(-time.Hour)) || len(set.Entries("typescript")) != 1 {
|
if !gate.Assignments(k, v, false, old.Add(-time.Hour)) || len(set.Entries("typescript")) != 1 {
|
||||||
t.Fatalf("the first placement leaves every node it does not place out; got %d", len(set.Entries("typescript")))
|
t.Fatalf("the first assignments leaves every node it does not place out; got %d", len(set.Entries("typescript")))
|
||||||
}
|
}
|
||||||
if !gate.Placement(k, nil, true, placed) || len(set.Entries("typescript")) != 4 {
|
if !gate.Assignments(k, nil, true, placed) || len(set.Entries("typescript")) != 4 {
|
||||||
t.Fatal("the last placement gone: every report counts again")
|
t.Fatal("the last assignments gone: every report counts again")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("the history: a module in the first revision heard of a key stands since before it", func(t *testing.T) {
|
t.Run("the history: a module in the first revision heard of a key stands since before it", func(t *testing.T) {
|
||||||
p := meshmoduleassignerclient.NewPlaced()
|
p := meshmoduleassignerclient.NewPlaced()
|
||||||
// ace in the form before, then converted: its module stands since before, the legacy key read no more.
|
// ace's node key is the first revision heard of it: its module stands since before.
|
||||||
k, v := placingLegacy(t, "ace", NodeBuildModule)
|
k, v := placing(t, "ace", NodeBuildModule)
|
||||||
p.Put(k, v, false, old)
|
p.Put(k, v, false, old)
|
||||||
k, v = placing(t, "ace", NodeBuildModule)
|
|
||||||
if _, changed, err := p.Put(k, v, false, placed); len(changed) != 0 || err != nil {
|
|
||||||
t.Errorf("the conversion of ace's key changed %v, %v", changed, err)
|
|
||||||
}
|
|
||||||
if _, changed, _ := p.Put("ace."+NodeBuildModule, []byte("{}"), false, placed); len(changed) != 0 {
|
if _, changed, _ := p.Put("ace."+NodeBuildModule, []byte("{}"), false, placed); len(changed) != 0 {
|
||||||
t.Errorf("a module key says which modules stand: %v", changed)
|
t.Errorf("a module key says which modules stand: %v", changed)
|
||||||
}
|
}
|
||||||
if k, v := placingLegacy(t, "ace"); true {
|
|
||||||
if _, changed, _ := p.Put(k, v, false, placed); len(changed) != 0 {
|
|
||||||
t.Errorf("the legacy key is read after the node key: %v", changed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
k2, v2 := placing(t, "g14")
|
k2, v2 := placing(t, "g14")
|
||||||
p.Put(k2, v2, false, old)
|
p.Put(k2, v2, false, old)
|
||||||
k2, v2 = placing(t, "g14", NodeBuildModule)
|
k2, v2 = placing(t, "g14", NodeBuildModule)
|
||||||
if _, changed, _ := p.Put(k2, v2, false, placed); strings.Join(changed, ",") != NodeBuildModule {
|
if _, changed, _ := p.Put(k2, v2, false, placed); strings.Join(changed, ",") != NodeBuildModule {
|
||||||
t.Errorf("a module placed says changed %v", changed)
|
t.Errorf("a module placed says changed %v", changed)
|
||||||
}
|
}
|
||||||
if _, _, err := p.Put("placement.novox", []byte("not json"), false, old); err == nil {
|
if _, _, err := p.Put("novox.node", []byte("not json"), false, old); err == nil {
|
||||||
t.Error("a value that does not read was not said")
|
t.Error("a value that does not read was not said")
|
||||||
}
|
}
|
||||||
if s, ok := p.Since("ace", NodeBuildModule); !ok || !s.IsZero() {
|
if s, ok := p.Since("ace", NodeBuildModule); !ok || !s.IsZero() {
|
||||||
@@ -223,14 +199,14 @@ func TestToolchainGate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("a placement the bus refuses is said, and the part goes on", func(t *testing.T) {
|
t.Run("assignments the bus refuses is said, and the part goes on", func(t *testing.T) {
|
||||||
var logs bytes.Buffer
|
var logs bytes.Buffer
|
||||||
logger := log.New(&logs, "", 0)
|
logger := log.New(&logs, "", 0)
|
||||||
refused := errors.New("nats: permissions violation for publish to $JS.API.CONSUMER.CREATE.KV_mesh-delivery-assigner_placement")
|
refused := errors.New("nats: permissions violation for publish to $JS.API.CONSUMER.CREATE.KV_mesh-delivery-assigner_assignments")
|
||||||
gate, _, _ := start(t, false)
|
gate, _, _ := start(t, false)
|
||||||
for _, bus := range []refusingBus{{err: refused}, {refuseOpen: true, err: refused}} {
|
for _, bus := range []refusingBus{{err: refused}, {refuseOpen: true, err: refused}} {
|
||||||
if _, err := FollowPlacement(context.Background(), bus, gate, func(context.Context) {}, logger.Printf); err != nil {
|
if _, err := FollowAssignments(context.Background(), bus, gate, func(context.Context) {}, logger.Printf); err != nil {
|
||||||
t.Errorf("a refused placement stopped the start: %v", err)
|
t.Errorf("a refused assignments stopped the start: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !strings.Contains(logs.String(), "cannot be watched") || !strings.Contains(logs.String(), "cannot be read") {
|
if !strings.Contains(logs.String(), "cannot be watched") || !strings.Contains(logs.String(), "cannot be read") {
|
||||||
|
|||||||
@@ -303,7 +303,7 @@ func startBedWith(t *testing.T, closed bool) *bed {
|
|||||||
viewstest.Serve(t, ctx, js, ctl, indexerModule, st, indexerModule+".indexer_read_"+st, readers...)
|
viewstest.Serve(t, ctx, js, ctl, indexerModule, st, indexerModule+".indexer_read_"+st, readers...)
|
||||||
}
|
}
|
||||||
b.packages = viewstest.Serve(t, ctx, js, ctl, indexerModule, meshcatalog.StatePackages, indexerModule+".indexer_read_packages", readers...)
|
b.packages = viewstest.Serve(t, ctx, js, ctl, indexerModule, meshcatalog.StatePackages, indexerModule+".indexer_read_packages", readers...)
|
||||||
viewstest.Serve(t, ctx, js, ctl, assignerModule, "placement", assignerModule+".assigner_read_placement", readers...)
|
viewstest.Serve(t, ctx, js, ctl, assignerModule, "assignments", assignerModule+".assigner_read_assignments", readers...)
|
||||||
b.builds = mk(builds.BuilderBuildsBucket, builds.BuilderBuildsHistory)
|
b.builds = mk(builds.BuilderBuildsBucket, builds.BuilderBuildsHistory)
|
||||||
b.instances = mk(instancesBucket, 1)
|
b.instances = mk(instancesBucket, 1)
|
||||||
// The tag asks and their bounds (design 51 *Tagged versions*): the bounds declared key-ttl, so an ended bound leaves the
|
// The tag asks and their bounds (design 51 *Tagged versions*): the bounds declared key-ttl, so an ended bound leaves the
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// The indexer's modules, manifests, candidates and packages and the assigner's placement are their owners' states, read by the
|
// The indexer's modules, manifests, candidates and packages and the assigner's assignments are their owners' states, read by the
|
||||||
// first view of their read tools and then their views families, never their buckets (ADR 0615 §1, DLV-M1). An owner that
|
// first view of their read tools and then their views families, never their buckets (ADR 0615 §1, DLV-M1). An owner that
|
||||||
// is down never holds the start: each is read on a goroutine once its first view is answered, and until then the
|
// is down never holds the start: each is read on a goroutine once its first view is answered, and until then the
|
||||||
// unanswered first view is the builder's finding.
|
// unanswered first view is the builder's finding.
|
||||||
@@ -105,7 +105,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
placement, err := reader.Follow(assignerModule, "placement", assignerModule+".assigner_read_placement")
|
assignments, err := reader.Follow(assignerModule, "assignments", assignerModule+".assigner_read_assignments")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -258,11 +258,11 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
|||||||
// The toolchains first, so a build that starts from the first modules value finds the nodes.
|
// The toolchains first, so a build that starts from the first modules value finds the nodes.
|
||||||
watch := newToolchainWatch(ctx, js, tools, b.NodesChanged, logger)
|
watch := newToolchainWatch(ctx, js, tools, b.NodesChanged, logger)
|
||||||
// Only the toolchain reports of nodes the catalogue places the build module on count (issue 1940, ADR 0549 §3): the
|
// Only the toolchain reports of nodes the catalogue places the build module on count (issue 1940, ADR 0549 §3): the
|
||||||
// placement is settled first, so the toolchains settle through the gate already knowing it.
|
// assignments is settled first, so the toolchains settle through the gate already knowing it.
|
||||||
gate := buildrun.NewToolchainGate([]string{buildrun.GoBuildModule, buildrun.NodeBuildModule}, tools.Put, logger.Printf)
|
gate := buildrun.NewToolchainGate([]string{buildrun.GoBuildModule, buildrun.NodeBuildModule}, tools.Put, logger.Printf)
|
||||||
watch.put = gate.Toolchain
|
watch.put = gate.Toolchain
|
||||||
// What each counted node's engine reads (ADR 0618 §2, ADR 0624 §1): asked through the node-engine seat's verb as the
|
// What each counted node's engine reads (ADR 0618 §2, ADR 0624 §1): asked through the node-engine seat's verb as the
|
||||||
// builder's own account, for every node the placement names, and kept in the state engine-reads once it is bound.
|
// builder's own account, for every node the assignments names, and kept in the state engine-reads once it is bound.
|
||||||
reads := newEngineReads(calls.CallOn, gate, logger.Printf)
|
reads := newEngineReads(calls.CallOn, gate, logger.Printf)
|
||||||
// The old reference form's window over them (DLV-B9), kept in the state old-form once it is bound; builder_old_form
|
// The old reference form's window over them (DLV-B9), kept in the state old-form once it is bound; builder_old_form
|
||||||
// reads it and drops a retired node by hand.
|
// reads it and drops a retired node by hand.
|
||||||
@@ -288,20 +288,20 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
|||||||
})
|
})
|
||||||
gate.OnPlaced = reads.placed
|
gate.OnPlaced = reads.placed
|
||||||
b.Forms = reads.built
|
b.Forms = reads.built
|
||||||
// Until the placement's first view is answered the gate passes every report on, as before the placement is settled.
|
// Until the assignments's first view is answered the gate passes every report on, as before the assignments is settled.
|
||||||
placementErrs := make(chan error, 1)
|
assignmentsErrs := make(chan error, 1)
|
||||||
go func() {
|
go func() {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
case <-placement.Follower().Ready():
|
case <-assignments.Follower().Ready():
|
||||||
}
|
}
|
||||||
pe, err := buildrun.FollowPlacement(ctx, reader, gate, b.NodesChanged, logger.Printf)
|
pe, err := buildrun.FollowAssignments(ctx, reader, gate, b.NodesChanged, logger.Printf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
placementErrs <- err
|
assignmentsErrs <- err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
placementErrs <- <-pe
|
assignmentsErrs <- <-pe
|
||||||
}()
|
}()
|
||||||
if err := watch.start(); err != nil {
|
if err := watch.start(); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -456,7 +456,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
|||||||
return nil
|
return nil
|
||||||
case err := <-errs:
|
case err := <-errs:
|
||||||
return err
|
return err
|
||||||
case err := <-placementErrs:
|
case err := <-assignmentsErrs:
|
||||||
return err
|
return err
|
||||||
case err := <-watch.errs:
|
case err := <-watch.errs:
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const (
|
|||||||
|
|
||||||
// indexerModule is the holder of the states modules, manifests, candidates and packages the builder follows.
|
// indexerModule is the holder of the states modules, manifests, candidates and packages the builder follows.
|
||||||
indexerModule = "mesh-delivery-indexer"
|
indexerModule = "mesh-delivery-indexer"
|
||||||
// assignerModule is the holder of the state placement.
|
// assignerModule is the holder of the state assignments.
|
||||||
assignerModule = buildrun.AssignerModule
|
assignerModule = buildrun.AssignerModule
|
||||||
|
|
||||||
// sourceBucket is the gate's state source (reads: mesh-delivery-gate.source), the heads a candidate's base names.
|
// sourceBucket is the gate's state source (reads: mesh-delivery-gate.source), the heads a candidate's base names.
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ func TestTheNamesAreTheManifests(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, view := range []string{indexerModule + ".views.modules", indexerModule + ".views.manifests", indexerModule + ".views.candidates",
|
for _, view := range []string{indexerModule + ".views.modules", indexerModule + ".views.manifests", indexerModule + ".views.candidates",
|
||||||
indexerModule + ".views.packages", assignerModule + ".views.placement"} {
|
indexerModule + ".views.packages", assignerModule + ".views.assignments"} {
|
||||||
if !slices.Contains(m.Consumes, view) {
|
if !slices.Contains(m.Consumes, view) {
|
||||||
t.Errorf("the builder follows %s, its manifest consumes %v", view, m.Consumes)
|
t.Errorf("the builder follows %s, its manifest consumes %v", view, m.Consumes)
|
||||||
}
|
}
|
||||||
@@ -41,7 +41,7 @@ func TestTheNamesAreTheManifests(t *testing.T) {
|
|||||||
for want, got := range map[string]string{
|
for want, got := range map[string]string{
|
||||||
"mesh-delivery-gate_source": sourceBucket, "mesh-build-go_steps": buildrun.StepsBucket("mesh-build-go"),
|
"mesh-delivery-gate_source": sourceBucket, "mesh-build-go_steps": buildrun.StepsBucket("mesh-build-go"),
|
||||||
"mesh-build-node_toolchains": buildrun.ToolchainsBucket("mesh-build-node"), "mesh-delivery-indexer_packages": packagesBucket,
|
"mesh-build-node_toolchains": buildrun.ToolchainsBucket("mesh-build-node"), "mesh-delivery-indexer_packages": packagesBucket,
|
||||||
"mesh-delivery-assigner_placement": buildrun.PlacementBucket,
|
"mesh-delivery-assigner_assignments": buildrun.AssignmentsBucket,
|
||||||
} {
|
} {
|
||||||
if want != got {
|
if want != got {
|
||||||
t.Errorf("a bucket is %q, want %q", got, want)
|
t.Errorf("a bucket is %q, want %q", got, want)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// oldForm keeps the old reference form's window (design 51 *Every build keeps the old reference form*, DLV-B9) in the
|
// oldForm keeps the old reference form's window (design 51 *Every build keeps the old reference form*, DLV-B9) in the
|
||||||
// state old-form: stepped on every change of what the counted nodes state, of the placement, on a hand drop and at each
|
// state old-form: stepped on every change of what the counted nodes state, of the assignments, on a hand drop and at each
|
||||||
// build key written, never on a timer. A node holding it seven days is the finding old-form-held; its close is said once
|
// build key written, never on a timer. A node holding it seven days is the finding old-form-held; its close is said once
|
||||||
// as version.old-form-closed and in the builder's log.
|
// as version.old-form-closed and in the builder's log.
|
||||||
type oldForm struct {
|
type oldForm struct {
|
||||||
@@ -84,7 +84,7 @@ func (o *oldForm) dropped() map[string]bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// step moves the window to what counted states.
|
// step moves the window to what counted states.
|
||||||
// named is every node the read named (the placement's nodes before any is dropped), counted the statements of those
|
// named is every node the read named (the assignments's nodes before any is dropped), counted the statements of those
|
||||||
// still counted: a read naming no node judges nothing (window.StepWindow), so it never closes the window.
|
// still counted: a read naming no node judges nothing (window.StepWindow), so it never closes the window.
|
||||||
func (o *oldForm) step(ctx context.Context, named []string, counted map[string][]string) {
|
func (o *oldForm) step(ctx context.Context, named []string, counted map[string][]string) {
|
||||||
o.mu.Lock()
|
o.mu.Lock()
|
||||||
@@ -106,7 +106,7 @@ func (o *oldForm) step(ctx context.Context, named []string, counted map[string][
|
|||||||
n, next.Since[n].Format(time.RFC3339)),
|
n, next.Since[n].Format(time.RFC3339)),
|
||||||
headline: "A node holds the old reference form",
|
headline: "A node holds the old reference form",
|
||||||
explanation: "The builder writes an older reference form into every build key while a counted node's engine reads none of the newest (ADR 0624 §1); this node has held it seven days.",
|
explanation: "The builder writes an older reference form into every build key while a counted node's engine reads none of the newest (ADR 0624 §1); this node has held it seven days.",
|
||||||
resolved: "The node's engine states the newest form, the node leaves the placement, or it is dropped as retired.",
|
resolved: "The node's engine states the newest form, the node leaves the assignments, or it is dropped as retired.",
|
||||||
needs: "builder_old_form shows the window; a retired node is dropped there by hand; builder_engine_reads asks the node again."})
|
needs: "builder_old_form shows the window; a retired node is dropped there by hand; builder_engine_reads asks the node again."})
|
||||||
}
|
}
|
||||||
o.overdue = overdue
|
o.overdue = overdue
|
||||||
|
|||||||
@@ -10,14 +10,14 @@ import (
|
|||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||||
"git.novox.be/novox/mesh-sdk/go/process"
|
"git.novox.be/novox/mesh-sdk/go/process"
|
||||||
|
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
|
||||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient/enginereads"
|
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient/enginereads"
|
||||||
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EngineReadsState is the builder's state of what each counted node's engine states it reads (novox/hq ADR 0618 §2, ADR
|
// EngineReadsState is the builder's state of what each counted node's engine states it reads (novox/hq ADR 0618 §2, ADR
|
||||||
// 0624 §1): one key `<node>`, written from the node-engine seat's verb manifest-reads at the builder's start, on the
|
// 0624 §1): one key `<node>`, written from the node-engine seat's verb manifest-reads at the builder's start, on the
|
||||||
// seat's event manifest-reads-changed for that node, and when a person asks again (builder_engine_reads); a node that
|
// seat's event manifest-reads-changed for that node, and when a person asks again (builder_engine_reads); a node that
|
||||||
// is away keeps its last statement, and its key is removed when the placement no longer names the node. A counted node
|
// is away keeps its last statement, and its key is removed when the assignments no longer names the node. A counted node
|
||||||
// with no key reads as stating the baseline: its first forms. No node's record is read in its place.
|
// with no key reads as stating the baseline: its first forms. No node's record is read in its place.
|
||||||
const EngineReadsState = "engine-reads"
|
const EngineReadsState = "engine-reads"
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ func newEngineReads(call enginereads.Caller, gate *buildrun.ToolchainGate, logf
|
|||||||
}
|
}
|
||||||
|
|
||||||
// stepWindow moves the old form's window to what the counted nodes state now (design 51 *Every build keeps the old
|
// stepWindow moves the old form's window to what the counted nodes state now (design 51 *Every build keeps the old
|
||||||
// reference form*): on a statement's change, the placement's and a hand drop, and at each build key written.
|
// reference form*): on a statement's change, the assignments's and a hand drop, and at each build key written.
|
||||||
func (r *engineReads) stepWindow() {
|
func (r *engineReads) stepWindow() {
|
||||||
if r.window == nil {
|
if r.window == nil {
|
||||||
return
|
return
|
||||||
@@ -56,7 +56,7 @@ func (r *engineReads) stepWindow() {
|
|||||||
r.window.step(ctx, r.named(), counted)
|
r.window.step(ctx, r.named(), counted)
|
||||||
}
|
}
|
||||||
|
|
||||||
// named are the nodes the placement names, none dropped: what the window's step judges a read by.
|
// named are the nodes the assignments names, none dropped: what the window's step judges a read by.
|
||||||
func (r *engineReads) named() []string {
|
func (r *engineReads) named() []string {
|
||||||
r.mu.Lock()
|
r.mu.Lock()
|
||||||
defer r.mu.Unlock()
|
defer r.mu.Unlock()
|
||||||
@@ -86,7 +86,7 @@ func (r *engineReads) bind(ctx context.Context, kv jetstream.KeyValue, errs chan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// placed takes the placement in: a node it names newly is asked, one it no longer names is forgotten, key and all. The
|
// placed takes the assignments in: a node it names newly is asked, one it no longer names is forgotten, key and all. The
|
||||||
// first call asks every counted node (the builder's start).
|
// first call asks every counted node (the builder's start).
|
||||||
func (r *engineReads) placed() {
|
func (r *engineReads) placed() {
|
||||||
nodes := r.gate.Nodes()
|
nodes := r.gate.Nodes()
|
||||||
@@ -96,7 +96,7 @@ func (r *engineReads) placed() {
|
|||||||
r.mu.Unlock()
|
r.mu.Unlock()
|
||||||
for _, node := range nodes {
|
for _, node := range nodes {
|
||||||
if first || !slices.Contains(before, node) {
|
if first || !slices.Contains(before, node) {
|
||||||
r.keeper.AskNode(node, "counted: the placement names it")
|
r.keeper.AskNode(node, "counted: the assignments names it")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, node := range before {
|
for _, node := range before {
|
||||||
|
|||||||
@@ -20,15 +20,15 @@ import (
|
|||||||
"github.com/nats-io/nats.go"
|
"github.com/nats-io/nats.go"
|
||||||
"github.com/nats-io/nats.go/jetstream"
|
"github.com/nats-io/nats.go/jetstream"
|
||||||
|
|
||||||
|
"git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
|
||||||
|
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient/enginereads"
|
||||||
|
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||||
"git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
|
||||||
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
|
||||||
sweep "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshartifactsweep"
|
sweep "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshartifactsweep"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds/forms"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds/forms"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg"
|
||||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient/enginereads"
|
|
||||||
|
|
||||||
"mesh-delivery-builder/internal/build"
|
"mesh-delivery-builder/internal/build"
|
||||||
"mesh-delivery-builder/internal/versions"
|
"mesh-delivery-builder/internal/versions"
|
||||||
@@ -36,7 +36,7 @@ import (
|
|||||||
|
|
||||||
// 1945 (a part exits at start for a bucket it works without): a bus that refuses every toolchains bucket leaves the
|
// 1945 (a part exits at start for a bucket it works without): a bus that refuses every toolchains bucket leaves the
|
||||||
// watch unstarted and the part running, said; a refused bucket it cannot work without is an error naming the bucket and
|
// watch unstarted and the part running, said; a refused bucket it cannot work without is an error naming the bucket and
|
||||||
// its grant. The gate over the seat's placement, and its refusal, are mesh-sdk's (buildrun.ToolchainGate,
|
// its grant. The gate over the seat's assignments, and its refusal, are mesh-sdk's (buildrun.ToolchainGate,
|
||||||
// issue 774).
|
// issue 774).
|
||||||
func TestARefusedBucketIsSaidAndThePartGoesOn(t *testing.T) {
|
func TestARefusedBucketIsSaidAndThePartGoesOn(t *testing.T) {
|
||||||
t.Run("a floor's wake is heard on the events consumer (design 51 The floor)", aFloorsWakeIsHeard)
|
t.Run("a floor's wake is heard on the events consumer (design 51 The floor)", aFloorsWakeIsHeard)
|
||||||
@@ -69,28 +69,28 @@ func TestARefusedBucketIsSaidAndThePartGoesOn(t *testing.T) {
|
|||||||
t.Run("ADR 0624: the counted nodes' engine reads", theCountedNodesEngineReads)
|
t.Run("ADR 0624: the counted nodes' engine reads", theCountedNodesEngineReads)
|
||||||
}
|
}
|
||||||
|
|
||||||
// theCountedNodesEngineReads: the builder asks the node-engine seat's verb of every node the placement names; a node away
|
// theCountedNodesEngineReads: the builder asks the node-engine seat's verb of every node the assignments names; a node away
|
||||||
// and never asked has no statement and reads as the baseline's first forms, with no node record read in its place; an
|
// and never asked has no statement and reads as the baseline's first forms, with no node record read in its place; an
|
||||||
// away node keeps its last statement; a node the placement no longer names leaves the count, its key removed; and
|
// away node keeps its last statement; a node the assignments no longer names leaves the count, its key removed; and
|
||||||
// builder_engine_reads drops one node's key.
|
// builder_engine_reads drops one node's key.
|
||||||
func theCountedNodesEngineReads(t *testing.T) {
|
func theCountedNodesEngineReads(t *testing.T) {
|
||||||
gate := buildrun.NewToolchainGate(nil, func(string, string, []byte, bool) bool { return false }, t.Logf)
|
gate := buildrun.NewToolchainGate(nil, func(string, string, []byte, bool) bool { return false }, t.Logf)
|
||||||
place := func(nodes ...string) {
|
place := func(nodes ...string) {
|
||||||
for _, node := range []string{"n1", "n2"} {
|
for _, node := range []string{"n1", "n2"} {
|
||||||
pl := meshmoduleassigner.Placement{Node: node}
|
pl := meshmoduleassigner.Assignments{Node: node}
|
||||||
if slices.Contains(nodes, node) {
|
if slices.Contains(nodes, node) {
|
||||||
pl.Modules = []meshmoduleassigner.PlacedModule{{Module: "web"}}
|
pl.Modules = []meshmoduleassigner.PlacedModule{{Module: "web"}}
|
||||||
}
|
}
|
||||||
values, err := meshmoduleassignerclient.PlacementValues(pl)
|
values, err := meshmoduleassignerclient.AssignmentsValues(pl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
for key, raw := range values {
|
for key, raw := range values {
|
||||||
gate.Placement(key, raw, false, time.Now())
|
gate.Assignments(key, raw, false, time.Now())
|
||||||
}
|
}
|
||||||
if !slices.Contains(nodes, node) {
|
if !slices.Contains(nodes, node) {
|
||||||
key, _ := meshmoduleassignerclient.ModuleKey(node, "web")
|
key, _ := meshmoduleassignerclient.ModuleKey(node, "web")
|
||||||
gate.Placement(key, nil, true, time.Now())
|
gate.Assignments(key, nil, true, time.Now())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -125,7 +125,7 @@ func theCountedNodesEngineReads(t *testing.T) {
|
|||||||
if _, counted = r.forms(); !slices.Equal(counted["n2"], []string{"form:new"}) || r.keeper.Statements()["n2"].Answered {
|
if _, counted = r.forms(); !slices.Equal(counted["n2"], []string{"form:new"}) || r.keeper.Statements()["n2"].Answered {
|
||||||
t.Errorf("away again, n2 reads %v (%+v)", counted["n2"], r.keeper.Statements()["n2"])
|
t.Errorf("away again, n2 reads %v (%+v)", counted["n2"], r.keeper.Statements()["n2"])
|
||||||
}
|
}
|
||||||
// n2 retired: it leaves the placement, and its key goes.
|
// n2 retired: it leaves the assignments, and its key goes.
|
||||||
place("n1")
|
place("n1")
|
||||||
r.placed()
|
r.placed()
|
||||||
if _, counted = r.forms(); len(counted) != 1 || kept["n2"] != nil {
|
if _, counted = r.forms(); len(counted) != 1 || kept["n2"] != nil {
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ require (
|
|||||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/npmpackageregistry v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-delivery/contracts/go/seats/npmpackageregistry v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/ociartifactregistry v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-delivery/contracts/go/seats/ociartifactregistry 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/go/clients/nodeengineclient v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery v0.0.0-00010101000000-000000000000
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ import (
|
|||||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||||
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds/forms"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds/forms"
|
||||||
|
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||||
repotrunk "git.novox.be/novox/mesh-delivery/sdk/trunk"
|
repotrunk "git.novox.be/novox/mesh-delivery/sdk/trunk"
|
||||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||||
|
|
||||||
@@ -92,7 +92,7 @@ type Builder struct {
|
|||||||
// Tags answers a check's build's tag from the kept tag asks; nil numbers no check's build.
|
// Tags answers a check's build's tag from the kept tag asks; nil numbers no check's build.
|
||||||
Tags TagOf
|
Tags TagOf
|
||||||
// Forms answers what each node's engine states it reads (ADR 0618 §2, ADR 0624 §1): present, the nodes whose engine
|
// Forms answers what each node's engine states it reads (ADR 0618 §2, ADR 0624 §1): present, the nodes whose engine
|
||||||
// answered when last asked, and counted, every node the placement names, present or away, a node with no statement
|
// answered when last asked, and counted, every node the assignments names, present or away, a node with no statement
|
||||||
// stating nothing (the baseline's first forms). A done key names each artifact in the forms these keep, the oldest in
|
// stating nothing (the baseline's first forms). A done key names each artifact in the forms these keep, the oldest in
|
||||||
// its single reference, and its needs. Nil: no node states anything, and the first forms are kept.
|
// its single reference, and its needs. Nil: no node states anything, and the first forms are kept.
|
||||||
Forms func() (present, counted map[string][]string)
|
Forms func() (present, counted map[string][]string)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import (
|
|||||||
// builder's state engine-reads; this is only the window over them: open while a counted node-engine, present or away,
|
// builder's state engine-reads; this is only the window over them: open while a counted node-engine, present or away,
|
||||||
// reads of some artifact kind none of the newest form the builder knows for it, so an older form stays in new build keys
|
// reads of some artifact kind none of the newest form the builder knows for it, so an older form stays in new build keys
|
||||||
// for it; closed, said once with `old-form-closed` and a log line, when none does. It moves on what moves a statement
|
// for it; closed, said once with `old-form-closed` and a log line, when none does. It moves on what moves a statement
|
||||||
// (the node-engine seat's verb `manifest-reads` and its event `manifest-reads-changed`), the placement, a hand drop and
|
// (the node-engine seat's verb `manifest-reads` and its event `manifest-reads-changed`), the assignments, a hand drop and
|
||||||
// each build key written; never on a timer.
|
// each build key written; never on a timer.
|
||||||
|
|
||||||
// OldFormState is the builder's state of the window: one key, OldFormKey, an OldFormWindow. builder_old_form reads it.
|
// OldFormState is the builder's state of the window: one key, OldFormKey, an OldFormWindow. builder_old_form reads it.
|
||||||
@@ -94,7 +94,7 @@ const FindingAfter = 7 * 24 * time.Hour
|
|||||||
|
|
||||||
// Step moves the window by mesh-sdk's one window rule (window.StepWindow): named is every counted node the read named,
|
// Step moves the window by mesh-sdk's one window rule (window.StepWindow): named is every counted node the read named,
|
||||||
// those dropped by hand included, and holding those of them holding the window (Holding). A read naming no node (no
|
// those dropped by hand included, and holding those of them holding the window (Holding). A read naming no node (no
|
||||||
// placement read yet, an empty node_haves) judges nothing, so it never closes the window. It answers the new window,
|
// assignments read yet, an empty node_haves) judges nothing, so it never closes the window. It answers the new window,
|
||||||
// whether it closed in this step (so the builder says `old-form-closed` and its log line once), and the nodes holding it
|
// whether it closed in this step (so the builder says `old-form-closed` and its log line once), and the nodes holding it
|
||||||
// past FindingAfter (the finding, cleared for every other node).
|
// past FindingAfter (the finding, cleared for every other node).
|
||||||
func (w OldFormWindow) Step(named, holding []string, now time.Time) (next OldFormWindow, closed bool, overdue []string) {
|
func (w OldFormWindow) Step(named, holding []string, now time.Time) (next OldFormWindow, closed bool, overdue []string) {
|
||||||
@@ -146,7 +146,7 @@ const OldFormDescription = "The old reference form's window (design 51 *Every bu
|
|||||||
"the counted nodes, present or away, whose node-engine reads an older reference form alone and since when (a node holding it seven days " +
|
"the counted nodes, present or away, whose node-engine reads an older reference form alone and since when (a node holding it seven days " +
|
||||||
"is the builder's finding " + OldFormHeld + "), when it last closed and who held it then, and the retired nodes dropped from the count by " +
|
"is the builder's finding " + OldFormHeld + "), when it last closed and who held it then, and the retired nodes dropped from the count by " +
|
||||||
"hand. With drop (a node) and why, drops that retired node from the count by hand, a hand act the builder answers; a node still placed " +
|
"hand. With drop (a node) and why, drops that retired node from the count by hand, a hand act the builder answers; a node still placed " +
|
||||||
"leaves the count by itself only when the placement no longer names it. builder_engine_reads shows what each node's engine states."
|
"leaves the count by itself only when the assignments no longer names it. builder_engine_reads shows what each node's engine states."
|
||||||
|
|
||||||
// AnswerOldForm is builder_old_form's read: the window as the state holds it.
|
// AnswerOldForm is builder_old_form's read: the window as the state holds it.
|
||||||
func AnswerOldForm(r Reader) (OldFormWindow, error) {
|
func AnswerOldForm(r Reader) (OldFormWindow, error) {
|
||||||
|
|||||||
@@ -365,7 +365,7 @@ func TestTheBuildersVersions(t *testing.T) {
|
|||||||
t.Error("a drop with no why is taken")
|
t.Error("a drop with no why is taken")
|
||||||
}
|
}
|
||||||
// Security-upgrade F3 (review round 1), by mesh-sdk's window.StepWindow: a read naming no node (an empty
|
// Security-upgrade F3 (review round 1), by mesh-sdk's window.StepWindow: a read naming no node (an empty
|
||||||
// node_haves, the placement not read yet) never closes the window, and its holder's seven days still count.
|
// node_haves, the assignments not read yet) never closes the window, and its holder's seven days still count.
|
||||||
if held, shut, late := w.Step(nil, nil, t0.Add(FindingAfter+time.Minute)); shut || !held.Open || !reflect.DeepEqual(late, []string{"c"}) {
|
if held, shut, late := w.Step(nil, nil, t0.Add(FindingAfter+time.Minute)); shut || !held.Open || !reflect.DeepEqual(late, []string{"c"}) {
|
||||||
t.Fatalf("a read naming no node: %+v closed=%v overdue %v, want the window open and c overdue", held, shut, late)
|
t.Fatalf("a read naming no node: %+v closed=%v overdue %v, want the window open and c overdue", held, shut, late)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ func TestManifestDeclarations(t *testing.T) { // 1607/AC1
|
|||||||
// 1887/A4c: the builder chooses no node, so it reads neither presence nor have, invokes no build module's tool and
|
// 1887/A4c: the builder chooses no node, so it reads neither presence nor have, invokes no build module's tool and
|
||||||
// holds no capacity; it puts asks on the three build seats and reads their steps (ADR 0525 §1, §4, §5).
|
// holds no capacity; it puts asks on the three build seats and reads their steps (ADR 0525 §1, §4, §5).
|
||||||
if want := []string{"mesh-build-go.steps", "mesh-build-go.toolchains", "mesh-build-image.steps", "mesh-build-node.steps", "mesh-build-node.toolchains",
|
if want := []string{"mesh-build-go.steps", "mesh-build-go.toolchains", "mesh-build-image.steps", "mesh-build-node.steps", "mesh-build-node.toolchains",
|
||||||
"mesh-delivery-assigner.placement", "mesh-delivery-gate.source"}; !reflect.DeepEqual(sorted(m.Reads), want) {
|
"mesh-delivery-assigner.assignments", "mesh-delivery-gate.source"}; !reflect.DeepEqual(sorted(m.Reads), want) {
|
||||||
t.Errorf("reads = %v, want %v", m.Reads, want)
|
t.Errorf("reads = %v, want %v", m.Reads, want)
|
||||||
}
|
}
|
||||||
if want := []string{"node-build-go", "node-build-image", "node-build-node"}; !reflect.DeepEqual(sorted(m.Uses), want) {
|
if want := []string{"node-build-go", "node-build-image", "node-build-node"}; !reflect.DeepEqual(sorted(m.Uses), want) {
|
||||||
@@ -111,18 +111,18 @@ func TestManifestDeclarations(t *testing.T) { // 1607/AC1
|
|||||||
// 1937/AC3: the builder asks the controller whether the artifact store still holds a done key's blobs (held), and no
|
// 1937/AC3: the builder asks the controller whether the artifact store still holds a done key's blobs (held), and no
|
||||||
// other tool; it holds no capacity.
|
// other tool; it holds no capacity.
|
||||||
// 1955/L7 (ADR 0571 §2): the seat mesh-artifact-sweep's held, the controller's `held` is no longer asked (issue 1955).
|
// 1955/L7 (ADR 0571 §2): the seat mesh-artifact-sweep's held, the controller's `held` is no longer asked (issue 1955).
|
||||||
// DLV-M1 (ADR 0615 §1): the indexer's modules, manifests and candidates and the assigner's placement are asked by their
|
// DLV-M1 (ADR 0615 §1): the indexer's modules, manifests and candidates and the assigner's assignments are asked by their
|
||||||
// read tools and followed by their views families, never read; so are the indexer's packages, whose names a package is
|
// read tools and followed by their views families, never read; so are the indexer's packages, whose names a package is
|
||||||
// numbered under (design 32).
|
// numbered under (design 32).
|
||||||
for _, r := range m.Reads {
|
for _, r := range m.Reads {
|
||||||
// The toolchain gate follows the assigner's placement through meshmoduleassignerclient (buildrun.PlacementBucket), so
|
// The toolchain gate follows the assigner's assignments through meshmoduleassignerclient (buildrun.AssignmentsBucket), so
|
||||||
// it declares that one read, as the console and route-proxy do; everything else of theirs is asked by tools.
|
// it declares that one read, as the console and route-proxy do; everything else of theirs is asked by tools.
|
||||||
if strings.HasPrefix(r, "mesh-delivery-indexer.") || (strings.HasPrefix(r, "mesh-delivery-assigner.") && r != "mesh-delivery-assigner.placement") {
|
if strings.HasPrefix(r, "mesh-delivery-indexer.") || (strings.HasPrefix(r, "mesh-delivery-assigner.") && r != "mesh-delivery-assigner.assignments") {
|
||||||
t.Errorf("reads %s: the indexer's and the assigner's state is read by their tools and views families alone", r)
|
t.Errorf("reads %s: the indexer's and the assigner's state is read by their tools and views families alone", r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Design 51 *The floor*: the floor's three sources, the registry seats' versions, bridge_tags and indexer_requirements.
|
// Design 51 *The floor*: the floor's three sources, the registry seats' versions, bridge_tags and indexer_requirements.
|
||||||
if want := []string{"mesh-delivery-assigner.assigner_read_placement", "mesh-delivery-forge-bridge.bridge_tags", "mesh-delivery-indexer.indexer_read_candidates", "mesh-delivery-indexer.indexer_read_manifests",
|
if want := []string{"mesh-delivery-assigner.assigner_read_assignments", "mesh-delivery-forge-bridge.bridge_tags", "mesh-delivery-indexer.indexer_read_candidates", "mesh-delivery-indexer.indexer_read_manifests",
|
||||||
"mesh-delivery-indexer.indexer_read_modules", "mesh-delivery-indexer.indexer_read_packages", "mesh-delivery-indexer.indexer_requirements", "seat:container-image-registry.remove", "seat:container-image-registry.versions",
|
"mesh-delivery-indexer.indexer_read_modules", "mesh-delivery-indexer.indexer_read_packages", "mesh-delivery-indexer.indexer_requirements", "seat:container-image-registry.remove", "seat:container-image-registry.versions",
|
||||||
"seat:go-package-registry.remove", "seat:go-package-registry.versions", "seat:mesh-artifact-sweep.held", "seat:mesh-hand-acts.hand-act", "seat:node-engine.manifest-reads", "seat:npm-package-registry.remove",
|
"seat:go-package-registry.remove", "seat:go-package-registry.versions", "seat:mesh-artifact-sweep.held", "seat:mesh-hand-acts.hand-act", "seat:node-engine.manifest-reads", "seat:npm-package-registry.remove",
|
||||||
"seat:npm-package-registry.versions", "seat:oci-artifact-registry.remove", "seat:oci-artifact-registry.versions"}; !reflect.DeepEqual(sorted(m.Invokes), want) || len(m.Config) != 0 {
|
"seat:npm-package-registry.versions", "seat:oci-artifact-registry.remove", "seat:oci-artifact-registry.versions"}; !reflect.DeepEqual(sorted(m.Invokes), want) || len(m.Config) != 0 {
|
||||||
@@ -137,7 +137,7 @@ func TestManifestDeclarations(t *testing.T) { // 1607/AC1
|
|||||||
if want := []string{"container-image-registry.kept", "container-image-registry.published", "container-image-registry.refused", "container-image-registry.removed",
|
if want := []string{"container-image-registry.kept", "container-image-registry.published", "container-image-registry.refused", "container-image-registry.removed",
|
||||||
"go-package-registry.kept", "go-package-registry.published", "go-package-registry.refused", "go-package-registry.removed",
|
"go-package-registry.kept", "go-package-registry.published", "go-package-registry.refused", "go-package-registry.removed",
|
||||||
"mesh-artifact-sweep.artifacts-gone", "mesh-build-go.toolchain.reported", "mesh-build-node.toolchain.reported", "mesh-catalog.manifest-accepted",
|
"mesh-artifact-sweep.artifacts-gone", "mesh-build-go.toolchain.reported", "mesh-build-node.toolchain.reported", "mesh-catalog.manifest-accepted",
|
||||||
"mesh-conditions.condition-cleared", "mesh-delivery-assigner.views.placement", "mesh-delivery-checker.tag-wanted", "mesh-delivery-forge-bridge.forge.**",
|
"mesh-conditions.condition-cleared", "mesh-delivery-assigner.views.assignments", "mesh-delivery-checker.tag-wanted", "mesh-delivery-forge-bridge.forge.**",
|
||||||
"mesh-delivery-indexer.views.candidates", "mesh-delivery-indexer.views.manifests", "mesh-delivery-indexer.views.modules",
|
"mesh-delivery-indexer.views.candidates", "mesh-delivery-indexer.views.manifests", "mesh-delivery-indexer.views.modules",
|
||||||
"mesh-delivery-indexer.views.packages", "node-engine.manifest-reads-changed",
|
"mesh-delivery-indexer.views.packages", "node-engine.manifest-reads-changed",
|
||||||
"npm-package-registry.kept", "npm-package-registry.published", "npm-package-registry.refused", "npm-package-registry.removed",
|
"npm-package-registry.kept", "npm-package-registry.published", "npm-package-registry.refused", "npm-package-registry.removed",
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
"oci-artifact-registry.published",
|
"oci-artifact-registry.published",
|
||||||
"oci-artifact-registry.refused",
|
"oci-artifact-registry.refused",
|
||||||
"oci-artifact-registry.removed",
|
"oci-artifact-registry.removed",
|
||||||
"mesh-delivery-assigner.views.placement",
|
"mesh-delivery-assigner.views.assignments",
|
||||||
"mesh-delivery-indexer.views.candidates",
|
"mesh-delivery-indexer.views.candidates",
|
||||||
"mesh-delivery-indexer.views.manifests",
|
"mesh-delivery-indexer.views.manifests",
|
||||||
"mesh-delivery-indexer.views.modules",
|
"mesh-delivery-indexer.views.modules",
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
"seat:npm-package-registry.versions",
|
"seat:npm-package-registry.versions",
|
||||||
"seat:oci-artifact-registry.remove",
|
"seat:oci-artifact-registry.remove",
|
||||||
"seat:oci-artifact-registry.versions",
|
"seat:oci-artifact-registry.versions",
|
||||||
"mesh-delivery-assigner.assigner_read_placement",
|
"mesh-delivery-assigner.assigner_read_assignments",
|
||||||
"mesh-delivery-indexer.indexer_read_candidates",
|
"mesh-delivery-indexer.indexer_read_candidates",
|
||||||
"mesh-delivery-indexer.indexer_read_manifests",
|
"mesh-delivery-indexer.indexer_read_manifests",
|
||||||
"mesh-delivery-indexer.indexer_read_modules",
|
"mesh-delivery-indexer.indexer_read_modules",
|
||||||
@@ -103,7 +103,7 @@
|
|||||||
"mesh-build-image.steps",
|
"mesh-build-image.steps",
|
||||||
"mesh-build-node.steps",
|
"mesh-build-node.steps",
|
||||||
"mesh-build-node.toolchains",
|
"mesh-build-node.toolchains",
|
||||||
"mesh-delivery-assigner.placement",
|
"mesh-delivery-assigner.assignments",
|
||||||
"mesh-delivery-gate.source"
|
"mesh-delivery-gate.source"
|
||||||
],
|
],
|
||||||
"uses": [
|
"uses": [
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ import (
|
|||||||
|
|
||||||
"mesh-delivery-checker/internal/check"
|
"mesh-delivery-checker/internal/check"
|
||||||
|
|
||||||
|
"git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
|
||||||
nec "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
nec "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||||
ne "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
ne "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||||
"git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
||||||
@@ -58,7 +58,7 @@ func (e engineAsks) ComposeCandidate(ctx context.Context, node string, ask ne.Co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// placedNodes names every node the seat mesh-placement places now, read from its state when a check composes (no
|
// placedNodes names every node the seat mesh-module-assigner places now, read from its state when a check composes (no
|
||||||
// watch: a check reads it once). A state that cannot be read is an error, so the check says its composition was not
|
// watch: a check reads it once). A state that cannot be read is an error, so the check says its composition was not
|
||||||
// made rather than passing with no node asked (review finding 4).
|
// made rather than passing with no node asked (review finding 4).
|
||||||
type placedNodes struct {
|
type placedNodes struct {
|
||||||
@@ -70,14 +70,14 @@ func (p placedNodes) Placed() ([]string, error) {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
kv, err := p.kv(ctx)
|
kv, err := p.kv(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("the state %s cannot be read: %w", buildrun.PlacementBucket, err)
|
return nil, fmt.Errorf("the state %s cannot be read: %w", buildrun.AssignmentsBucket, err)
|
||||||
}
|
}
|
||||||
lister, err := kv.ListKeys(ctx)
|
lister, err := kv.ListKeys(ctx)
|
||||||
if errors.Is(err, jetstream.ErrNoKeysFound) {
|
if errors.Is(err, jetstream.ErrNoKeysFound) {
|
||||||
return nil, nil // a placement that places nothing
|
return nil, nil // assignments that places nothing
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("the state %s cannot be listed: %w", buildrun.PlacementBucket, err)
|
return nil, fmt.Errorf("the state %s cannot be listed: %w", buildrun.AssignmentsBucket, err)
|
||||||
}
|
}
|
||||||
seen := map[string]bool{}
|
seen := map[string]bool{}
|
||||||
for key := range lister.Keys() {
|
for key := range lister.Keys() {
|
||||||
|
|||||||
@@ -25,13 +25,13 @@ import (
|
|||||||
"mesh-delivery-checker/internal/check"
|
"mesh-delivery-checker/internal/check"
|
||||||
|
|
||||||
gobuild "git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
gobuild "git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||||
|
"git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
|
||||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||||
nec "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
nec "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||||
|
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||||
ne "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
ne "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||||
"git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshwatchdogs"
|
"git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshwatchdogs"
|
||||||
"git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
|
||||||
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||||
@@ -323,13 +323,13 @@ func (b *bed) said(t *testing.T, words string) {
|
|||||||
// the seat verb's caller-named and plain subjects); the others never answer. It answers what it was asked.
|
// the seat verb's caller-named and plain subjects); the others never answer. It answers what it was asked.
|
||||||
func (b *bed) engine(t *testing.T, nodes ...string) func() []ne.ComposeCandidateInput {
|
func (b *bed) engine(t *testing.T, nodes ...string) func() []ne.ComposeCandidateInput {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
kv, err := b.js.CreateKeyValue(b.ctx, jetstream.KeyValueConfig{Bucket: buildrun.PlacementBucket, History: 1})
|
kv, err := b.js.CreateKeyValue(b.ctx, jetstream.KeyValueConfig{Bucket: buildrun.AssignmentsBucket, History: 1})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
for _, n := range nodes {
|
for _, n := range nodes {
|
||||||
pk, _ := meshmoduleassignerclient.NodeKey(n)
|
pk, _ := meshmoduleassignerclient.NodeKey(n)
|
||||||
raw, _ := meshmoduleassignerclient.EncodeNode(meshmoduleassigner.PlacementByNodeNodeValue{Node: n})
|
raw, _ := meshmoduleassignerclient.EncodeNode(meshmoduleassigner.AssignmentsByNodeNodeValue{Node: n})
|
||||||
if _, err := kv.Put(b.ctx, pk, raw); err != nil {
|
if _, err := kv.Put(b.ctx, pk, raw); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -466,7 +466,7 @@ func startBed(t *testing.T) *bed {
|
|||||||
for _, st := range []string{candidatesState, modulesState, manifestsState, meshcatalog.StatePackages, check.IndexerPredefinedState, seatDefinitionsState} {
|
for _, st := range []string{candidatesState, modulesState, manifestsState, meshcatalog.StatePackages, check.IndexerPredefinedState, seatDefinitionsState} {
|
||||||
viewstest.Serve(t, ctx, js, ctl, check.IndexerModule, st, check.IndexerModule+".indexer_read_"+strings.ReplaceAll(st, "-", "_"), "n1_"+check.CheckerModule)
|
viewstest.Serve(t, ctx, js, ctl, check.IndexerModule, st, check.IndexerModule+".indexer_read_"+strings.ReplaceAll(st, "-", "_"), "n1_"+check.CheckerModule)
|
||||||
}
|
}
|
||||||
viewstest.Serve(t, ctx, js, ctl, buildrun.AssignerModule, meshmoduleassigner.StatePlacement, buildrun.AssignerModule+".assigner_read_placement", "n1_"+check.CheckerModule)
|
viewstest.Serve(t, ctx, js, ctl, buildrun.AssignerModule, meshmoduleassigner.StateAssignments, buildrun.AssignerModule+".assigner_read_assignments", "n1_"+check.CheckerModule)
|
||||||
// 1929/AC1: the indexer's value of the repository's module, with no TypeScript manifest, so the Go build module alone
|
// 1929/AC1: the indexer's value of the repository's module, with no TypeScript manifest, so the Go build module alone
|
||||||
// judges the repository (an unknown repository is judged by both, issue 1887 A12).
|
// judges the repository (an unknown repository is judged by both, issue 1887 A12).
|
||||||
known, err := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Repository: repo, Commit: trunk, Path: "modules/m", None2: "the fingerprint is computed with its chain"})
|
known, err := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Repository: repo, Commit: trunk, Path: "modules/m", None2: "the fingerprint is computed with its chain"})
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import (
|
|||||||
|
|
||||||
"mesh-delivery-checker/internal/check"
|
"mesh-delivery-checker/internal/check"
|
||||||
|
|
||||||
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
||||||
@@ -145,7 +145,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
|||||||
logger.Printf("what the checker keeps could not be said on the bus, so readers of owed, previews and verdicts are not granted until it is: %v", err)
|
logger.Printf("what the checker keeps could not be said on the bus, so readers of owed, previews and verdicts are not granted until it is: %v", err)
|
||||||
}
|
}
|
||||||
// The candidates it checks, the modules and manifests that say which build modules judge a repository, the published
|
// The candidates it checks, the modules and manifests that say which build modules judge a repository, the published
|
||||||
// packages, the predefined keys and the placement are their owners' states (the indexer's, the assigner's), read by the
|
// packages, the predefined keys and the assignments are their owners' states (the indexer's, the assigner's), read by the
|
||||||
// first view of their read tools and then their views families, never their buckets (ADR 0615 §1, ADR 0596, DLV-M2).
|
// first view of their read tools and then their views families, never their buckets (ADR 0615 §1, ADR 0596, DLV-M2).
|
||||||
// An owner that is down never holds the start: the checks start once every first view is answered, and until then the
|
// An owner that is down never holds the start: the checks start once every first view is answered, and until then the
|
||||||
// unanswered first view is the checker's finding.
|
// unanswered first view is the checker's finding.
|
||||||
@@ -168,7 +168,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
|||||||
manifests := followOwner(check.IndexerModule, manifestsState, "indexer_read_manifests", indexerViews)
|
manifests := followOwner(check.IndexerModule, manifestsState, "indexer_read_manifests", indexerViews)
|
||||||
packages := followOwner(check.IndexerModule, meshcatalog.StatePackages, "indexer_read_packages", indexerViews)
|
packages := followOwner(check.IndexerModule, meshcatalog.StatePackages, "indexer_read_packages", indexerViews)
|
||||||
predefined := followOwner(check.IndexerModule, check.IndexerPredefinedState, "indexer_read_predefined", indexerViews)
|
predefined := followOwner(check.IndexerModule, check.IndexerPredefinedState, "indexer_read_predefined", indexerViews)
|
||||||
placement := followOwner(buildrun.AssignerModule, meshmoduleassigner.StatePlacement, "assigner_read_placement", assignerViews)
|
assignments := followOwner(buildrun.AssignerModule, meshmoduleassigner.StateAssignments, "assigner_read_assignments", assignerViews)
|
||||||
// The seventh: the indexer's seat definitions, so a pull request whose head's definition does not parse fails.
|
// The seventh: the indexer's seat definitions, so a pull request whose head's definition does not parse fails.
|
||||||
seatDefs := followOwner(check.IndexerModule, seatDefinitionsState, "indexer_read_seat_definitions", indexerViews)
|
seatDefs := followOwner(check.IndexerModule, seatDefinitionsState, "indexer_read_seat_definitions", indexerViews)
|
||||||
if followed != nil {
|
if followed != nil {
|
||||||
@@ -246,7 +246,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
|||||||
verdicts := &manifestVerdicts{}
|
verdicts := &manifestVerdicts{}
|
||||||
compositions := check.EngineCompositions{
|
compositions := check.EngineCompositions{
|
||||||
Asker: engineAsks{call: caller.CallOn},
|
Asker: engineAsks{call: caller.CallOn},
|
||||||
Placed: placedNodes{kv: func(context.Context) (jetstream.KeyValue, error) { return placement, nil }}.Placed,
|
Placed: placedNodes{kv: func(context.Context) (jetstream.KeyValue, error) { return assignments, nil }}.Placed,
|
||||||
Bound: answerBound,
|
Bound: answerBound,
|
||||||
Sandbox: seatSandbox{asker: buildrun.Asker{Publisher: asks, Waiter: waiter, Bound: stepBound}},
|
Sandbox: seatSandbox{asker: buildrun.Asker{Publisher: asks, Waiter: waiter, Bound: stepBound}},
|
||||||
Verdicts: verdicts.all,
|
Verdicts: verdicts.all,
|
||||||
@@ -303,23 +303,23 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
|||||||
// The toolchains first, so a check that starts from the first candidate finds the nodes.
|
// The toolchains first, so a check that starts from the first candidate finds the nodes.
|
||||||
watch := newToolchainWatch(ctx, js, tools, ch.NodesChanged, logger)
|
watch := newToolchainWatch(ctx, js, tools, ch.NodesChanged, logger)
|
||||||
// Only the toolchain reports of nodes the catalogue places the build module on count (issue 1940, ADR 0549 §3): the
|
// Only the toolchain reports of nodes the catalogue places the build module on count (issue 1940, ADR 0549 §3): the
|
||||||
// placement is settled first, so the toolchains settle through the gate already knowing it.
|
// assignments is settled first, so the toolchains settle through the gate already knowing it.
|
||||||
gate := buildrun.NewToolchainGate([]string{buildrun.GoBuildModule, buildrun.NodeBuildModule}, tools.Put, logger.Printf)
|
gate := buildrun.NewToolchainGate([]string{buildrun.GoBuildModule, buildrun.NodeBuildModule}, tools.Put, logger.Printf)
|
||||||
watch.put = gate.Toolchain
|
watch.put = gate.Toolchain
|
||||||
// Until the placement's first view is answered the gate passes every report on, as before the placement is settled.
|
// Until the assignments's first view is answered the gate passes every report on, as before the assignments is settled.
|
||||||
placementErrs := make(chan error, 1)
|
assignmentsErrs := make(chan error, 1)
|
||||||
go func() {
|
go func() {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
case <-placement.Follower().Ready():
|
case <-assignments.Follower().Ready():
|
||||||
}
|
}
|
||||||
pe, err := buildrun.FollowPlacement(ctx, reader, gate, ch.NodesChanged, logger.Printf)
|
pe, err := buildrun.FollowAssignments(ctx, reader, gate, ch.NodesChanged, logger.Printf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
placementErrs <- err
|
assignmentsErrs <- err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
placementErrs <- <-pe
|
assignmentsErrs <- <-pe
|
||||||
}()
|
}()
|
||||||
if err := watch.start(); err != nil {
|
if err := watch.start(); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -481,7 +481,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
|||||||
defer hub.Stop()
|
defer hub.Stop()
|
||||||
// The checks, the tries and the hand asks, once every first view is answered (the lead's ruling for DLV-M2): a check
|
// The checks, the tries and the hand asks, once every first view is answered (the lead's ruling for DLV-M2): a check
|
||||||
// judged without the candidates, the modules, the manifests, the published packages, the predefined keys or the
|
// judged without the candidates, the modules, the manifests, the published packages, the predefined keys or the
|
||||||
// placement would be another check.
|
// assignments would be another check.
|
||||||
go func() {
|
go func() {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
@@ -500,7 +500,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
|||||||
return nil
|
return nil
|
||||||
case err := <-errs:
|
case err := <-errs:
|
||||||
return err
|
return err
|
||||||
case err := <-placementErrs:
|
case err := <-assignmentsErrs:
|
||||||
return err
|
return err
|
||||||
case err := <-watch.errs:
|
case err := <-watch.errs:
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import (
|
|||||||
|
|
||||||
// 1945 (a part exits at start for a bucket it works without): a bus that refuses every toolchains bucket leaves the
|
// 1945 (a part exits at start for a bucket it works without): a bus that refuses every toolchains bucket leaves the
|
||||||
// watch unstarted and the part running, said; a refused bucket it cannot work without is an error naming the bucket and
|
// watch unstarted and the part running, said; a refused bucket it cannot work without is an error naming the bucket and
|
||||||
// its grant. The gate over the seat's placement, and its refusal, are mesh-sdk's (buildrun.ToolchainGate,
|
// its grant. The gate over the seat's assignments, and its refusal, are mesh-sdk's (buildrun.ToolchainGate,
|
||||||
// issue 774).
|
// issue 774).
|
||||||
func TestARefusedBucketIsSaidAndThePartGoesOn(t *testing.T) {
|
func TestARefusedBucketIsSaidAndThePartGoesOn(t *testing.T) {
|
||||||
set := &buildrun.ToolchainSet{}
|
set := &buildrun.ToolchainSet{}
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ go 1.26.0
|
|||||||
require (
|
require (
|
||||||
git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep 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/seats/meshwatchdogs v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient v0.0.0-00010101000000-000000000000
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ package check
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants"
|
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||||
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants"
|
||||||
)
|
)
|
||||||
|
|
||||||
// withComposition is v with the check's composition taken in: a failed one fails the check, naming its machine.
|
// withComposition is v with the check's composition taken in: a failed one fails the check, naming its machine.
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants"
|
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||||
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants"
|
||||||
buildsrec "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
buildsrec "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ import (
|
|||||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||||
nec "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
nec "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||||
ne "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
ne "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||||
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants"
|
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||||
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants"
|
||||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||||
"git.novox.be/novox/mesh-delivery/sdk/composer"
|
"git.novox.be/novox/mesh-delivery/sdk/composer"
|
||||||
)
|
)
|
||||||
@@ -46,7 +46,7 @@ type Sandbox interface {
|
|||||||
// EngineCompositions is the checker's composition of a check (Examiner.Compositions).
|
// EngineCompositions is the checker's composition of a check (Examiner.Compositions).
|
||||||
type EngineCompositions struct {
|
type EngineCompositions struct {
|
||||||
Asker EngineAsker
|
Asker EngineAsker
|
||||||
// Placed names every node in placement now (the seat mesh-placement's state), or why it cannot be read: then no node
|
// Placed names every node in assignments now (the seat mesh-module-assigner's state), or why it cannot be read: then no node
|
||||||
// can be asked, and the composition is not made (an error), never passed with nobody asked.
|
// can be asked, and the composition is not made (an error), never passed with nobody asked.
|
||||||
Placed func() ([]string, error)
|
Placed func() ([]string, error)
|
||||||
// Bound is the answer bound (the setting compose-answer-bound); zero is nec.AnswerBound.
|
// Bound is the answer bound (the setting compose-answer-bound); zero is nec.AnswerBound.
|
||||||
@@ -80,7 +80,7 @@ func (e EngineCompositions) Compose(ctx context.Context, c *Case) (meshwants.Com
|
|||||||
var nodes []string
|
var nodes []string
|
||||||
if e.Placed != nil {
|
if e.Placed != nil {
|
||||||
if nodes, err = e.Placed(); err != nil {
|
if nodes, err = e.Placed(); err != nil {
|
||||||
return meshwants.CompositionsByGroupHeadsValue{}, fmt.Errorf("the placement cannot be read, so no node can be asked: %w", err)
|
return meshwants.CompositionsByGroupHeadsValue{}, fmt.Errorf("the assignments cannot be read, so no node can be asked: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bound := e.Bound
|
bound := e.Bound
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ import (
|
|||||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||||
nec "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
nec "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||||
ne "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
ne "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||||
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants"
|
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||||
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants"
|
||||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||||
"git.novox.be/novox/mesh-delivery/sdk/composer"
|
"git.novox.be/novox/mesh-delivery/sdk/composer"
|
||||||
)
|
)
|
||||||
@@ -204,11 +204,11 @@ func TestTheCompositionIsMadeFromTheEnginesAnswers(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Review finding 4: a placement that cannot be read is no mesh with no node: the composition is not made, said.
|
// Review finding 4: assignments that cannot be read is no mesh with no node: the composition is not made, said.
|
||||||
t.Run("an unreadable placement makes no composition", func(t *testing.T) {
|
t.Run("an unreadable assignments makes no composition", func(t *testing.T) {
|
||||||
ec := EngineCompositions{Asker: &fakeEngines{}, Placed: func() ([]string, error) { return nil, errors.New("no such bucket") }}
|
ec := EngineCompositions{Asker: &fakeEngines{}, Placed: func() ([]string, error) { return nil, errors.New("no such bucket") }}
|
||||||
if _, err := ec.Compose(context.Background(), cs); err == nil || !strings.Contains(err.Error(), "no such bucket") {
|
if _, err := ec.Compose(context.Background(), cs); err == nil || !strings.Contains(err.Error(), "no such bucket") {
|
||||||
t.Errorf("an unreadable placement: %v", err)
|
t.Errorf("an unreadable assignments: %v", err)
|
||||||
}
|
}
|
||||||
b := newBench(t)
|
b := newBench(t)
|
||||||
b.ex.Compositions = ec
|
b.ex.Compositions = ec
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants"
|
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||||
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants"
|
||||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -20,12 +20,12 @@ import (
|
|||||||
|
|
||||||
gobuild "git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
gobuild "git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||||
"git.novox.be/novox/mesh-contracts/go/seats/meshconfig"
|
"git.novox.be/novox/mesh-contracts/go/seats/meshconfig"
|
||||||
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants"
|
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||||
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants"
|
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||||
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
||||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||||
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants"
|
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||||
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-checker/checkermsg"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-checker/checkermsg"
|
||||||
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ func TestManifestDeclarations(t *testing.T) { // 1678/AC1
|
|||||||
t.Errorf("reads = %v, want %v", m.Reads, want)
|
t.Errorf("reads = %v, want %v", m.Reads, want)
|
||||||
}
|
}
|
||||||
// DLV-M2 (ADR 0615 §1, ADR 0596): the indexer's candidates, modules, manifests, packages and predefined keys and the
|
// DLV-M2 (ADR 0615 §1, ADR 0596): the indexer's candidates, modules, manifests, packages and predefined keys and the
|
||||||
// assigner's placement are asked by their read tools and followed by their views families, never read.
|
// assigner's assignments are asked by their read tools and followed by their views families, never read.
|
||||||
for _, r := range m.Reads {
|
for _, r := range m.Reads {
|
||||||
if strings.HasPrefix(r, "mesh-delivery-indexer.") || strings.HasPrefix(r, "mesh-delivery-assigner.") {
|
if strings.HasPrefix(r, "mesh-delivery-indexer.") || strings.HasPrefix(r, "mesh-delivery-assigner.") {
|
||||||
t.Errorf("reads %s: the indexer's and the assigner's state is read by their tools and views families alone", r)
|
t.Errorf("reads %s: the indexer's and the assigner's state is read by their tools and views families alone", r)
|
||||||
@@ -126,7 +126,7 @@ func TestManifestDeclarations(t *testing.T) { // 1678/AC1
|
|||||||
// The forge's reads for a mesh-sdk pin (ADR 0484 clause 9), and of the node seat node-engine its one verb
|
// The forge's reads for a mesh-sdk pin (ADR 0484 clause 9), and of the node seat node-engine its one verb
|
||||||
// compose-candidate, asked of every placed node (ADR 0593 §7), never the seat's other verbs.
|
// compose-candidate, asked of every placed node (ADR 0593 §7), never the seat's other verbs.
|
||||||
if want := []string{"gitea.gitea_commits", "gitea.gitea_compare", "gitea.gitea_get_file", "gitea.gitea_list_branches",
|
if want := []string{"gitea.gitea_commits", "gitea.gitea_compare", "gitea.gitea_get_file", "gitea.gitea_list_branches",
|
||||||
"mesh-delivery-assigner.assigner_read_placement", "mesh-delivery-indexer.indexer_read_candidates", "mesh-delivery-indexer.indexer_read_manifests",
|
"mesh-delivery-assigner.assigner_read_assignments", "mesh-delivery-indexer.indexer_read_candidates", "mesh-delivery-indexer.indexer_read_manifests",
|
||||||
"mesh-delivery-indexer.indexer_read_modules", "mesh-delivery-indexer.indexer_read_packages", "mesh-delivery-indexer.indexer_read_predefined",
|
"mesh-delivery-indexer.indexer_read_modules", "mesh-delivery-indexer.indexer_read_packages", "mesh-delivery-indexer.indexer_read_predefined",
|
||||||
"mesh-delivery-indexer.indexer_read_seat_definitions",
|
"mesh-delivery-indexer.indexer_read_seat_definitions",
|
||||||
"seat:mesh-catalog.seat-definitions", "seat:mesh-config.renames", "seat:mesh-hand-acts.hand-act", "seat:node-engine.compose-candidate"}; !reflect.DeepEqual(sorted(m.Invokes), want) {
|
"seat:mesh-catalog.seat-definitions", "seat:mesh-config.renames", "seat:mesh-hand-acts.hand-act", "seat:node-engine.compose-candidate"}; !reflect.DeepEqual(sorted(m.Invokes), want) {
|
||||||
@@ -138,7 +138,7 @@ func TestManifestDeclarations(t *testing.T) { // 1678/AC1
|
|||||||
// The checker is woken by a build module's report of a toolchain, as the indexer is: the bucket may not exist when it starts.
|
// The checker is woken by a build module's report of a toolchain, as the indexer is: the bucket may not exist when it starts.
|
||||||
// And a group's check by the package registry seats' published of its tagged versions (design 51 *Tagged versions*).
|
// And a group's check by the package registry seats' published of its tagged versions (design 51 *Tagged versions*).
|
||||||
if want := []string{"go-package-registry.published", "mesh-build-go.toolchain.reported", "mesh-build-node.toolchain.reported",
|
if want := []string{"go-package-registry.published", "mesh-build-go.toolchain.reported", "mesh-build-node.toolchain.reported",
|
||||||
"mesh-catalog.seat-definition-changed", "mesh-catalog.seat-definition-removed", "mesh-delivery-assigner.views.placement", "mesh-delivery-indexer.views.candidates", "mesh-delivery-indexer.views.manifests", "mesh-delivery-indexer.views.modules",
|
"mesh-catalog.seat-definition-changed", "mesh-catalog.seat-definition-removed", "mesh-delivery-assigner.views.assignments", "mesh-delivery-indexer.views.candidates", "mesh-delivery-indexer.views.manifests", "mesh-delivery-indexer.views.modules",
|
||||||
"mesh-delivery-indexer.views.packages", "mesh-delivery-indexer.views.predefined", "mesh-delivery-indexer.views.seat-definitions", "npm-package-registry.published"}; !reflect.DeepEqual(sorted(m.Consumes), want) {
|
"mesh-delivery-indexer.views.packages", "mesh-delivery-indexer.views.predefined", "mesh-delivery-indexer.views.seat-definitions", "npm-package-registry.published"}; !reflect.DeepEqual(sorted(m.Consumes), want) {
|
||||||
t.Errorf("consumes = %v, want %v", m.Consumes, want)
|
t.Errorf("consumes = %v, want %v", m.Consumes, want)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
"mesh-catalog.seat-definition-removed",
|
"mesh-catalog.seat-definition-removed",
|
||||||
"mesh-build-go.toolchain.reported",
|
"mesh-build-go.toolchain.reported",
|
||||||
"mesh-build-node.toolchain.reported",
|
"mesh-build-node.toolchain.reported",
|
||||||
"mesh-delivery-assigner.views.placement",
|
"mesh-delivery-assigner.views.assignments",
|
||||||
"mesh-delivery-indexer.views.candidates",
|
"mesh-delivery-indexer.views.candidates",
|
||||||
"mesh-delivery-indexer.views.manifests",
|
"mesh-delivery-indexer.views.manifests",
|
||||||
"mesh-delivery-indexer.views.modules",
|
"mesh-delivery-indexer.views.modules",
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
"gitea.gitea_compare",
|
"gitea.gitea_compare",
|
||||||
"gitea.gitea_get_file",
|
"gitea.gitea_get_file",
|
||||||
"gitea.gitea_list_branches",
|
"gitea.gitea_list_branches",
|
||||||
"mesh-delivery-assigner.assigner_read_placement",
|
"mesh-delivery-assigner.assigner_read_assignments",
|
||||||
"mesh-delivery-indexer.indexer_read_candidates",
|
"mesh-delivery-indexer.indexer_read_candidates",
|
||||||
"mesh-delivery-indexer.indexer_read_manifests",
|
"mesh-delivery-indexer.indexer_read_manifests",
|
||||||
"mesh-delivery-indexer.indexer_read_modules",
|
"mesh-delivery-indexer.indexer_read_modules",
|
||||||
|
|||||||
@@ -143,11 +143,11 @@ type Forge struct {
|
|||||||
Act string `json:"act,omitempty"`
|
Act string `json:"act,omitempty"`
|
||||||
Words string `json:"words,omitempty"`
|
Words string `json:"words,omitempty"`
|
||||||
Open []OpenPull `json:"open,omitempty"`
|
Open []OpenPull `json:"open,omitempty"`
|
||||||
// RetiredPlacements is the placement files a bridge before ADR 0567 carried (ADR 0549 §2, ADR 0559 §2): placement is
|
// RetiredAssignments is the assignments files a bridge before ADR 0567 carried (ADR 0549 §2, ADR 0559 §2): assignments is
|
||||||
// the seat mesh-placement's state now and no repository holds it (ADR 0567 §11), so a message carrying them is
|
// the seat mesh-module-assigner's state now and no repository holds it (ADR 0567 §11), so a message carrying them is
|
||||||
// refused, loudly, rather than read with them dropped.
|
// refused, loudly, rather than read with them dropped.
|
||||||
RetiredPlacements json.RawMessage `json:"placements,omitempty"`
|
RetiredAssignments json.RawMessage `json:"assignments,omitempty"`
|
||||||
RetiredRead bool `json:"placements-read,omitempty"`
|
RetiredRead bool `json:"assignments-read,omitempty"`
|
||||||
// Predefined is the catalogue's predefined.json, exact, at the merge commit of a merged message (novox/hq ADR 0565
|
// Predefined is the catalogue's predefined.json, exact, at the merge commit of a merged message (novox/hq ADR 0565
|
||||||
// §2): what the indexer keeps as its state `predefined`. PredefinedRead says the bridge read the merge and it holds
|
// §2): what the indexer keeps as its state `predefined`. PredefinedRead says the bridge read the merge and it holds
|
||||||
// no predefined.json. Carried on a merged message only; an older bridge sends neither, and the indexer keeps what it
|
// no predefined.json. Carried on a merged message only; an older bridge sends neither, and the indexer keeps what it
|
||||||
@@ -226,9 +226,9 @@ func (f Forge) check() error {
|
|||||||
if len(f.Open) > 0 {
|
if len(f.Open) > 0 {
|
||||||
return fmt.Errorf("delivery: only an open message names open pull requests")
|
return fmt.Errorf("delivery: only an open message names open pull requests")
|
||||||
}
|
}
|
||||||
if len(f.RetiredPlacements) > 0 || f.RetiredRead {
|
if len(f.RetiredAssignments) > 0 || f.RetiredRead {
|
||||||
return fmt.Errorf("delivery: a forge message carries placement files, which no repository holds since ADR 0567: " +
|
return fmt.Errorf("delivery: a forge message carries assignments files, which no repository holds since ADR 0567: " +
|
||||||
"placement is the seat mesh-placement's state")
|
"assignments is the seat mesh-module-assigner's state")
|
||||||
}
|
}
|
||||||
if err := f.checkPredefined(); err != nil {
|
if err := f.checkPredefined(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -148,8 +148,8 @@ func TestForge(t *testing.T) {
|
|||||||
{"empty lock hash", `"a"`, in(func(f *Forge) { f.Locks["a"]["go.sum"] = "" })},
|
{"empty lock hash", `"a"`, in(func(f *Forge) { f.Locks["a"]["go.sum"] = "" })},
|
||||||
{"named paths outside trees", `"other"`, in(func(f *Forge) { f.Paths = map[string]map[string]string{"other": {"src": "o"}} })},
|
{"named paths outside trees", `"other"`, in(func(f *Forge) { f.Paths = map[string]map[string]string{"other": {"src": "o"}} })},
|
||||||
{"a named path with no object", `"src"`, in(func(f *Forge) { f.Paths = map[string]map[string]string{"a": {"src": ""}} })},
|
{"a named path with no object", `"src"`, in(func(f *Forge) { f.Paths = map[string]map[string]string{"a": {"src": ""}} })},
|
||||||
{"placement files", "mesh-placement", in(func(f *Forge) { f.RetiredPlacements = []byte(`{"anchor":"{}"}`) })},
|
{"assignments files", "mesh-module-assigner", in(func(f *Forge) { f.RetiredAssignments = []byte(`{"anchor":"{}"}`) })},
|
||||||
{"placements read", "mesh-placement", in(func(f *Forge) { f.RetiredRead = true })},
|
{"assignments read", "mesh-module-assigner", in(func(f *Forge) { f.RetiredRead = true })},
|
||||||
{"predefined not an object", "one JSON object", in(func(f *Forge) { f.Predefined = []byte(`["domain"]`) })},
|
{"predefined not an object", "one JSON object", in(func(f *Forge) { f.Predefined = []byte(`["domain"]`) })},
|
||||||
{"predefined both", "says the merge holds none", in(func(f *Forge) { f.Predefined = []byte(`{}`); f.PredefinedRead = true })},
|
{"predefined both", "says the merge holds none", in(func(f *Forge) { f.Predefined = []byte(`{}`); f.PredefinedRead = true })},
|
||||||
{"pull with the predefined list", "only a merged message", Forge{Kind: ForgePull, Repository: "novox/mesh-sdk", Number: 1, Head: "h", Action: PullOpened, PredefinedRead: true}},
|
{"pull with the predefined list", "only a merged message", Forge{Kind: ForgePull, Repository: "novox/mesh-sdk", Number: 1, Head: "h", Action: PullOpened, PredefinedRead: true}},
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ func TestMergedCarriesManifestsAndLocks(t *testing.T) { // 1448/AC1
|
|||||||
if len(f.Packages) != 0 || strings.Contains(string(msg.Data), `"packages"`) {
|
if len(f.Packages) != 0 || strings.Contains(string(msg.Data), `"packages"`) {
|
||||||
t.Errorf("the merged message carries packages: %v", f.Packages)
|
t.Errorf("the merged message carries packages: %v", f.Packages)
|
||||||
}
|
}
|
||||||
// ADR 0567: placement is the seat mesh-placement's: a merge of the catalogue carries no placement file and asks the
|
// ADR 0567: assignments is the seat mesh-module-assigner's: a merge of the catalogue carries no assignments file and asks the
|
||||||
// forge for none.
|
// forge for none.
|
||||||
forge.calls = nil
|
forge.calls = nil
|
||||||
msg, err = Convert(mergedSubject, mergedBody(map[string]any{"repo": "mesh-catalog"}), treesOf(&calls, "r0", map[string]string{"modules/a": "t1"}, nil), forge.get)
|
msg, err = Convert(mergedSubject, mergedBody(map[string]any{"repo": "mesh-catalog"}), treesOf(&calls, "r0", map[string]string{"modules/a": "t1"}, nil), forge.get)
|
||||||
@@ -94,11 +94,11 @@ func TestMergedCarriesManifestsAndLocks(t *testing.T) { // 1448/AC1
|
|||||||
if f, err = forgemsg.DecodeForge(msg.Data); err != nil {
|
if f, err = forgemsg.DecodeForge(msg.Data); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if len(f.RetiredPlacements) > 0 || f.RetiredRead || strings.Contains(string(msg.Data), `"placements`) {
|
if len(f.RetiredAssignments) > 0 || f.RetiredRead || strings.Contains(string(msg.Data), `"assignments`) {
|
||||||
t.Errorf("a merge of the catalogue carries placement: %s", msg.Data)
|
t.Errorf("a merge of the catalogue carries assignments: %s", msg.Data)
|
||||||
}
|
}
|
||||||
for _, c := range forge.calls {
|
for _, c := range forge.calls {
|
||||||
if strings.HasPrefix(c.path, "placement") {
|
if strings.HasPrefix(c.path, "assignments") {
|
||||||
t.Errorf("the forge was asked for %s", c.path)
|
t.Errorf("the forge was asked for %s", c.path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ func holdsTool(read stdio.KeptState, ask asker, now func() time.Time, acts meshh
|
|||||||
") which not every counted node-engine states, present or away (ADR 0624 §1). Per module: what it needs, which nodes " +
|
") which not every counted node-engine states, present or away (ADR 0624 §1). Per module: what it needs, which nodes " +
|
||||||
"lack what (or why no node is counted yet), the manifest the modules state keeps and the one held, the merge that " +
|
"lack what (or why no node is counted yet), the manifest the modules state keeps and the one held, the merge that " +
|
||||||
"brought it and since when. The hold moves by itself once every counted node-engine states it (on manifest-reads-changed, " +
|
"brought it and since when. The hold moves by itself once every counted node-engine states it (on manifest-reads-changed, " +
|
||||||
"the placement's change or a merge). With module and ask_again, the engines of the nodes that lack it are asked again " +
|
"the assignments's change or a merge). With module and ask_again, the engines of the nodes that lack it are asked again " +
|
||||||
"and the hold is weighed now; with module, release and why, it moves now by your word whatever the engines state (a hand " +
|
"and the hold is weighed now; with module, release and why, it moves now by your word whatever the engines state (a hand " +
|
||||||
"act, recorded through mesh-hand-acts, which names the nodes it strands: each node whose engine lacks the reading, an " +
|
"act, recorded through mesh-hand-acts, which names the nodes it strands: each node whose engine lacks the reading, an " +
|
||||||
"away node from its return, reads the new entry without it and an engine before form:seat-reach finds no store). A hold " +
|
"away node from its return, reads the new entry without it and an engine before form:seat-reach finds no store). A hold " +
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ func viewTools(getenv func(string) string, state func(string) views.Source, now
|
|||||||
for _, t := range index.ReadTools {
|
for _, t := range index.ReadTools {
|
||||||
out = append(out, views.ReadTool(index.Module, t.Tool, t.State, t.Words, state(t.State), marks, now))
|
out = append(out, views.ReadTool(index.Module, t.Tool, t.State, t.Words, state(t.State), marks, now))
|
||||||
}
|
}
|
||||||
// Its own first views, of the assigner's placement (DLV-F3r): read, and asked again by hand.
|
// Its own first views, of the assigner's assignments (DLV-F3r): read, and asked again by hand.
|
||||||
return append(out, views.StatusTools(index.Module, "indexer_first_views", "indexer_first_view_again", stdio.State(views.StatusState), meshhandactsclient.Recorder{Ask: stdio.Ask}, now)...)
|
return append(out, views.StatusTools(index.Module, "indexer_first_views", "indexer_first_view_again", stdio.State(views.StatusState), meshhandactsclient.Recorder{Ask: stdio.Ask}, now)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ import (
|
|||||||
"github.com/nats-io/nats.go"
|
"github.com/nats-io/nats.go"
|
||||||
"github.com/nats-io/nats.go/jetstream"
|
"github.com/nats-io/nats.go/jetstream"
|
||||||
|
|
||||||
"git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
"git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
|
||||||
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||||
@@ -559,7 +559,7 @@ func startIndexerRun(t *testing.T, put func(context.Context, buildLog), before .
|
|||||||
b(ctx, js)
|
b(ctx, js)
|
||||||
}
|
}
|
||||||
|
|
||||||
serveAssignerPlacement(t, ctx, js, ctl)
|
serveAssignerAssignments(t, ctx, js, ctl)
|
||||||
|
|
||||||
broker := filepath.Join(t.TempDir(), "broker")
|
broker := filepath.Join(t.TempDir(), "broker")
|
||||||
cred, _ := json.Marshal(process.Account{URL: srv.ClientURL(), Node: "n1", Module: "mesh-delivery-indexer", User: "n1.mesh-delivery-indexer", Password: "pw"})
|
cred, _ := json.Marshal(process.Account{URL: srv.ClientURL(), Node: "n1", Module: "mesh-delivery-indexer", User: "n1.mesh-delivery-indexer", Password: "pw"})
|
||||||
@@ -978,33 +978,33 @@ func TestIndexerJudgesAnUnreadableVerdictAgainAtStart(t *testing.T) { // 1923/AC
|
|||||||
// TestIndexerCountsOnlyThePlacedBuildNodesToolchains (issue 1940, issue 774): the toolchains bucket holds the report of
|
// TestIndexerCountsOnlyThePlacedBuildNodesToolchains (issue 1940, issue 774): the toolchains bucket holds the report of
|
||||||
// n2, where the catalogue no longer places mesh-build-go, at another version than n1's, as ace's v26.12.0 stayed beside
|
// n2, where the catalogue no longer places mesh-build-go, at another version than n1's, as ace's v26.12.0 stayed beside
|
||||||
// the v26.12.1 of the nodes mesh-build-node runs on. The indexer gives the merged module a fingerprint from n1's
|
// the v26.12.1 of the nodes mesh-build-node runs on. The indexer gives the merged module a fingerprint from n1's
|
||||||
// toolchain alone; when the placement puts mesh-build-go on n2 again, its report written since counts and the builders
|
// toolchain alone; when the assignments puts mesh-build-go on n2 again, its report written since counts and the builders
|
||||||
// disagree (the guard stays); when it is taken off again, the fingerprint comes back. No restart, no timer: the
|
// disagree (the guard stays); when it is taken off again, the fingerprint comes back. No restart, no timer: the
|
||||||
// placement's watch (ADR 0549 §3), not the controller's assignments.
|
// assignments's watch (ADR 0549 §3), not the controller's assignments.
|
||||||
func TestIndexerCountsOnlyThePlacedBuildNodesToolchains(t *testing.T) {
|
func TestIndexerCountsOnlyThePlacedBuildNodesToolchains(t *testing.T) {
|
||||||
manifest, err := os.ReadFile("../../internal/index/testdata/catalogue-a67f0196-avahi.module.json")
|
manifest, err := os.ReadFile("../../internal/index/testdata/catalogue-a67f0196-avahi.module.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
const goSum = "3b5028f0c1e9bde04dd766abd63eb5931bc516bae695cf46f00abcd8510b0381"
|
const goSum = "3b5028f0c1e9bde04dd766abd63eb5931bc516bae695cf46f00abcd8510b0381"
|
||||||
var placement, toolchains jetstream.KeyValue
|
var assignments, toolchains jetstream.KeyValue
|
||||||
place := func(ctx context.Context, t *testing.T, node string, modules ...string) {
|
place := func(ctx context.Context, t *testing.T, node string, modules ...string) {
|
||||||
p := meshmoduleassigner.Placement{Node: node}
|
p := meshmoduleassigner.Assignments{Node: node}
|
||||||
for _, m := range modules {
|
for _, m := range modules {
|
||||||
p.Modules = append(p.Modules, meshmoduleassigner.PlacedModule{Module: m})
|
p.Modules = append(p.Modules, meshmoduleassigner.PlacedModule{Module: m})
|
||||||
}
|
}
|
||||||
// Its node key, the commit that names the modules placed (research 072 row 37).
|
// Its node key, the commit that names the modules placed (research 072 row 37).
|
||||||
values, err := meshmoduleassignerclient.PlacementValues(p)
|
values, err := meshmoduleassignerclient.AssignmentsValues(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
key := meshmoduleassigner.PlacementByNodeNodeKey(node)
|
key := meshmoduleassigner.AssignmentsByNodeNodeKey(node)
|
||||||
if _, err := placement.Put(ctx, key, values[key]); err != nil {
|
if _, err := assignments.Put(ctx, key, values[key]); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// DLV-F5r (ADR 0621 §3): the assigner's runtime starts together with the indexer and answers its first view only after
|
// DLV-F5r (ADR 0621 §3): the assigner's runtime starts together with the indexer and answers its first view only after
|
||||||
// the indexer asked; within the bound the indexer follows the placement it answers (only n1's toolchain counts).
|
// the indexer asked; within the bound the indexer follows the assignments it answers (only n1's toolchain counts).
|
||||||
up := make(chan struct{})
|
up := make(chan struct{})
|
||||||
assignerLate = up
|
assignerLate = up
|
||||||
t.Cleanup(func() { assignerLate = nil })
|
t.Cleanup(func() { assignerLate = nil })
|
||||||
@@ -1024,8 +1024,8 @@ func TestIndexerCountsOnlyThePlacedBuildNodesToolchains(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// The seat mesh-placement's placement, as the assigner writes it (ADR 0567): n1 places mesh-build-go, n2 nothing.
|
// The seat mesh-module-assigner's assignments, as the assigner writes it (ADR 0567): n1 places mesh-build-go, n2 nothing.
|
||||||
if placement, err = js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: index.PlacementBucket, History: index.PlacementHistory}); err != nil {
|
if assignments, err = js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: index.AssignmentsBucket, History: index.AssignmentsHistory}); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
place(ctx, t, "n1", "mesh-build-go")
|
place(ctx, t, "n1", "mesh-build-go")
|
||||||
@@ -1072,13 +1072,13 @@ func TestIndexerCountsOnlyThePlacedBuildNodesToolchains(t *testing.T) {
|
|||||||
if _, err := toolchains.Put(ctx, key, value); err != nil {
|
if _, err := toolchains.Put(ctx, key, value); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
// n2's report, written since its placement, counts: the builders disagree. A fingerprint already computed is kept
|
// n2's report, written since its assignments, counts: the builders disagree. A fingerprint already computed is kept
|
||||||
// (issue 1940), and only a new one needs the agreement: a merge moving avahi gets none until they agree.
|
// (issue 1940), and only a new one needs the agreement: a merge moving avahi gets none until they agree.
|
||||||
time.Sleep(500 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
if e, err := modules.Get(ctx, "avahi"); err != nil {
|
if e, err := modules.Get(ctx, "avahi"); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
} else if m, err := meshcatalogclient.DecodeModule(e.Value()); err != nil || m.Fp2 != held.Fp2 {
|
} else if m, err := meshcatalogclient.DecodeModule(e.Value()); err != nil || m.Fp2 != held.Fp2 {
|
||||||
t.Fatalf("after n2's placement avahi has fp2 %q, none2 %q: want its fingerprint %q kept", m.Fp2, m.None2, held.Fp2)
|
t.Fatalf("after n2's assignments avahi has fp2 %q, none2 %q: want its fingerprint %q kept", m.Fp2, m.None2, held.Fp2)
|
||||||
}
|
}
|
||||||
moved := strings.Replace(string(data), `"newtree"`, `"movedtree"`, 1)
|
moved := strings.Replace(string(data), `"newtree"`, `"movedtree"`, 1)
|
||||||
if moved == string(data) {
|
if moved == string(data) {
|
||||||
@@ -1091,11 +1091,11 @@ func TestIndexerCountsOnlyThePlacedBuildNodesToolchains(t *testing.T) {
|
|||||||
return m.Fp2 == "" && strings.HasPrefix(m.None2, "builders disagree: go1.27.2 recipe 1 on n2")
|
return m.Fp2 == "" && strings.HasPrefix(m.None2, "builders disagree: go1.27.2 recipe 1 on n2")
|
||||||
}
|
}
|
||||||
if m := read(disagree); !disagree(m) {
|
if m := read(disagree); !disagree(m) {
|
||||||
t.Fatalf("within 20 s of a merge after n2's placement avahi has fp2 %q, none2 %q: want the builders to disagree", m.Fp2, m.None2)
|
t.Fatalf("within 20 s of a merge after n2's assignments avahi has fp2 %q, none2 %q: want the builders to disagree", m.Fp2, m.None2)
|
||||||
}
|
}
|
||||||
place(ctx, t, "n2")
|
place(ctx, t, "n2")
|
||||||
if m := read(agreed); !agreed(m) {
|
if m := read(agreed); !agreed(m) {
|
||||||
t.Fatalf("within 20 s of n2's placement taken away avahi has fp2 %q, none2 %q", m.Fp2, m.None2)
|
t.Fatalf("within 20 s of n2's assignments taken away avahi has fp2 %q, none2 %q", m.Fp2, m.None2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1103,14 +1103,14 @@ func TestIndexerCountsOnlyThePlacedBuildNodesToolchains(t *testing.T) {
|
|||||||
// assigner's runtime started together with the indexer's, answering after the indexer asked (DLV-F5r).
|
// assigner's runtime started together with the indexer's, answering after the indexer asked (DLV-F5r).
|
||||||
var assignerLate chan struct{}
|
var assignerLate chan struct{}
|
||||||
|
|
||||||
// serveAssignerPlacement is the assigner as the owner of its placement (ADR 0615, ADR 0621), with the real owner's side:
|
// serveAssignerAssignments is the assigner as the owner of its assignments (ADR 0615, ADR 0621), with the real owner's side:
|
||||||
// its first-view tool placement answered from the bucket at its mark (the bucket made empty where a test made none),
|
// its first-view tool assignments answered from the bucket at its mark (the bucket made empty where a test made none),
|
||||||
// and its views family said by the views emitter onto ASSIGNER_VIEWS, which the indexer's durable consumer reads.
|
// and its views family said by the views emitter onto ASSIGNER_VIEWS, which the indexer's durable consumer reads.
|
||||||
func serveAssignerPlacement(t *testing.T, ctx context.Context, js jetstream.JetStream, nc *nats.Conn) {
|
func serveAssignerAssignments(t *testing.T, ctx context.Context, js jetstream.JetStream, nc *nats.Conn) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
placement, err := js.KeyValue(ctx, index.PlacementBucket)
|
assignments, err := js.KeyValue(ctx, index.AssignmentsBucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if placement, err = js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: index.PlacementBucket, History: index.PlacementHistory}); err != nil {
|
if assignments, err = js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: index.AssignmentsBucket, History: index.AssignmentsHistory}); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1124,11 +1124,11 @@ func serveAssignerPlacement(t *testing.T, ctx context.Context, js jetstream.JetS
|
|||||||
}
|
}
|
||||||
em := views.Emitter{Owner: index.AssignerModule, Subject: process.Account{Node: "n1", Module: index.AssignerModule}.EventSubject,
|
em := views.Emitter{Owner: index.AssignerModule, Subject: process.Account{Node: "n1", Module: index.AssignerModule}.EventSubject,
|
||||||
Publish: views.JetStreamPublish(js), Marks: marks}
|
Publish: views.JetStreamPublish(js), Marks: marks}
|
||||||
if err := em.Start(ctx, index.PlacementState, placement, nil); err != nil {
|
if err := em.Start(ctx, index.AssignmentsState, assignments, nil); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
late := assignerLate
|
late := assignerLate
|
||||||
sub, err := nc.Subscribe("mesh.mod.mesh-delivery-assigner.call.placement.>", func(m *nats.Msg) {
|
sub, err := nc.Subscribe("mesh.mod.mesh-delivery-assigner.call.assignments.>", func(m *nats.Msg) {
|
||||||
if late != nil { // its runtime comes up after the indexer started (DLV-F5r)
|
if late != nil { // its runtime comes up after the indexer started (DLV-F5r)
|
||||||
select {
|
select {
|
||||||
case <-late:
|
case <-late:
|
||||||
@@ -1136,7 +1136,7 @@ func serveAssignerPlacement(t *testing.T, ctx context.Context, js jetstream.JetS
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
a, err := views.FirstView(index.AssignerModule, index.PlacementState, jsSource{ctx, placement}, jsSource{ctx, marks}, "", time.Now())
|
a, err := views.FirstView(index.AssignerModule, index.AssignmentsState, jsSource{ctx, assignments}, jsSource{ctx, marks}, "", time.Now())
|
||||||
out := map[string]any{"result": a, "node": "n0"}
|
out := map[string]any{"result": a, "node": "n0"}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
out = map[string]any{"error": err.Error()}
|
out = map[string]any{"error": err.Error()}
|
||||||
|
|||||||
@@ -472,10 +472,10 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Only the toolchain reports of nodes the catalogue places the build module on count (issue 1940, ADR 0549 §3): the
|
// Only the toolchain reports of nodes the catalogue places the build module on count (issue 1940, ADR 0549 §3): the
|
||||||
// placement is settled first, so the toolchains settle through the gate already knowing it.
|
// assignments is settled first, so the toolchains settle through the gate already knowing it.
|
||||||
gate := buildrun.NewToolchainGate(sortedModules(), ix.PutToolchain, logger.Printf)
|
gate := buildrun.NewToolchainGate(sortedModules(), ix.PutToolchain, logger.Printf)
|
||||||
// What each counted node's engine reads (ADR 0624 §1), for the mesh-wide hold of a manifest every node reads (ADR 0632
|
// What each counted node's engine reads (ADR 0624 §1), for the mesh-wide hold of a manifest every node reads (ADR 0632
|
||||||
// §2): asked through the node-engine seat's verb as the indexer's own account, for every node the placement names,
|
// §2): asked through the node-engine seat's verb as the indexer's own account, for every node the assignments names,
|
||||||
// kept in the state engine-reads once it is bound; each change weighs the holds again.
|
// kept in the state engine-reads once it is bound; each change weighs the holds again.
|
||||||
reads := newEngineReads(calls.CallOn, gate, func() { ix.KickHolds(ctx) }, logger.Printf)
|
reads := newEngineReads(calls.CallOn, gate, func() { ix.KickHolds(ctx) }, logger.Printf)
|
||||||
gate.OnPlaced = reads.placed
|
gate.OnPlaced = reads.placed
|
||||||
@@ -506,14 +506,14 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
|||||||
process.AwaitBucket(ctx, js, conn, index.EngineReadsBucket, "what each node's engine reads is kept in memory only", logger.Printf).Ready(func(kv jetstream.KeyValue) {
|
process.AwaitBucket(ctx, js, conn, index.EngineReadsBucket, "what each node's engine reads is kept in memory only", logger.Printf).Ready(func(kv jetstream.KeyValue) {
|
||||||
reads.bind(ctx, kv, errs, logger.Printf)
|
reads.bind(ctx, kv, errs, logger.Printf)
|
||||||
})
|
})
|
||||||
// The placement is the assigner's: read through its first view and its views family, never its bucket (DLV-F1r).
|
// The assignments is the assigner's: read through its first view and its views family, never its bucket (DLV-F1r).
|
||||||
placementErrs := make(chan error)
|
assignmentsErrs := make(chan error)
|
||||||
// The indexer's one pull of its durable on the shared events stream (process.EventHub): every handler is registered
|
// The indexer's one pull of its durable on the shared events stream (process.EventHub): every handler is registered
|
||||||
// before it starts.
|
// before it starts.
|
||||||
hub := process.NewEventHub(logger.Printf)
|
hub := process.NewEventHub(logger.Printf)
|
||||||
followPlacement(ctx, hub, cred, calls.Call, gate, func(ctx context.Context) {
|
followAssignments(ctx, hub, cred, calls.Call, gate, func(ctx context.Context) {
|
||||||
if err := ix.Recompute(ctx); err != nil {
|
if err := ix.Recompute(ctx); err != nil {
|
||||||
logger.Printf("writing the modules again after the placement of a build module changed: %v", err)
|
logger.Printf("writing the modules again after the assignments of a build module changed: %v", err)
|
||||||
}
|
}
|
||||||
}, findings, process.AwaitBucket(ctx, js, conn, firstViewsBucket, "indexer_first_views reads nothing and indexer_first_view_again is not heard", logger.Printf), logger.Printf)
|
}, findings, process.AwaitBucket(ctx, js, conn, firstViewsBucket, "indexer_first_views reads nothing and indexer_first_view_again is not heard", logger.Printf), logger.Printf)
|
||||||
tools := newToolchainWatch(ctx, js, toolchainSink{
|
tools := newToolchainWatch(ctx, js, toolchainSink{
|
||||||
@@ -649,7 +649,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
|||||||
if err := hub.Handle(numberedSubject, versions); err != nil {
|
if err := hub.Handle(numberedSubject, versions); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_ = notHeard.Bind(process.EventsStream, "no package's series moves, no node's manifest-reads-changed and no placement view is heard until the indexer starts again", func() error {
|
_ = notHeard.Bind(process.EventsStream, "no package's series moves, no node's manifest-reads-changed and no assignments view is heard until the indexer starts again", func() error {
|
||||||
if err := hub.Start(ctx, js, cred); err != nil {
|
if err := hub.Start(ctx, js, cred); err != nil {
|
||||||
stopped = cmp.Or(stopped, err)
|
stopped = cmp.Or(stopped, err)
|
||||||
return err
|
return err
|
||||||
@@ -716,7 +716,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
|||||||
return err
|
return err
|
||||||
case err := <-sourceErrs:
|
case err := <-sourceErrs:
|
||||||
return err
|
return err
|
||||||
case err := <-placementErrs:
|
case err := <-assignmentsErrs:
|
||||||
return err
|
return err
|
||||||
case err := <-tools.errs:
|
case err := <-tools.errs:
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -9,23 +9,23 @@ import (
|
|||||||
|
|
||||||
"git.novox.be/novox/mesh-sdk/go/process"
|
"git.novox.be/novox/mesh-sdk/go/process"
|
||||||
|
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
|
||||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient/enginereads"
|
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient/enginereads"
|
||||||
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
||||||
|
|
||||||
"mesh-delivery-indexer/internal/index"
|
"mesh-delivery-indexer/internal/index"
|
||||||
)
|
)
|
||||||
|
|
||||||
// engineReads is what each counted node's engine states it reads, for the indexer's mesh-wide hold (index.Holds; novox/hq
|
// engineReads is what each counted node's engine states it reads, for the indexer's mesh-wide hold (index.Holds; novox/hq
|
||||||
// ADR 0632 §2, ADR 0624 §1), in the shape the builder keeps it: asked through the node-engine seat's verb manifest-reads
|
// ADR 0632 §2, ADR 0624 §1), in the shape the builder keeps it: asked through the node-engine seat's verb manifest-reads
|
||||||
// for every node the placement names, on the indexer's start, on the seat's event manifest-reads-changed for that node and
|
// for every node the assignments names, on the indexer's start, on the seat's event manifest-reads-changed for that node and
|
||||||
// when a person asks again (indexer_engine_reads, indexer_holds); kept in the indexer's state engine-reads, one key per
|
// when a person asks again (indexer_engine_reads, indexer_holds); kept in the indexer's state engine-reads, one key per
|
||||||
// node, so a node that is away keeps its last statement. A node the placement no longer names is forgotten. Until the
|
// node, so a node that is away keeps its last statement. A node the assignments no longer names is forgotten. Until the
|
||||||
// placement is read no node is counted, and every hold stays.
|
// assignments is read no node is counted, and every hold stays.
|
||||||
type engineReads struct {
|
type engineReads struct {
|
||||||
keeper *enginereads.Keeper
|
keeper *enginereads.Keeper
|
||||||
gate *buildrun.ToolchainGate
|
gate *buildrun.ToolchainGate
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
known bool // the placement is read
|
known bool // the assignments is read
|
||||||
nodes []string
|
nodes []string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ func (r *engineReads) bind(ctx context.Context, kv jetstream.KeyValue, errs chan
|
|||||||
r.ask(nodes, "the state engine-reads is bound")
|
r.ask(nodes, "the state engine-reads is bound")
|
||||||
}
|
}
|
||||||
|
|
||||||
// placed takes the placement in, once it is read and after each change of it: a node it names newly is asked, one it no
|
// placed takes the assignments in, once it is read and after each change of it: a node it names newly is asked, one it no
|
||||||
// longer names is forgotten, key and all.
|
// longer names is forgotten, key and all.
|
||||||
func (r *engineReads) placed() {
|
func (r *engineReads) placed() {
|
||||||
nodes := r.gate.Nodes()
|
nodes := r.gate.Nodes()
|
||||||
@@ -64,7 +64,7 @@ func (r *engineReads) placed() {
|
|||||||
r.mu.Unlock()
|
r.mu.Unlock()
|
||||||
for _, node := range nodes {
|
for _, node := range nodes {
|
||||||
if first || !slices.Contains(before, node) {
|
if first || !slices.Contains(before, node) {
|
||||||
r.keeper.AskNode(node, "counted: the placement names it")
|
r.keeper.AskNode(node, "counted: the assignments names it")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, node := range before {
|
for _, node := range before {
|
||||||
@@ -94,7 +94,7 @@ func (r *engineReads) heard(m jetstream.Msg, logf func(string, ...any)) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// counted is index.Indexer.Counted: what each counted node states (nil, the baseline, for one with no statement), and
|
// counted is index.Indexer.Counted: what each counted node states (nil, the baseline, for one with no statement), and
|
||||||
// whether the placement is read.
|
// whether the assignments is read.
|
||||||
func (r *engineReads) counted() (map[string][]string, bool) {
|
func (r *engineReads) counted() (map[string][]string, bool) {
|
||||||
r.mu.Lock()
|
r.mu.Lock()
|
||||||
nodes, known := slices.Clone(r.nodes), r.known
|
nodes, known := slices.Clone(r.nodes), r.known
|
||||||
|
|||||||
@@ -13,15 +13,15 @@ import (
|
|||||||
"git.novox.be/novox/mesh-sdk/go/process"
|
"git.novox.be/novox/mesh-sdk/go/process"
|
||||||
|
|
||||||
sdkfindings "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient/findings"
|
sdkfindings "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient/findings"
|
||||||
"git.novox.be/novox/mesh-delivery/sdk/graph"
|
|
||||||
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient/heard"
|
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient/heard"
|
||||||
|
"git.novox.be/novox/mesh-delivery/sdk/graph"
|
||||||
"git.novox.be/novox/mesh-sdk/go/views"
|
"git.novox.be/novox/mesh-sdk/go/views"
|
||||||
|
|
||||||
"mesh-delivery-indexer/internal/index"
|
"mesh-delivery-indexer/internal/index"
|
||||||
)
|
)
|
||||||
|
|
||||||
// findingsBucket is the indexer's own findings, which the condition keeper raises and clears (ADR 0564 §2): a value too
|
// findingsBucket is the indexer's own findings, which the condition keeper raises and clears (ADR 0564 §2): a value too
|
||||||
// large for its views event, and its first view of the assigner's placement unanswered past its bound.
|
// large for its views event, and its first view of the assigner's assignments unanswered past its bound.
|
||||||
var findingsBucket = mcc.FindingsBucket(index.Module)
|
var findingsBucket = mcc.FindingsBucket(index.Module)
|
||||||
|
|
||||||
// readyOf binds a state's bucket: now for one bound at the start, once it appears for one awaited.
|
// readyOf binds a state's bucket: now for one bound at the start, once it appears for one awaited.
|
||||||
@@ -109,15 +109,15 @@ const graphState = graph.State
|
|||||||
// where indexer_first_view_again asks.
|
// where indexer_first_view_again asks.
|
||||||
var firstViewsBucket = index.Module + "_" + views.StatusState
|
var firstViewsBucket = index.Module + "_" + views.StatusState
|
||||||
|
|
||||||
// placementTool is the assigner's first-view tool of its placement, as the indexer's invokes names it (ADR 0621 §2).
|
// assignmentsTool is the assigner's first-view tool of its assignments, as the indexer's invokes names it (ADR 0621 §2).
|
||||||
const placementTool = index.AssignerModule + "." + index.PlacementState
|
const assignmentsTool = index.AssignerModule + "." + index.AssignmentsState
|
||||||
|
|
||||||
// followPlacement reads the seat mesh-placement's placement through the assigner's first view and then its views
|
// followAssignments reads the seat mesh-module-assigner's assignments through the assigner's first view and then its views
|
||||||
// family, never its bucket (ADR 0615 §1, DLV-F1r), into gate: each key as it changes, settled once answered, with
|
// family, never its bucket (ADR 0615 §1, DLV-F1r), into gate: each key as it changes, settled once answered, with
|
||||||
// changed told whenever what the gate passes on changed. It waits for the first answer at most views.Bound, so a
|
// changed told whenever what the gate passes on changed. It waits for the first answer at most views.Bound, so a
|
||||||
// normal start, the two owners coming up together, computes the fingerprints once; past it the indexer goes on, the gate
|
// normal start, the two owners coming up together, computes the fingerprints once; past it the indexer goes on, the gate
|
||||||
// passing every report, and raises its condition naming the assigner and placement until the answer clears it.
|
// passing every report, and raises its condition naming the assigner and assignments until the answer clears it.
|
||||||
func followPlacement(ctx context.Context, hub *process.EventHub, cred process.Account, call func(string, any) (json.RawMessage, error),
|
func followAssignments(ctx context.Context, hub *process.EventHub, cred process.Account, call func(string, any) (json.RawMessage, error),
|
||||||
gate *buildrun.ToolchainGate, changed func(context.Context), findings, status *process.AwaitedBucket, logf func(string, ...any)) *views.Follower {
|
gate *buildrun.ToolchainGate, changed func(context.Context), findings, status *process.AwaitedBucket, logf func(string, ...any)) *views.Follower {
|
||||||
finding := func(s views.Status, raised bool) {
|
finding := func(s views.Status, raised bool) {
|
||||||
w := sdkfindings.Writer(ctx, findings.KV)
|
w := sdkfindings.Writer(ctx, findings.KV)
|
||||||
@@ -133,11 +133,11 @@ func followPlacement(ctx context.Context, hub *process.EventHub, cred process.Ac
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
var settled atomic.Bool // the first view answered: the gate's nodes are the counted ones from now on
|
var settled atomic.Bool // the first view answered: the gate's nodes are the counted ones from now on
|
||||||
f := &views.Follower{Owner: index.AssignerModule, Tool: placementTool, State: index.PlacementState,
|
f := &views.Follower{Owner: index.AssignerModule, Tool: assignmentsTool, State: index.AssignmentsState,
|
||||||
Ask: views.AskVia(call, placementTool), Logf: logf,
|
Ask: views.AskVia(call, assignmentsTool), Logf: logf,
|
||||||
Raise: func(s views.Status) { finding(s, true) }, Clear: func(s views.Status) { finding(s, false) },
|
Raise: func(s views.Status) { finding(s, true) }, Clear: func(s views.Status) { finding(s, false) },
|
||||||
Changed: func(key string, value json.RawMessage, deleted bool, at time.Time) {
|
Changed: func(key string, value json.RawMessage, deleted bool, at time.Time) {
|
||||||
if gate.Placement(key, value, deleted, at) {
|
if gate.Assignments(key, value, deleted, at) {
|
||||||
changed(ctx)
|
changed(ctx)
|
||||||
}
|
}
|
||||||
if settled.Load() && gate.OnPlaced != nil {
|
if settled.Load() && gate.OnPlaced != nil {
|
||||||
@@ -160,21 +160,21 @@ func followPlacement(ctx context.Context, hub *process.EventHub, cred process.Ac
|
|||||||
}
|
}
|
||||||
settled.Store(true)
|
settled.Store(true)
|
||||||
if gate.OnPlaced != nil {
|
if gate.OnPlaced != nil {
|
||||||
gate.OnPlaced() // the placement is read: its nodes are the counted ones
|
gate.OnPlaced() // the assignments is read: its nodes are the counted ones
|
||||||
}
|
}
|
||||||
close(answered)
|
close(answered)
|
||||||
}()
|
}()
|
||||||
// The assigner's views family, on the module's one hub (the host starts it once every handler is registered); a family the
|
// The assigner's views family, on the module's one hub (the host starts it once every handler is registered); a family the
|
||||||
// hub cannot register is the finding family-not-heard until the indexer starts again (ADR 0615 §5).
|
// hub cannot register is the finding family-not-heard until the indexer starts again (ADR 0615 §5).
|
||||||
_ = notHeardWriter(ctx, findings, logf).Bind(process.EventsStream, "the placement is the first view's alone, asked again by hand, until the indexer starts again", func() error {
|
_ = notHeardWriter(ctx, findings, logf).Bind(process.EventsStream, "the assignments is the first view's alone, asked again by hand, until the indexer starts again", func() error {
|
||||||
return hub.Handle("mesh.mod."+index.AssignerModule+".event."+views.EventPrefix+"*", views.Hear(ctx, map[string]*views.Follower{index.PlacementState: f}, logf))
|
return hub.Handle("mesh.mod."+index.AssignerModule+".event."+views.EventPrefix+"*", views.Hear(ctx, map[string]*views.Follower{index.AssignmentsState: f}, logf))
|
||||||
})
|
})
|
||||||
wait, cancel := context.WithTimeout(ctx, views.Bound) // a context's bound, no timer (ADR 0437 §8)
|
wait, cancel := context.WithTimeout(ctx, views.Bound) // a context's bound, no timer (ADR 0437 §8)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
select {
|
select {
|
||||||
case <-answered:
|
case <-answered:
|
||||||
case <-wait.Done():
|
case <-wait.Done():
|
||||||
logf("the assigner has not answered placement within %s: the indexer goes on, every node's toolchain report counting until it answers", views.Bound)
|
logf("the assigner has not answered assignments within %s: the indexer goes on, every node's toolchain report counting until it answers", views.Bound)
|
||||||
}
|
}
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ go 1.26.0
|
|||||||
require (
|
require (
|
||||||
git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient v0.0.0-00010101000000-000000000000
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ type Indexer struct {
|
|||||||
// capability read mesh-wide, waiting for every counted node-engine to state it. Nil, or Counted nil, holds nothing.
|
// capability read mesh-wide, waiting for every counted node-engine to state it. Nil, or Counted nil, holds nothing.
|
||||||
Holds Store
|
Holds Store
|
||||||
// Counted is what each counted node's engine states it reads (ADR 0624 §1; nil, the baseline, for a node with no
|
// Counted is what each counted node's engine states it reads (ADR 0624 §1; nil, the baseline, for a node with no
|
||||||
// statement), and whether the counted nodes are known (the placement is read).
|
// statement), and whether the counted nodes are known (the assignments is read).
|
||||||
Counted func() (map[string][]string, bool)
|
Counted func() (map[string][]string, bool)
|
||||||
// AskEngines asks these nodes' engines what they read again (the node-engine seat's verb manifest-reads); nil asks none.
|
// AskEngines asks these nodes' engines what they read again (the node-engine seat's verb manifest-reads); nil asks none.
|
||||||
AskEngines func(nodes []string, why string)
|
AskEngines func(nodes []string, why string)
|
||||||
|
|||||||
@@ -28,10 +28,10 @@ import (
|
|||||||
// it is written as its merge would have written it, judged and said. The assigner's hold of ADR 0614 §4 holds a want on
|
// it is written as its merge would have written it, judged and said. The assigner's hold of ADR 0614 §4 holds a want on
|
||||||
// one node; this one holds the entry itself, which every node reads.
|
// one node; this one holds the entry itself, which every node reads.
|
||||||
//
|
//
|
||||||
// "Counted" is ADR 0624 §1's set: every node the placement names, present or away, each read as its engine last stated
|
// "Counted" is ADR 0624 §1's set: every node the assignments names, present or away, each read as its engine last stated
|
||||||
// through the node-engine seat's verb manifest-reads (a node with no statement read as the baseline; the indexer's state
|
// through the node-engine seat's verb manifest-reads (a node with no statement read as the baseline; the indexer's state
|
||||||
// engine-reads, nodeengineclient/enginereads). Until the placement is read no node is counted and every hold stays. The hold moves
|
// engine-reads, nodeengineclient/enginereads). Until the assignments is read no node is counted and every hold stays. The hold moves
|
||||||
// on the event manifest-reads-changed, the placement's change, a person's ask again, and a merge; never on a timer.
|
// on the event manifest-reads-changed, the assignments's change, a person's ask again, and a merge; never on a timer.
|
||||||
const (
|
const (
|
||||||
// HoldsState is the indexer's state of held entries: the bucket mesh-delivery-indexer_holds, keyed as the modules state.
|
// HoldsState is the indexer's state of held entries: the bucket mesh-delivery-indexer_holds, keyed as the modules state.
|
||||||
HoldsState = "holds"
|
HoldsState = "holds"
|
||||||
@@ -126,7 +126,7 @@ func meshWide(needs []string) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// noneCounted is why a hold stays while no node is counted.
|
// noneCounted is why a hold stays while no node is counted.
|
||||||
const noneCounted = "no node is counted yet (the placement is not read), so the hold stays until it is"
|
const noneCounted = "no node is counted yet (the assignments is not read), so the hold stays until it is"
|
||||||
|
|
||||||
// lacking is, of the counted nodes, each whose engine lacks some of needs, with what it lacks; or why no node is counted.
|
// lacking is, of the counted nodes, each whose engine lacks some of needs, with what it lacks; or why no node is counted.
|
||||||
func (ix *Indexer) lacking(needs []string) (map[string][]string, string) {
|
func (ix *Indexer) lacking(needs []string) (map[string][]string, string) {
|
||||||
@@ -232,7 +232,7 @@ func (ix *Indexer) dropHold(ctx context.Context, key, why string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// KickHolds moves every hold whose needs every counted node-engine now states, in the background, once at a time: on a
|
// KickHolds moves every hold whose needs every counted node-engine now states, in the background, once at a time: on a
|
||||||
// node's statement changed, the placement's change and the holds state's binding.
|
// node's statement changed, the assignments's change and the holds state's binding.
|
||||||
func (ix *Indexer) KickHolds(ctx context.Context) {
|
func (ix *Indexer) KickHolds(ctx context.Context) {
|
||||||
if ix.Holds == nil || ix.Counted == nil {
|
if ix.Holds == nil || ix.Counted == nil {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -37,9 +37,9 @@ const (
|
|||||||
CheckerModule = "mesh-delivery-checker"
|
CheckerModule = "mesh-delivery-checker"
|
||||||
BuildGoModule = "mesh-build-go"
|
BuildGoModule = "mesh-build-go"
|
||||||
BuildNodeModule = "mesh-build-node"
|
BuildNodeModule = "mesh-build-node"
|
||||||
PlacementState = "placement"
|
AssignmentsState = "assignments"
|
||||||
PlacementBucket = AssignerModule + "_" + PlacementState
|
AssignmentsBucket = AssignerModule + "_" + AssignmentsState
|
||||||
PlacementHistory = 5
|
AssignmentsHistory = 5
|
||||||
SourceBucket = GateModule + "_source"
|
SourceBucket = GateModule + "_source"
|
||||||
PreviewsBucket = CheckerModule + "_previews"
|
PreviewsBucket = CheckerModule + "_previews"
|
||||||
PreviewsHistory = 5
|
PreviewsHistory = 5
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import (
|
|||||||
var OldSections = []string{"state", "logs", "streams", "objects"}
|
var OldSections = []string{"state", "logs", "streams", "objects"}
|
||||||
|
|
||||||
// Foundation are the modules that keep using the old sections for good: exactly the nine parts the bus module's own
|
// Foundation are the modules that keep using the old sections for good: exactly the nine parts the bus module's own
|
||||||
// composition, placement, build or delivery needs before it can answer (novox/hq ADR 0619 §1, §3): the bus module, the
|
// composition, assignments, build or delivery needs before it can answer (novox/hq ADR 0619 §1, §3): the bus module, the
|
||||||
// grants part, the indexer, the assigner, the tool runner, the builder, the checker, mesh-config and the vault. Their new
|
// grants part, the indexer, the assigner, the tool runner, the builder, the checker, mesh-config and the vault. Their new
|
||||||
// state goes on their old sections too, so a foundation manifest asking bus state in the new form is refused.
|
// state goes on their old sections too, so a foundation manifest asking bus state in the new form is refused.
|
||||||
var Foundation = []string{"nats", "mesh-grants", "mesh-delivery-indexer", "mesh-delivery-assigner", "node-tools",
|
var Foundation = []string{"nats", "mesh-grants", "mesh-delivery-indexer", "mesh-delivery-assigner", "node-tools",
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ func TestManifestDeclarations(t *testing.T) { // 1238/AC1, 1444 (reads the build
|
|||||||
if m.Module != "mesh-delivery-indexer" {
|
if m.Module != "mesh-delivery-indexer" {
|
||||||
t.Errorf("module = %q", m.Module)
|
t.Errorf("module = %q", m.Module)
|
||||||
}
|
}
|
||||||
// 1126: placement is the seat mesh-placement's (ADR 0567): the indexer holds no placement state. ADR 0578 §1: it says
|
// 1126: assignments is the seat mesh-module-assigner's (ADR 0567): the indexer holds no assignments state. ADR 0578 §1: it says
|
||||||
// a refused manifest as manifest-refused, and (issue 1966 D3, 1966/AC10) a newly accepted one asking for bus state as
|
// a refused manifest as manifest-refused, and (issue 1966 D3, 1966/AC10) a newly accepted one asking for bus state as
|
||||||
// manifest-accepted, both events of the seat mesh-catalog it holds (ADR 0636 §4), so not among its `emits`.
|
// manifest-accepted, both events of the seat mesh-catalog it holds (ADR 0636 §4), so not among its `emits`.
|
||||||
if own := slices.DeleteFunc(slices.Clone(m.Emits), func(e string) bool { return strings.HasPrefix(e, views.EventPrefix) }); !reflect.DeepEqual(own, []string{takein.TakeInPendingEvent, takein.TakeInTakenEvent}) {
|
if own := slices.DeleteFunc(slices.Clone(m.Emits), func(e string) bool { return strings.HasPrefix(e, views.EventPrefix) }); !reflect.DeepEqual(own, []string{takein.TakeInPendingEvent, takein.TakeInTakenEvent}) {
|
||||||
@@ -136,14 +136,14 @@ func TestManifestDeclarations(t *testing.T) { // 1238/AC1, 1444 (reads the build
|
|||||||
t.Errorf("the indexer reads the bucket %s, its manifest's reads are %v (want %s)", bucket, m.Reads, read)
|
t.Errorf("the indexer reads the bucket %s, its manifest's reads are %v (want %s)", bucket, m.Reads, read)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// DLV-F1r (ADR 0615 §1): the assigner's placement is read through its first view and views family, never its state.
|
// DLV-F1r (ADR 0615 §1): the assigner's assignments is read through its first view and views family, never its state.
|
||||||
if want := []string{"mesh-build-go.toolchains", "mesh-build-node.toolchains", "mesh-delivery-builder.build-log", "mesh-delivery-checker.previews", "mesh-delivery-gate.source"}; !reflect.DeepEqual(sorted(m.Reads), want) {
|
if want := []string{"mesh-build-go.toolchains", "mesh-build-node.toolchains", "mesh-delivery-builder.build-log", "mesh-delivery-checker.previews", "mesh-delivery-gate.source"}; !reflect.DeepEqual(sorted(m.Reads), want) {
|
||||||
t.Errorf("reads = %v, want %v", m.Reads, want)
|
t.Errorf("reads = %v, want %v", m.Reads, want)
|
||||||
}
|
}
|
||||||
// "Reads FORGE" is a consumes of the indexer's stream: the broker grants only buckets by reads (mesh-control
|
// "Reads FORGE" is a consumes of the indexer's stream: the broker grants only buckets by reads (mesh-control
|
||||||
// internal/broker/state.go:62-93).
|
// internal/broker/state.go:62-93).
|
||||||
// The registry seats' published and the builder's numbered fill each package's series (DLV-I5, design 51 *The versions*).
|
// The registry seats' published and the builder's numbered fill each package's series (DLV-I5, design 51 *The versions*).
|
||||||
if want := []string{"go-package-registry.published", "mesh-build-go.toolchain.reported", "mesh-build-node.toolchain.reported", "mesh-delivery-assigner.views.placement",
|
if want := []string{"go-package-registry.published", "mesh-build-go.toolchain.reported", "mesh-build-node.toolchain.reported", "mesh-delivery-assigner.views.assignments",
|
||||||
"mesh-delivery-builder.version.numbered", "mesh-delivery-checker.preview.written", "mesh-delivery-checker.try.ended", "mesh-delivery-checker.try.started", "mesh-delivery-checker.verdict", "mesh-delivery-forge-bridge.forge.**",
|
"mesh-delivery-builder.version.numbered", "mesh-delivery-checker.preview.written", "mesh-delivery-checker.try.ended", "mesh-delivery-checker.try.started", "mesh-delivery-checker.verdict", "mesh-delivery-forge-bridge.forge.**",
|
||||||
"node-engine.manifest-reads-changed", "npm-package-registry.published"}; !reflect.DeepEqual(sorted(m.Consumes), want) { // ADR 0632 §2: the counted engines' reads
|
"node-engine.manifest-reads-changed", "npm-package-registry.published"}; !reflect.DeepEqual(sorted(m.Consumes), want) { // ADR 0632 §2: the counted engines' reads
|
||||||
t.Errorf("consumes = %v, want %v", m.Consumes, want)
|
t.Errorf("consumes = %v, want %v", m.Consumes, want)
|
||||||
@@ -157,7 +157,7 @@ func TestManifestDeclarations(t *testing.T) { // 1238/AC1, 1444 (reads the build
|
|||||||
// Three invokes: the file of a registered module's manifest and lock files (registered.go), the forge's trees (the named paths' objects, since the indexer is the one reader of a manifest's paths:
|
// Three invokes: the file of a registered module's manifest and lock files (registered.go), the forge's trees (the named paths' objects, since the indexer is the one reader of a manifest's paths:
|
||||||
// ADR 0499 clauses 2 and 3, ADR 0495 clause 1; and the module directories of the trunk, issue 1900) and whether a
|
// ADR 0499 clauses 2 and 3, ADR 0495 clause 1; and the module directories of the trunk, issue 1900) and whether a
|
||||||
// commit is in another (issue 1900).
|
// commit is in another (issue 1900).
|
||||||
if want := []string{"mesh-delivery-assigner.placement", "gitea.gitea_commit_trees", "gitea.gitea_contains", "gitea.gitea_get_file", "gitea.gitea_open_pulls", "mesh-delivery-checker.checker_tries", "mesh-delivery-forge-bridge.bridge_merged_message", "seat:node-engine.composition", "seat:node-engine.manifest-reads", "seat:mesh-hand-acts.hand-act"}; !reflect.DeepEqual(m.Invokes, want) {
|
if want := []string{"mesh-delivery-assigner.assignments", "gitea.gitea_commit_trees", "gitea.gitea_contains", "gitea.gitea_get_file", "gitea.gitea_open_pulls", "mesh-delivery-checker.checker_tries", "mesh-delivery-forge-bridge.bridge_merged_message", "seat:node-engine.composition", "seat:node-engine.manifest-reads", "seat:mesh-hand-acts.hand-act"}; !reflect.DeepEqual(m.Invokes, want) {
|
||||||
t.Errorf("invokes = %v, want %v", m.Invokes, want)
|
t.Errorf("invokes = %v, want %v", m.Invokes, want)
|
||||||
}
|
}
|
||||||
// 1945: indexer_take_in, served by its tools bundle through the takeins state; the process serves none. The same bundle
|
// 1945: indexer_take_in, served by its tools bundle through the takeins state; the process serves none. The same bundle
|
||||||
|
|||||||
@@ -60,7 +60,7 @@
|
|||||||
"go-package-registry.published",
|
"go-package-registry.published",
|
||||||
"mesh-build-go.toolchain.reported",
|
"mesh-build-go.toolchain.reported",
|
||||||
"mesh-build-node.toolchain.reported",
|
"mesh-build-node.toolchain.reported",
|
||||||
"mesh-delivery-assigner.views.placement",
|
"mesh-delivery-assigner.views.assignments",
|
||||||
"mesh-delivery-builder.version.numbered",
|
"mesh-delivery-builder.version.numbered",
|
||||||
"mesh-delivery-checker.preview.written",
|
"mesh-delivery-checker.preview.written",
|
||||||
"mesh-delivery-checker.try.ended",
|
"mesh-delivery-checker.try.ended",
|
||||||
@@ -119,7 +119,7 @@
|
|||||||
"mesh-delivery-gate.source"
|
"mesh-delivery-gate.source"
|
||||||
],
|
],
|
||||||
"invokes": [
|
"invokes": [
|
||||||
"mesh-delivery-assigner.placement",
|
"mesh-delivery-assigner.assignments",
|
||||||
"gitea.gitea_commit_trees",
|
"gitea.gitea_commit_trees",
|
||||||
"gitea.gitea_contains",
|
"gitea.gitea_contains",
|
||||||
"gitea.gitea_get_file",
|
"gitea.gitea_get_file",
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ require (
|
|||||||
git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient v0.0.0-00010101000000-000000000000 // indirect
|
git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient v0.0.0-00010101000000-000000000000 // indirect
|
||||||
git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions v0.0.0-00010101000000-000000000000 // indirect
|
git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions v0.0.0-00010101000000-000000000000 // indirect
|
||||||
git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshhandacts v0.0.0-00010101000000-000000000000 // indirect
|
git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshhandacts v0.0.0-00010101000000-000000000000 // indirect
|
||||||
git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient v0.0.0-00010101000000-000000000000 // indirect
|
git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient v0.0.0-00010101000000-000000000000 // indirect
|
||||||
git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000 // indirect
|
git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000 // indirect
|
||||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/containerimageregistryclient v0.0.0-00010101000000-000000000000 // indirect
|
git.novox.be/novox/mesh-delivery/contracts/go/clients/containerimageregistryclient v0.0.0-00010101000000-000000000000 // indirect
|
||||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000 // indirect
|
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000 // indirect
|
||||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry v0.0.0-00010101000000-000000000000 // indirect
|
git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry v0.0.0-00010101000000-000000000000 // indirect
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// how a module is spoken to"). An ask the teller holds open at the router keeps one of the three places the router gives
|
// how a module is spoken to"). An ask the teller holds open at the router keeps one of the three places the router gives
|
||||||
// its asker (asks.MostOpen) until it is answered, it lapses, or the gate or the assigner takes its key away. When that last
|
// its asker (asks.MostOpen) until it is answered, it lapses, or the gate or the assigner takes its key away. When that last
|
||||||
// never comes (on 2026-10-31 two merge asks of pull requests merged by another path held two places for a day, and a
|
// never comes (on 2026-10-31 two merge asks of pull requests merged by another path held two places for a day, and a
|
||||||
// placement ask was refused for want of room), a person ends the ask by hand. The teller's process serves no request
|
// assignments ask was refused for want of room), a person ends the ask by hand. The teller's process serves no request
|
||||||
// subject (ADR 0437 §4), so the tool writes an end into the teller's state ends, which the teller watches, carries out
|
// subject (ADR 0437 §4), so the tool writes an end into the teller's state ends, which the teller watches, carries out
|
||||||
// and answers there, as the indexer answers indexer_take_in.
|
// and answers there, as the indexer answers indexer_take_in.
|
||||||
package ends
|
package ends
|
||||||
|
|||||||
@@ -4,20 +4,22 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"maps"
|
||||||
"mesh-delivery-view/internal/view"
|
"mesh-delivery-view/internal/view"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
|
||||||
|
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||||
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
|
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
|
||||||
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
|
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
|
||||||
"git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
|
||||||
"git.novox.be/novox/mesh-contracts/placement/go/clients/meshwantsclient"
|
|
||||||
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||||
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshwantsclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-assigner/asks"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-assigner/asks"
|
||||||
@@ -27,11 +29,13 @@ import (
|
|||||||
func serve(t *testing.T) string {
|
func serve(t *testing.T) string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
m := view.New()
|
m := view.New()
|
||||||
placed, err := meshmoduleassignerclient.EncodePlacement(meshmoduleassigner.Placement{Node: "a", Modules: []meshmoduleassigner.PlacedModule{{Module: "x"}}})
|
placed, err := meshmoduleassignerclient.AssignmentsValues(meshmoduleassigner.Assignments{Node: "a", Modules: []meshmoduleassigner.PlacedModule{{Module: "x"}}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
m.Apply(view.Entry{Bucket: view.PlacementBucket, Key: "placement.a", Value: placed})
|
for _, k := range slices.Sorted(maps.Keys(placed)) { // a key per module, then the node key
|
||||||
|
m.Apply(view.Entry{Bucket: view.AssignmentsBucket, Key: k, Value: placed[k]})
|
||||||
|
}
|
||||||
dir, err := os.MkdirTemp(os.Getenv("TMPDIR"), "c")
|
dir, err := os.MkdirTemp(os.Getenv("TMPDIR"), "c")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ import (
|
|||||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||||
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshwatchdogsclient"
|
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshwatchdogsclient"
|
||||||
"git.novox.be/novox/mesh-contracts/placement/go/clients/meshwantsclient"
|
|
||||||
"git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient"
|
"git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||||
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshwantsclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/takein"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/takein"
|
||||||
|
|||||||
@@ -6,23 +6,25 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
|
"maps"
|
||||||
"mesh-delivery-view/internal/view"
|
"mesh-delivery-view/internal/view"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
|
||||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||||
|
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||||
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshwatchdogsclient"
|
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshwatchdogsclient"
|
||||||
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
|
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
|
||||||
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
|
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
|
||||||
"git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
|
||||||
"git.novox.be/novox/mesh-contracts/placement/go/clients/meshwantsclient"
|
|
||||||
"git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
|
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||||
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshwantsclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-assigner/asks"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-assigner/asks"
|
||||||
@@ -70,7 +72,7 @@ func TestProcessWholeSeesTheBuckets(t *testing.T) { // 1134/AC8
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
kvs := map[string]jetstream.KeyValue{}
|
kvs := map[string]jetstream.KeyValue{}
|
||||||
for _, b := range []string{view.HaveBucket, view.PlacementBucket, view.PresenceBucket, view.StuckBucket} {
|
for _, b := range []string{view.HaveBucket, view.AssignmentsBucket, view.PresenceBucket, view.StuckBucket} {
|
||||||
kv, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: b, History: 5})
|
kv, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: b, History: 5})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -90,20 +92,22 @@ func TestProcessWholeSeesTheBuckets(t *testing.T) { // 1134/AC8
|
|||||||
}
|
}
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
// The seat's placement (issue 774, ADR 0549 §3): x and y on a, nothing on b.
|
// The seat's assignments (issue 774, ADR 0549 §3): x and y on a, nothing on b.
|
||||||
placeValue := func(node string, modules ...string) []byte {
|
place := func(node string, modules ...string) {
|
||||||
p := meshmoduleassigner.Placement{Node: node}
|
p := meshmoduleassigner.Assignments{Node: node}
|
||||||
for _, m := range modules {
|
for _, m := range modules {
|
||||||
p.Modules = append(p.Modules, meshmoduleassigner.PlacedModule{Module: m})
|
p.Modules = append(p.Modules, meshmoduleassigner.PlacedModule{Module: m})
|
||||||
}
|
}
|
||||||
v, err := meshmoduleassignerclient.EncodePlacement(p)
|
values, err := meshmoduleassignerclient.AssignmentsValues(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
return v
|
for _, k := range slices.Sorted(maps.Keys(values)) { // a key per module, then the node key
|
||||||
|
put(view.AssignmentsBucket, k, values[k])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
put(view.PlacementBucket, "placement.a", placeValue("a", "x", "y"))
|
place("a", "x", "y")
|
||||||
put(view.PlacementBucket, "placement.b", placeValue("b"))
|
place("b")
|
||||||
put(view.HaveBucket, "a.x", haveValue("b1", nodeengineclient.PhaseChecking, nodeengineclient.HealthWaits))
|
put(view.HaveBucket, "a.x", haveValue("b1", nodeengineclient.PhaseChecking, nodeengineclient.HealthWaits))
|
||||||
put(view.HaveBucket, "b.x", haveValue("b1", nodeengineclient.PhaseRunning, nodeengineclient.HealthOK))
|
put(view.HaveBucket, "b.x", haveValue("b1", nodeengineclient.PhaseRunning, nodeengineclient.HealthOK))
|
||||||
|
|
||||||
@@ -182,7 +186,7 @@ func TestProcessWholeShowsWhatTheWatchmanSays(t *testing.T) { // 1168/AC5
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
kvs := map[string]jetstream.KeyValue{}
|
kvs := map[string]jetstream.KeyValue{}
|
||||||
for _, b := range []string{view.HaveBucket, view.PlacementBucket, view.PresenceBucket, view.StuckBucket} {
|
for _, b := range []string{view.HaveBucket, view.AssignmentsBucket, view.PresenceBucket, view.StuckBucket} {
|
||||||
kv, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: b, History: 5})
|
kv, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: b, History: 5})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -269,7 +273,7 @@ func TestProcessWholeShowsTheIndexersBucketsOnceTheyExist(t *testing.T) { // 124
|
|||||||
}
|
}
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
for _, b := range []string{view.HaveBucket, view.PlacementBucket, view.PresenceBucket, view.StuckBucket} {
|
for _, b := range []string{view.HaveBucket, view.AssignmentsBucket, view.PresenceBucket, view.StuckBucket} {
|
||||||
if _, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: b, History: 5}); err != nil {
|
if _, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: b, History: 5}); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Command mesh-delivery-view is the operator's view of delivery (novox/hq ADR 0429 §3): it watches the have, the seat's placement
|
// Command mesh-delivery-view is the operator's view of delivery (novox/hq ADR 0429 §3): it watches the have, the seat's assignments
|
||||||
// and builds buckets on its own bus credential, derives one row per module and node in memory, and serves them on a
|
// and builds buckets on its own bus credential, derives one row per module and node in memory, and serves them on a
|
||||||
// local socket (for mesh-delivery-cli) and on a local web page. It starts, stops or retries nothing; the one thing it writes is the operator's answer to an ask that needs no proof of who
|
// local socket (for mesh-delivery-cli) and on a local web page. It starts, stops or retries nothing; the one thing it writes is the operator's answer to an ask that needs no proof of who
|
||||||
// answered, under `<ask key>.view` in its bucket answers (ADR 0528 clause 6).
|
// answered, under `<ask key>.view` in its bucket answers (ADR 0528 clause 6).
|
||||||
@@ -21,9 +21,9 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
sdkfindings "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient/findings"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-view/viewstate"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-view/viewstate"
|
||||||
sdkfindings "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient/findings"
|
|
||||||
"git.novox.be/novox/mesh-delivery/sdk/trunk"
|
"git.novox.be/novox/mesh-delivery/sdk/trunk"
|
||||||
"git.novox.be/novox/mesh-sdk/go/process"
|
"git.novox.be/novox/mesh-sdk/go/process"
|
||||||
"git.novox.be/novox/mesh-sdk/go/views"
|
"git.novox.be/novox/mesh-sdk/go/views"
|
||||||
@@ -126,7 +126,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
open := buckets{js: js, reader: reader}
|
open := buckets{js: js, reader: reader}
|
||||||
for _, bucket := range []string{view.HaveBucket, view.PlacementBucket, view.PresenceBucket, view.StuckBucket, view.CandidatesBucket, view.ModulesBucket, view.AsksBucket, view.AnswersBucket, builds.BuilderBuildsBucket, view.AssignerWantsBucket, view.SourceBucket, view.AssignerAsksBucket, viewstate.ViewAnswersBucket, view.TakenBucket} {
|
for _, bucket := range []string{view.HaveBucket, view.AssignmentsBucket, view.PresenceBucket, view.StuckBucket, view.CandidatesBucket, view.ModulesBucket, view.AsksBucket, view.AnswersBucket, builds.BuilderBuildsBucket, view.AssignerWantsBucket, view.SourceBucket, view.AssignerAsksBucket, viewstate.ViewAnswersBucket, view.TakenBucket} {
|
||||||
go follow(ctx, open, bucket, false, model, logger)
|
go follow(ctx, open, bucket, false, model, logger)
|
||||||
}
|
}
|
||||||
// The modules' and the source's history give each fingerprint's first commit and each head's source revision (ADR 0556).
|
// The modules' and the source's history give each fingerprint's first commit and each head's source revision (ADR 0556).
|
||||||
@@ -185,7 +185,7 @@ var (
|
|||||||
// owner's read tool (as the view's invokes names it) and views family (its events on the shared events stream); the
|
// owner's read tool (as the view's invokes names it) and views family (its events on the shared events stream); the
|
||||||
// view's tools ask the other six through the owners' read tools on each call.
|
// view's tools ask the other six through the owners' read tools on each call.
|
||||||
var ownersFollowed = []struct{ owner, state, tool string }{
|
var ownersFollowed = []struct{ owner, state, tool string }{
|
||||||
{view.AssignerModule, view.PlacementState, view.AssignerModule + ".assigner_read_placement"},
|
{view.AssignerModule, view.AssignmentsState, view.AssignerModule + ".assigner_read_assignments"},
|
||||||
{view.AssignerModule, "wants", view.AssignerModule + ".assigner_read_wants"},
|
{view.AssignerModule, "wants", view.AssignerModule + ".assigner_read_wants"},
|
||||||
{view.AssignerModule, "asks", view.AssignerModule + ".assigner_read_asks"},
|
{view.AssignerModule, "asks", view.AssignerModule + ".assigner_read_asks"},
|
||||||
{view.IndexerModule, "candidates", view.IndexerModule + ".indexer_read_candidates"},
|
{view.IndexerModule, "candidates", view.IndexerModule + ".indexer_read_candidates"},
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ require (
|
|||||||
git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient 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/health-and-repair/go/clients/meshwatchdogsclient 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/identity-and-access/go/clients/meshvaultclient v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-contracts/placement/go/clients/meshwantsclient v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshwantsclient v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient 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-delivery/contracts/go/seats/meshcatalog v0.0.0-00010101000000-000000000000
|
||||||
@@ -58,7 +58,7 @@ require (
|
|||||||
git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshhandacts v0.0.0-00010101000000-000000000000 // indirect
|
git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshhandacts v0.0.0-00010101000000-000000000000 // indirect
|
||||||
git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshwatchdogs v0.0.0-00010101000000-000000000000 // indirect
|
git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshwatchdogs v0.0.0-00010101000000-000000000000 // indirect
|
||||||
git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshvault v0.0.0-00010101000000-000000000000 // indirect
|
git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshvault v0.0.0-00010101000000-000000000000 // indirect
|
||||||
git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants v0.0.0-00010101000000-000000000000 // indirect
|
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants v0.0.0-00010101000000-000000000000 // indirect
|
||||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/containerimageregistryclient v0.0.0-00010101000000-000000000000 // indirect
|
git.novox.be/novox/mesh-delivery/contracts/go/clients/containerimageregistryclient v0.0.0-00010101000000-000000000000 // indirect
|
||||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry v0.0.0-00010101000000-000000000000 // indirect
|
git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry v0.0.0-00010101000000-000000000000 // indirect
|
||||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/ociartifactregistry v0.0.0-00010101000000-000000000000 // indirect
|
git.novox.be/novox/mesh-delivery/contracts/go/seats/ociartifactregistry v0.0.0-00010101000000-000000000000 // indirect
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
|
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
|
||||||
"git.novox.be/novox/mesh-contracts/placement/go/clients/meshwantsclient"
|
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||||
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshwantsclient"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-assigner/asks"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-assigner/asks"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-view/viewstate"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-view/viewstate"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.novox.be/novox/mesh-contracts/placement/go/clients/meshwantsclient"
|
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||||
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshwantsclient"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-view/viewstate"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-view/viewstate"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package view
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||||
"git.novox.be/novox/mesh-contracts/placement/go/clients/meshwantsclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshwantsclient"
|
||||||
"git.novox.be/novox/mesh-sdk/go/motion"
|
"git.novox.be/novox/mesh-sdk/go/motion"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ const (
|
|||||||
IndexerConfigCandidatesState = "config-candidates"
|
IndexerConfigCandidatesState = "config-candidates"
|
||||||
|
|
||||||
// The assigner's states.
|
// The assigner's states.
|
||||||
PlacementState = "placement"
|
AssignmentsState = "assignments"
|
||||||
PlacementBucket = AssignerModule + "_" + PlacementState
|
AssignmentsBucket = AssignerModule + "_" + AssignmentsState
|
||||||
AssignerWantsBucket = AssignerModule + "_wants"
|
AssignerWantsBucket = AssignerModule + "_wants"
|
||||||
AssignerAsksBucket = AssignerModule + "_asks"
|
AssignerAsksBucket = AssignerModule + "_asks"
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import (
|
|||||||
|
|
||||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||||
"git.novox.be/novox/mesh-contracts/placement/go/clients/meshwantsclient"
|
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||||
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshwantsclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/takein"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/takein"
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user