Merge pull request 'The vault reads a machine's placement once per answer and says how long each answer took (issue 774)' (#576) from fix/vault-own-loop into main
This commit was merged in pull request #576.
This commit is contained in:
@@ -11,8 +11,10 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
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"
|
||||||
@@ -87,7 +89,7 @@ func (v *Vault) credentialRefusal(node string, c CredentialAsked, plan *planRead
|
|||||||
}
|
}
|
||||||
// The consuming module stands on the machine: read from the placement and the assignments, never assumed (one
|
// The consuming module stands on the machine: read from the placement and the assignments, never assumed (one
|
||||||
// that cannot be read refuses). The assignments' mirror can be behind, so one it does not hold is confirmed directly.
|
// that cannot be read refuses). The assignments' mirror can be behind, so one it does not hold is confirmed directly.
|
||||||
placed, err := v.placedOn(node, c.ConsumerModule)
|
placed, err := plan.placedOn(c.ConsumerModule)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Sprintf(notYetPrefix+"where %s stands could not be read: %v", c.ConsumerModule, err)
|
return fmt.Sprintf(notYetPrefix+"where %s stands could not be read: %v", c.ConsumerModule, err)
|
||||||
}
|
}
|
||||||
@@ -122,13 +124,19 @@ func (v *Vault) credentialRefusal(node string, c CredentialAsked, plan *planRead
|
|||||||
}
|
}
|
||||||
|
|
||||||
// planReader reads the assignments of one machine's modules for one answer of `own`, each module's once (issue 1126,
|
// planReader reads the assignments of one machine's modules for one answer of `own`, each module's once (issue 1126,
|
||||||
// 2026-10-28: one read per own secret and per credential, 203 on novox, held the answer past the node-engine's bound).
|
// 2026-10-28: one read per own secret and per credential, 203 on novox, held the answer past the node-engine's bound),
|
||||||
|
// and the machine's placement once (issue 774, 2026-10-31: read once per credential, 143 bus reads in one answer to ace).
|
||||||
type planReader struct {
|
type planReader struct {
|
||||||
node string
|
node string
|
||||||
reader AssignmentReader
|
reader AssignmentReader
|
||||||
confirm AssignmentReader
|
confirm AssignmentReader
|
||||||
why string
|
why string
|
||||||
got map[string]planRead
|
got map[string]planRead
|
||||||
|
// placement reads the machine's placed modules; placed and placedErr keep the one read for the answer.
|
||||||
|
placement func() ([]string, error)
|
||||||
|
placedRead bool
|
||||||
|
placed []string
|
||||||
|
placedErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
type planRead struct {
|
type planRead struct {
|
||||||
@@ -142,7 +150,17 @@ func (v *Vault) plansOf(node string) *planReader {
|
|||||||
if d, ok := v.Askers.(Declared); ok && d.Confirm != nil {
|
if d, ok := v.Askers.(Declared); ok && d.Confirm != nil {
|
||||||
confirm = standing(d.Confirm)
|
confirm = standing(d.Confirm)
|
||||||
}
|
}
|
||||||
return &planReader{node: node, confirm: confirm, reader: read, why: why, got: map[string]planRead{}}
|
return &planReader{node: node, confirm: confirm, reader: read, why: why, got: map[string]planRead{},
|
||||||
|
placement: func() ([]string, error) { return v.placedModules(node) }}
|
||||||
|
}
|
||||||
|
|
||||||
|
// placedOn says module is placed on the machine by the catalogue, from the machine's placement read once for the answer.
|
||||||
|
func (p *planReader) placedOn(module string) (bool, error) {
|
||||||
|
if !p.placedRead {
|
||||||
|
p.placed, p.placedErr = p.placement()
|
||||||
|
p.placedRead = true
|
||||||
|
}
|
||||||
|
return slices.Contains(p.placed, module), p.placedErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// read is module's assignment on the machine, nil when there is none, or why the vault reads none.
|
// read is module's assignment on the machine, nil when there is none, or why the vault reads none.
|
||||||
@@ -222,7 +240,22 @@ func composeOrigin(origin string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// OwnOfNode answers the node-engine of a machine the own secrets of its modules, each sealed to the machine's stated key.
|
// OwnOfNode answers the node-engine of a machine the own secrets of its modules, each sealed to the machine's stated key.
|
||||||
|
// Every answer says how long it took, and a refusal is said too (issue 774, 2026-10-31: ace's asks went unanswered past
|
||||||
|
// the node-engine's 15 s and the vault's journal named none of them, so whether they arrived could not be told).
|
||||||
func (v *Vault) OwnOfNode(caller string, asked ...CredentialAsked) (OwnAnswer, error) {
|
func (v *Vault) OwnOfNode(caller string, asked ...CredentialAsked) (OwnAnswer, error) {
|
||||||
|
began := v.Now()
|
||||||
|
a, err := v.ownOfNode(caller, asked...)
|
||||||
|
took := v.Now().Sub(began).Round(time.Millisecond)
|
||||||
|
if err != nil {
|
||||||
|
v.log("own for %s, %d credentials asked, refused after %s: %v", caller, len(asked), took, err)
|
||||||
|
return a, err
|
||||||
|
}
|
||||||
|
v.log("handed %s's node-engine the %d own secrets of its modules and %d credentials (%d not held), sealed to its key, in %s",
|
||||||
|
a.Node, len(a.Own), len(a.Credentials), len(a.Missing), took)
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Vault) ownOfNode(caller string, asked ...CredentialAsked) (OwnAnswer, error) {
|
||||||
node, ok := nodeEngineOf(caller)
|
node, ok := nodeEngineOf(caller)
|
||||||
if !ok {
|
if !ok {
|
||||||
if caller == "" || caller == stdio.Unproven {
|
if caller == "" || caller == stdio.Unproven {
|
||||||
@@ -328,8 +361,6 @@ func (v *Vault) OwnOfNode(caller string, asked ...CredentialAsked) (OwnAnswer, e
|
|||||||
return OwnAnswer{}, fmt.Errorf("nothing is handed out to %s: who was handed its %d values could not be recorded: %w",
|
return OwnAnswer{}, fmt.Errorf("nothing is handed out to %s: who was handed its %d values could not be recorded: %w",
|
||||||
caller, len(handings), err)
|
caller, len(handings), err)
|
||||||
}
|
}
|
||||||
v.log("handed %s's node-engine the %d own secrets of its modules and %d credentials (%d not held), sealed to its key",
|
|
||||||
node, len(out.Own), len(out.Credentials), len(out.Missing))
|
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -143,6 +144,11 @@ func TestOwnReadsEachAssignmentOnceAndSyncsItsRecordOnce(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Issue 774, 2026-10-31 (ace's 143 credentials went unanswered past the node-engine's 15 s while novox's asked every
|
||||||
|
// 8 s): the machine's placement, one key, is read once per answer too, never once per credential.
|
||||||
|
placed := placementsOf(t, map[string][]string{"anchor": {"web"}})
|
||||||
|
placementReads := 0
|
||||||
|
v.Placement = PlacementReader{Keys: placed.Keys, Read: func(key string) ([]byte, error) { placementReads++; return placed.Read(key) }}
|
||||||
for k := range reads {
|
for k := range reads {
|
||||||
delete(reads, k)
|
delete(reads, k)
|
||||||
}
|
}
|
||||||
@@ -161,6 +167,10 @@ func TestOwnReadsEachAssignmentOnceAndSyncsItsRecordOnce(t *testing.T) {
|
|||||||
if len(reads) != 2 {
|
if len(reads) != 2 {
|
||||||
t.Errorf("the answer read %v, want each of anchor's two modules once", reads)
|
t.Errorf("the answer read %v, want each of anchor's two modules once", reads)
|
||||||
}
|
}
|
||||||
|
if placementReads != 1 {
|
||||||
|
t.Errorf("one answer of two credentials read anchor's placement %d times, want once", placementReads)
|
||||||
|
}
|
||||||
|
v.Placement = PlacementReader{}
|
||||||
if n := v.Store.syncs - syncs; n != 1 {
|
if n := v.Store.syncs - syncs; n != 1 {
|
||||||
t.Errorf("one answer synced the record %d times", n)
|
t.Errorf("one answer synced the record %d times", n)
|
||||||
}
|
}
|
||||||
@@ -182,9 +192,15 @@ func TestOwnReadsEachAssignmentOnceAndSyncsItsRecordOnce(t *testing.T) {
|
|||||||
if err := os.Mkdir(filepath.Join(v.dir, "store", "handed.jsonl"), 0o700); err != nil {
|
if err := os.Mkdir(filepath.Join(v.dir, "store", "handed.jsonl"), 0o700); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
// And the refusal is said in the vault's journal with how long it took, as every answer is.
|
||||||
|
var said []string
|
||||||
|
v.Log = func(format string, args ...any) { said = append(said, fmt.Sprintf(format, args...)) }
|
||||||
if a, err := v.OwnOfNode("node.anchor"); err == nil {
|
if a, err := v.OwnOfNode("node.anchor"); err == nil {
|
||||||
t.Errorf("handed with no record of it: %+v", a)
|
t.Errorf("handed with no record of it: %+v", a)
|
||||||
}
|
}
|
||||||
|
if last := strings.Join(said, "\n"); !strings.Contains(last, "own for node.anchor, 0 credentials asked, refused after ") {
|
||||||
|
t.Errorf("a refused own was said as %q", last)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustHanded(t *testing.T, v testVault) []Handing {
|
func mustHanded(t *testing.T, v testVault) []Handing {
|
||||||
|
|||||||
@@ -58,22 +58,29 @@ func (v *Vault) placements() (map[string]delivery.Placement, error) {
|
|||||||
|
|
||||||
// placedOn says module is placed on node by the catalogue.
|
// placedOn says module is placed on node by the catalogue.
|
||||||
func (v *Vault) placedOn(node, module string) (bool, error) {
|
func (v *Vault) placedOn(node, module string) (bool, error) {
|
||||||
|
placed, err := v.placedModules(node)
|
||||||
|
return slices.Contains(placed, module), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// placedModules is every module the catalogue places on node, read from the node's one key of the placement; none when
|
||||||
|
// the vault reads no placement or the node has no key.
|
||||||
|
func (v *Vault) placedModules(node string) ([]string, error) {
|
||||||
if v.Placement.Read == nil {
|
if v.Placement.Read == nil {
|
||||||
return false, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
key, err := delivery.PlacementKey(node)
|
key, err := delivery.PlacementKey(node)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return nil, err
|
||||||
}
|
}
|
||||||
raw, err := v.Placement.Read(key)
|
raw, err := v.Placement.Read(key)
|
||||||
if err != nil || raw == nil {
|
if err != nil || raw == nil {
|
||||||
return false, err
|
return nil, err
|
||||||
}
|
}
|
||||||
p, err := delivery.DecodePlacement(raw)
|
p, err := delivery.DecodePlacement(raw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("the placement of %s is not readable: %w", node, err)
|
return nil, fmt.Errorf("the placement of %s is not readable: %w", node, err)
|
||||||
}
|
}
|
||||||
return slices.Contains(p.Assigned(), module), nil
|
return p.Assigned(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// standsOn says module stands on node: placed there, or assigned there while the assignments stand. A read that fails is
|
// standsOn says module stands on node: placed there, or assigned there while the assignments stand. A read that fails is
|
||||||
|
|||||||
Reference in New Issue
Block a user