A read the bus did not answer is tried again after a bound, never kept as an answer: the shadow's answer, the node's composition and the own-secret step (issue 1126)
Met twice on 2026-10-28: shanks kept 'want.shanks.brother could not be read ... no responders' as the own-secret step's last word, and ace kept 'the vault's own: not yet ... no responders' as its answer to seed-7, each until another input moved. Every bus read of the composer (manifests, builds, archives, served values, the vault's own and credentials, the have of what runs) is noted (BusFailed); an answer or a pass that met one is made again after a growing bound (5 s to 5 min), and a pass is not handed when the have of its sealed values did not answer. The own-secret step tells a read or an ask that did not answer (Retry, taken again) from a key absent or the vault's word.
This commit is contained in:
@@ -28,6 +28,8 @@ import (
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
|
||||
"github.com/novox/mesh-host/internal/selfcompose"
|
||||
)
|
||||
|
||||
// Verb is the vault's verb this step asks, with ReplaceRunning true in both steps.
|
||||
@@ -60,6 +62,9 @@ type Outcome struct {
|
||||
Name string
|
||||
Asked bool
|
||||
Said string
|
||||
// Retry says a read or an ask the bus did not answer stopped it: no answer, kept as none, and the step is to be
|
||||
// taken again after a bound (issue 1126, 2026-10-28: "want.shanks.brother could not be read ... no responders").
|
||||
Retry bool
|
||||
}
|
||||
|
||||
// Resync is the step of one node agent's run.
|
||||
@@ -107,7 +112,7 @@ func (r *Resync) Take(ctx context.Context, answers map[string]delivery.Candidate
|
||||
var out []Outcome
|
||||
for _, o := range drift {
|
||||
res := r.one(ctx, composes, o)
|
||||
if res.Said != "" {
|
||||
if res.Said != "" || res.Retry {
|
||||
out = append(out, res)
|
||||
}
|
||||
}
|
||||
@@ -125,7 +130,13 @@ func (r *Resync) one(ctx context.Context, composes string, o delivery.OwnValueDi
|
||||
return r.said(key, "not:composes", res, fmt.Sprintf("%s holds another value in the vault than runs here, and "+
|
||||
"nothing is handed: this node composes its own (%q), so the vault's value is the source", key, composes))
|
||||
}
|
||||
sealed, why := r.running(ctx, o)
|
||||
sealed, why, failed := r.running(ctx, o)
|
||||
if failed != nil {
|
||||
// The bus did not answer: no reason to keep, and taken again (Retry); said once per reason.
|
||||
res.Retry = true
|
||||
return r.said(key, "retry:"+why, res, fmt.Sprintf("%s holds another value in the vault than runs here, and "+
|
||||
"nothing is handed yet: %s; asked again", key, why))
|
||||
}
|
||||
if why != "" {
|
||||
return r.said(key, "not:"+why, res, fmt.Sprintf("%s holds another value in the vault than runs here, and "+
|
||||
"nothing is handed: %s", key, why))
|
||||
@@ -149,7 +160,13 @@ func (r *Resync) one(ctx context.Context, composes string, o delivery.OwnValueDi
|
||||
if ctx.Err() != nil {
|
||||
return res // the run ended: asked again in the next
|
||||
}
|
||||
said = fmt.Sprintf("%s: the vault's %s with replace_running refused or did not answer: %v", key, Verb, err)
|
||||
if selfcompose.BusFailed(err) {
|
||||
// The vault did not answer on the bus: no word of the vault's, so not kept as asked (Retry).
|
||||
res.Retry = true
|
||||
return r.said(key, "retry:ask", res, fmt.Sprintf("%s: the vault's %s with replace_running did not answer, "+
|
||||
"and is asked again: %v", key, Verb, err))
|
||||
}
|
||||
said = fmt.Sprintf("%s: the vault's %s with replace_running refused: %v", key, Verb, err)
|
||||
}
|
||||
// Once per running value, whatever the vault answered: a refusal is the vault's word, said once, and a new value
|
||||
// that runs asks again.
|
||||
@@ -176,8 +193,8 @@ func (r *Resync) said(key, reason string, res Outcome, why string) Outcome {
|
||||
}
|
||||
|
||||
// running is the sealed value of o in the composition that runs, when that composition is the one the module's want
|
||||
// names; or why it is not handed.
|
||||
func (r *Resync) running(ctx context.Context, o delivery.OwnValueDiffers) (string, string) {
|
||||
// names; or why it is not handed, with the error of a read the bus did not answer, which is no answer.
|
||||
func (r *Resync) running(ctx context.Context, o delivery.OwnValueDiffers) (sealed, why string, failed error) {
|
||||
var comp *delivery.Composed
|
||||
if r.env.Running != nil {
|
||||
for _, c := range r.env.Running() {
|
||||
@@ -189,39 +206,45 @@ func (r *Resync) running(ctx context.Context, o delivery.OwnValueDiffers) (strin
|
||||
}
|
||||
}
|
||||
if comp == nil {
|
||||
return "", o.Module + " runs no composition here"
|
||||
return "", o.Module + " runs no composition here", nil
|
||||
}
|
||||
wantKey, err := delivery.WantKey(r.env.Node, o.Module)
|
||||
if err != nil {
|
||||
return "", err.Error()
|
||||
return "", err.Error(), nil
|
||||
}
|
||||
raw, err := r.env.Get(ctx, delivery.AssignerWantsBucket, wantKey)
|
||||
if err != nil || raw == nil {
|
||||
return "", fmt.Sprintf("its want %s could not be read (%v)", wantKey, err)
|
||||
if err != nil {
|
||||
return "", fmt.Sprintf("its want %s could not be read (%v)", wantKey, err), err
|
||||
}
|
||||
if raw == nil {
|
||||
return "", fmt.Sprintf("it has no want %s", wantKey), nil
|
||||
}
|
||||
want, err := delivery.DecodeWant(raw)
|
||||
if err != nil {
|
||||
return "", fmt.Sprintf("its want %s is not one: %v", wantKey, err)
|
||||
return "", fmt.Sprintf("its want %s is not one: %v", wantKey, err), nil
|
||||
}
|
||||
if want.Self || want.Removed || want.Composed == "" {
|
||||
return "", fmt.Sprintf("its want %s names no composition of the controller's", wantKey)
|
||||
return "", fmt.Sprintf("its want %s names no composition of the controller's", wantKey), nil
|
||||
}
|
||||
// What runs is the composition the want names, as the module's have says: applied and running.
|
||||
haveKey, err := delivery.HaveKey(r.env.Node, o.Module)
|
||||
if err != nil {
|
||||
return "", err.Error()
|
||||
return "", err.Error(), nil
|
||||
}
|
||||
raw, err = r.env.Get(ctx, delivery.HaveBucket, haveKey)
|
||||
if err != nil || raw == nil {
|
||||
return "", fmt.Sprintf("its have %s could not be read (%v)", haveKey, err)
|
||||
if err != nil {
|
||||
return "", fmt.Sprintf("its have %s could not be read (%v)", haveKey, err), err
|
||||
}
|
||||
if raw == nil {
|
||||
return "", fmt.Sprintf("it has no have %s", haveKey), nil
|
||||
}
|
||||
have, err := delivery.DecodeHave(raw)
|
||||
if err != nil {
|
||||
return "", fmt.Sprintf("its have %s is not one: %v", haveKey, err)
|
||||
return "", fmt.Sprintf("its have %s is not one: %v", haveKey, err), nil
|
||||
}
|
||||
if have.Composed != want.Composed || have.Phase != delivery.PhaseRunning {
|
||||
return "", fmt.Sprintf("what runs (%s, %s) is not yet the composition its want names (%s)", have.Composed,
|
||||
have.Phase, want.Composed)
|
||||
have.Phase, want.Composed), nil
|
||||
}
|
||||
// On a node the resource's id carries its module's name (`only-office.needs-jwt`); delivery.OwnNeedName reads it.
|
||||
id := o.Module + "." + catalogue.NeedID(o.Name)
|
||||
@@ -237,11 +260,11 @@ func (r *Resync) running(ctx context.Context, o delivery.OwnValueDiffers) (strin
|
||||
continue
|
||||
}
|
||||
if s, ok := body.Sealed.(string); ok && s != "" {
|
||||
return s, ""
|
||||
return s, "", nil
|
||||
}
|
||||
return "", fmt.Sprintf("its resource %s carries no value sealed to this machine", id)
|
||||
return "", fmt.Sprintf("its resource %s carries no value sealed to this machine", id), nil
|
||||
}
|
||||
return "", fmt.Sprintf("what runs holds no resource %s", id)
|
||||
return "", fmt.Sprintf("what runs holds no resource %s", id), nil
|
||||
}
|
||||
|
||||
// hand asks the vault's two steps with value, and checks what the vault holds after by its fingerprint.
|
||||
|
||||
@@ -30,12 +30,18 @@ func TestTheNodeHandsTheVaultWhatRunsOfADriftedOwnSecret(t *testing.T) {
|
||||
refuse string // the vault's refusal
|
||||
asks int // the vault's verb asked, both passes
|
||||
says string
|
||||
// Issue 1126, 2026-10-28: a read or an ask the bus did not answer is no answer: taken again (Retry), never kept.
|
||||
wantRead string // the want's read: "" answers it, "fails" the bus does not answer, "absent" no key
|
||||
retry bool
|
||||
}{
|
||||
"applies the controller's, runs its want": {delivery.ComposesController, digest, "", 2, "generation 1 replaced by 2"},
|
||||
"composes its own": {delivery.ComposesOwn, digest, "", 0, "composes its own"},
|
||||
"not decided yet": {"", digest, "", 0, ""},
|
||||
"runs not yet what its want names": {delivery.ComposesController, "sha256:" + strings.Repeat("c", 64), "", 0, "not yet the composition"},
|
||||
"the vault refuses": {delivery.ComposesController, digest, "switched off", 1, "switched off"},
|
||||
"applies the controller's, runs its want": {delivery.ComposesController, digest, "", 2, "generation 1 replaced by 2", "", false},
|
||||
"composes its own": {delivery.ComposesOwn, digest, "", 0, "composes its own", "", false},
|
||||
"not decided yet": {"", digest, "", 0, "", "", false},
|
||||
"runs not yet what its want names": {delivery.ComposesController, "sha256:" + strings.Repeat("c", 64), "", 0, "not yet the composition", "", false},
|
||||
"the vault refuses": {delivery.ComposesController, digest, "switched off", 1, "switched off", "", false},
|
||||
"the vault does not answer": {delivery.ComposesController, digest, "nats: no responders available for request", 2, "asked again", "", true},
|
||||
"the want's read does not answer": {delivery.ComposesController, digest, "", 0, "asked again", "fails", true},
|
||||
"it has no want": {delivery.ComposesController, digest, "", 0, "it has no want", "absent", false},
|
||||
} {
|
||||
var asked []map[string]any
|
||||
var said []string
|
||||
@@ -50,6 +56,10 @@ func TestTheNodeHandsTheVaultWhatRunsOfADriftedOwnSecret(t *testing.T) {
|
||||
},
|
||||
Get: func(_ context.Context, bucket, key string) ([]byte, error) {
|
||||
switch {
|
||||
case bucket == delivery.AssignerWantsBucket && key == "want.novox.only-office" && row.wantRead == "fails":
|
||||
return nil, errors.New("the bucket mesh-delivery-assigner_wants: nats: no responders available for request")
|
||||
case bucket == delivery.AssignerWantsBucket && key == "want.novox.only-office" && row.wantRead == "absent":
|
||||
return nil, nil
|
||||
case bucket == delivery.AssignerWantsBucket && key == "want.novox.only-office":
|
||||
return want, nil
|
||||
case bucket == delivery.HaveBucket && key == "novox.only-office":
|
||||
@@ -90,7 +100,10 @@ func TestTheNodeHandsTheVaultWhatRunsOfADriftedOwnSecret(t *testing.T) {
|
||||
if len(asked) != row.asks {
|
||||
t.Errorf("%s: the vault asked %d times, want %d: %v", name, len(asked), row.asks, asked)
|
||||
}
|
||||
if len(again) != 0 {
|
||||
if retry := len(first) == 1 && first[0].Retry; retry != row.retry {
|
||||
t.Errorf("%s: taken again %v, want %v: %+v", name, retry, row.retry, first)
|
||||
}
|
||||
if row.retry && (len(again) != 1 || !again[0].Retry || again[0].Said != "") || !row.retry && len(again) != 0 {
|
||||
t.Errorf("%s: the same answer did or said something again: %+v", name, again)
|
||||
}
|
||||
all := strings.Join(said, "\n")
|
||||
|
||||
Reference in New Issue
Block a user