Merge remote-tracking branch 'origin/main' into feat/890-placement
This commit is contained in:
@@ -53,6 +53,25 @@ func read(s stdio.KeptState) check.StateRead {
|
||||
}}
|
||||
}
|
||||
|
||||
// runsOf says which commit the mesh runs of a module from the indexer's modules state, as the checker's own record of it does
|
||||
// (the commit the module's record was computed at); "" when the record is not readable.
|
||||
type runsOf struct{ modules check.StateRead }
|
||||
|
||||
func (r runsOf) CommitOf(module string) string {
|
||||
if r.modules.Get == nil {
|
||||
return ""
|
||||
}
|
||||
raw, err := r.modules.Get(module)
|
||||
if err != nil || len(raw) == 0 {
|
||||
return ""
|
||||
}
|
||||
m, err := delivery.DecodeModule(raw)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return m.Commit
|
||||
}
|
||||
|
||||
// stateStore is a state the tools write: the tries.
|
||||
type stateStore struct {
|
||||
check.StateRead
|
||||
@@ -197,6 +216,7 @@ func main() {
|
||||
StepStates: map[string]check.StateRead{"mesh-build-go": read(stdio.State("mesh-build-go.steps")), "mesh-build-node": read(stdio.State("mesh-build-node.steps"))},
|
||||
HaveState: read(stdio.State("node-engine.have")), StepBound: stepBound(),
|
||||
TurnState: read(stdio.State("mesh-delivery-gate.turns"))}
|
||||
r.Runs = runsOf{read(stdio.State("mesh-delivery-indexer.modules"))} // the node-engine at the commit the mesh runs, as the checker reads it
|
||||
tries := stdio.State(check.TriesState)
|
||||
source := stdio.State("mesh-delivery-gate.source")
|
||||
trunks := func() (map[string]string, error) {
|
||||
|
||||
@@ -272,13 +272,20 @@ func (ch *Checker) stale(v delivery.Verdict, cs *Case) bool {
|
||||
// besideNow are the commits a sibling is read at now, by repository: its trunk head, and for the node-engine the commit the
|
||||
// mesh runs when the indexer names one, as besideFor places them.
|
||||
func (ch *Checker) besideNow() map[string]string {
|
||||
now := ch.heads()
|
||||
if ch.Runs != nil {
|
||||
if c := ch.Runs.CommitOf(nodeEngineModule); c != "" {
|
||||
if now == nil {
|
||||
now = map[string]string{}
|
||||
return besideAt(ch.heads(), ch.Runs)
|
||||
}
|
||||
|
||||
// besideAt places the commits a sibling is read at: the trunk heads, with the node-engine at the commit the mesh runs when
|
||||
// runs names one (ADR 0548 §6). The checker's decision and the status read the same commits through it.
|
||||
func besideAt(now map[string]string, runs interface{ CommitOf(module string) string }) map[string]string {
|
||||
if runs != nil {
|
||||
if c := runs.CommitOf(nodeEngineModule); c != "" {
|
||||
out := make(map[string]string, len(now)+1)
|
||||
for r, h := range now {
|
||||
out[r] = h
|
||||
}
|
||||
now[nodeEngineRepo] = c
|
||||
out[nodeEngineRepo] = c
|
||||
return out
|
||||
}
|
||||
}
|
||||
return now
|
||||
|
||||
@@ -75,3 +75,40 @@ func TestCheckerStatusNamesTheWaitingChecksByKind(t *testing.T) { // 1897/AC4
|
||||
t.Errorf("waiting = %v, want %v", st.Waiting, want)
|
||||
}
|
||||
}
|
||||
|
||||
// 568 note 21: a verdict that read the node-engine at its running commit is not waiting for a trunk move while that commit
|
||||
// is still the one the mesh runs, though the node-engine's trunk has moved on (ADR 0548 §6).
|
||||
func TestCheckerStatusReadsTheNodeEngineAtItsRunningCommit(t *testing.T) { // 568/21
|
||||
cand := candidateOf(t, group(1), pullOf(repoA, 1, head1))
|
||||
ckey, _ := delivery.CandidatesKey(group(1))
|
||||
vkey := keyOf(t, group(1), cand)
|
||||
dc, _ := delivery.DecodeCandidate(cand)
|
||||
judged, err := delivery.EncodeVerdict(delivery.Verdict{Group: group(1), Heads: dc.HeadsHash, Trunk: map[string]string{repoA: trunkA}, Beside: map[string]string{nodeEngineRepo: "run111"}, State: delivery.VerdictPass, Levels: passing().Levels})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
heads := func() (map[string]string, error) {
|
||||
return map[string]string{repoA: trunkA, nodeEngineRepo: "trunk222"}, nil
|
||||
}
|
||||
for _, row := range []struct {
|
||||
name string
|
||||
runs runsAt
|
||||
want int
|
||||
}{
|
||||
{"the running commit the verdict read", runsAt{nodeEngineModule: "run111"}, 0},
|
||||
{"the running commit moved", runsAt{nodeEngineModule: "run999"}, 1},
|
||||
{"no running commit known: the trunk's", nil, 1},
|
||||
} {
|
||||
rd := Reader{CandidateState: fakeState{ckey: cand}.read(), VerdictState: fakeState{vkey: judged}.read(), Trunks: heads}
|
||||
if row.runs != nil {
|
||||
rd.Runs = row.runs
|
||||
}
|
||||
st, err := rd.Status()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := len(st.Waiting[KindTrunk]); got != row.want {
|
||||
t.Errorf("%s: waiting for a trunk move = %v, want %d", row.name, st.Waiting[KindTrunk], row.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,10 @@ type Reader struct {
|
||||
Trunks func() (map[string]string, error)
|
||||
// TurnState is the gate's merge turns (ADR 0548 §3); without it no check is named as held by a turn.
|
||||
TurnState StateRead
|
||||
Now func() time.Time
|
||||
// Runs says which commit the mesh runs of a module (the indexer's modules), as the checker's decision reads it: the
|
||||
// node-engine a verdict names is compared at its running commit, not at the trunk's (ADR 0548 §6); nil knows none.
|
||||
Runs interface{ CommitOf(module string) string }
|
||||
Now func() time.Time
|
||||
}
|
||||
|
||||
// VerdictRead is a verdict as stored with the full report of the status of each pull request it judged (Report), when the
|
||||
@@ -489,7 +492,7 @@ func (r Reader) Status() (Status, error) {
|
||||
case IsNodesFailure(v):
|
||||
st.Waiting[KindNodes] = append(st.Waiting[KindNodes], c.Group)
|
||||
case heads != nil:
|
||||
if cs := CaseOf(c, heads); cs != nil && (!sameTrunk(v.Trunk, cs.Trunk) || besideMoved(v.Beside, heads)) {
|
||||
if cs := CaseOf(c, heads); cs != nil && (!sameTrunk(v.Trunk, cs.Trunk) || besideMoved(v.Beside, besideAt(heads, r.Runs))) {
|
||||
st.Waiting[KindTrunk] = append(st.Waiting[KindTrunk], c.Group)
|
||||
if why := heldBy(turns, c, heads); why != "" {
|
||||
st.HeldByTurn[c.Group] = why
|
||||
|
||||
@@ -961,6 +961,17 @@ func TestGrants(t *testing.T) {
|
||||
if res := p.Compose(); res.Sealed == nil || !strings.Contains(res.Sealed.Why, "a read of this pass failed") {
|
||||
t.Errorf("a pass past a failed read = %+v", res.Sealed)
|
||||
}
|
||||
// While the hold lasts the warning stands, said once however many passes find it; it names why.
|
||||
p.Compose()
|
||||
nHeld := 0
|
||||
for _, c := range said {
|
||||
if c.Key == grants.HeldCondition && c.State == asks.StateRaised && c.Severity == asks.SeverityWarning && strings.Contains(c.Summary, "a read of this pass failed") {
|
||||
nHeld++
|
||||
}
|
||||
}
|
||||
if nHeld != 1 {
|
||||
t.Errorf("the held list raised the warning %d times, want once: %+v", nHeld, said)
|
||||
}
|
||||
b.refused[grants.ReadAccounts] = false
|
||||
if now, _, _ := p.Sealed(); now.Digest != g.Digest || now.Object != g.Object {
|
||||
t.Errorf("the list in force moved past a failed read: %s, was %s", now.Digest, g.Digest)
|
||||
@@ -990,6 +1001,18 @@ func TestGrants(t *testing.T) {
|
||||
if p.Compose(); func() bool { now, _, _ := p.Sealed(); return now.Digest != g.Digest }() {
|
||||
t.Errorf("the list in force moved with every read and hash back")
|
||||
}
|
||||
if last := said[len(said)-1]; last.Key != grants.HeldCondition || last.State != asks.StateCleared {
|
||||
t.Errorf("the held warning is not cleared once nothing is held: %+v", last)
|
||||
}
|
||||
// A part started while the warning stood (the said state is in memory only) clears it on its first pass.
|
||||
var fresh []asks.Condition
|
||||
fp := partOn(b)
|
||||
fp.Objects = p.Objects
|
||||
fp.Asker, fp.Say = fa, func(c asks.Condition) error { fresh = append(fresh, c); return nil }
|
||||
fp.Compose()
|
||||
if len(fresh) == 0 || fresh[0].Key != grants.HeldCondition || fresh[0].State != asks.StateCleared {
|
||||
t.Errorf("a fresh part's first pass does not say the held warning is cleared: %+v", fresh)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -143,6 +143,12 @@ type Part struct {
|
||||
made creations
|
||||
// askSeen is the revision of the last ask to compose (KeyAsked) this part acted on.
|
||||
askSeen uint64
|
||||
// heldSaid is the reason of the held-list condition last said (empty: none raised); heldKnown is whether one was said
|
||||
// since the start, so the first pass after a start always says its state (a condition raised before a restart is cleared
|
||||
// by it). holdMu guards both.
|
||||
holdMu sync.Mutex
|
||||
heldSaid string
|
||||
heldKnown bool
|
||||
}
|
||||
|
||||
// Ask is the value of KeyAsked: a tool's ask to compose now, and, from grants_assert, to create one module's missing
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/asks"
|
||||
"git.novox.be/novox/mesh-sdk/go/buscompose"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
@@ -80,7 +81,7 @@ func (p *Part) writeSealed(res *Result, list []byte, users int, leftOut []string
|
||||
key = in.Loaded.Key
|
||||
}
|
||||
digest := delivery.UserListDigest(list)
|
||||
why := ""
|
||||
why, held := "", ""
|
||||
switch {
|
||||
case p.Objects == nil:
|
||||
// The tools: the process writes; the follow alone may change here, from a tool's view of the switch, and is not.
|
||||
@@ -94,6 +95,7 @@ func (p *Part) writeSealed(res *Result, list []byte, users int, leftOut []string
|
||||
// After take-over the list in force is never replaced by one composed past a failed read, or one that drops a user
|
||||
// of it for want of a hash: that user's module would be locked out of the bus (review of the switch).
|
||||
why = p.holdsBack(in, leftOut, bus)
|
||||
held = why
|
||||
case cur.Digest != digest || cur.SealedTo != delivery.SealingKeyID(key) || cur.Node != in.BusNode:
|
||||
object, err := delivery.SealUserList(list, key)
|
||||
if err == nil {
|
||||
@@ -109,6 +111,7 @@ func (p *Part) writeSealed(res *Result, list []byte, users int, leftOut []string
|
||||
}
|
||||
next.Digest, next.Object, next.SealedTo, next.Node = digest, delivery.UserListObjectName(digest), delivery.SealingKeyID(key), in.BusNode
|
||||
}
|
||||
p.sayHeld(held)
|
||||
if next.Digest == digest {
|
||||
next.Users, next.LeftOut = users, leftOut
|
||||
}
|
||||
@@ -143,6 +146,36 @@ func (p *Part) writeSealed(res *Result, list []byte, users int, leftOut []string
|
||||
"left_out": len(next.LeftOut), "follow": next.Follow, "sealed_to": next.Node})
|
||||
}
|
||||
|
||||
// sayHeld raises the warning condition mesh-grants.user-list-held while the list in force is kept (why, which words the pass's
|
||||
// `held: ...`), and clears it on the first pass that holds nothing back: the list advanced, or the reason went. The refusal
|
||||
// condition's way (sayRefused): said once per reason, again when the reason changes, and tried again when a say failed.
|
||||
func (p *Part) sayHeld(why string) {
|
||||
if p.Say == nil {
|
||||
return
|
||||
}
|
||||
p.holdMu.Lock()
|
||||
defer p.holdMu.Unlock()
|
||||
if p.heldKnown && why == p.heldSaid {
|
||||
return
|
||||
}
|
||||
c := asks.Condition{Key: HeldCondition, Subject: asks.Scope{Scope: "module", ID: "mesh-grants"}}
|
||||
if why == "" {
|
||||
c.State = asks.StateCleared
|
||||
} else {
|
||||
reason := strings.TrimPrefix(why, "held: ")
|
||||
c.Severity, c.State = asks.SeverityWarning, asks.StateRaised
|
||||
c.Summary, c.Headline, c.Needs = "the user list is held: "+reason, "The user list in force is held back", "a look at why"
|
||||
c.Explanation = asks.NeedsYou + "a look at why. The bus node keeps following the list written before, not the one composed now: " + reason
|
||||
c.Resolved = "a pass finds nothing to hold back and the list advances"
|
||||
}
|
||||
if p.Say(c) == nil {
|
||||
p.heldSaid, p.heldKnown = why, true
|
||||
}
|
||||
}
|
||||
|
||||
// HeldCondition is the key of the warning raised while a list in force is held back after take-over.
|
||||
const HeldCondition = "mesh-grants.user-list-held"
|
||||
|
||||
func sameSealed(a, b delivery.GrantsUserList) bool {
|
||||
a.At, b.At = b.At, a.At
|
||||
x, _ := json.Marshal(a)
|
||||
|
||||
Reference in New Issue
Block a user