Say what a node held on the way when it is refused whole, so a held module's own refusal is never hidden (issue 774)
At 19:30 on 2026-10-30 ace, g14 and shanks refused every module with "node-tools … its build produced 0 bundle(s)": the refusal of node-tools' stand-in, while why node-tools could not be composed was dropped with the hold. Quick fix: log held in mesh-host before marking. Thorough fix: Compose's node-wide error carries "(held before: <module>: <why>; …)", so the engine's journal, every have's detail and any caller see the cause. Chose thorough.
This commit is contained in:
@@ -429,6 +429,17 @@ func input(ctx context.Context, r Reader, mesh Mesh, s Scope, refuse func(module
|
|||||||
// elsewhere, a stand-in for the node's own) refuses the node, err.
|
// elsewhere, a stand-in for the node's own) refuses the node, err.
|
||||||
func Compose(ctx context.Context, r Reader, mesh Mesh, s Scope, fps map[string]string) (out map[string]delivery.Composed, skipped []string, held map[string]string, err error) {
|
func Compose(ctx context.Context, r Reader, mesh Mesh, s Scope, fps map[string]string) (out map[string]delivery.Composed, skipped []string, held map[string]string, err error) {
|
||||||
held = map[string]string{}
|
held = map[string]string{}
|
||||||
|
// A node refused whole says what it held on the way, since the refusal can be of a held module's stub (issue 774,
|
||||||
|
// 2026-10-30 19:30: node-tools held, then "its build produced 0 bundle(s)" with its own refusal unsaid).
|
||||||
|
defer func() {
|
||||||
|
if err != nil && len(held) > 0 {
|
||||||
|
var said []string
|
||||||
|
for _, m := range sortedKeys(held) {
|
||||||
|
said = append(said, m+": "+held[m])
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%w (held before: %s)", err, strings.Join(said, "; "))
|
||||||
|
}
|
||||||
|
}()
|
||||||
in, err := input(ctx, r, mesh, s, func(module string, err error) { held[module] = err.Error() })
|
in, err := input(ctx, r, mesh, s, func(module string, err error) { held[module] = err.Error() })
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, held, err
|
return nil, nil, held, err
|
||||||
|
|||||||
@@ -138,6 +138,8 @@ func TestInput(t *testing.T) {
|
|||||||
// saying why (answer_rows_test.go).
|
// saying why (answer_rows_test.go).
|
||||||
t.Run("every refusal, and an old build left to the node", answerNamesEveryRefusalAndLeavesAnOldBuildToTheNode)
|
t.Run("every refusal, and an old build left to the node", answerNamesEveryRefusalAndLeavesAnOldBuildToTheNode)
|
||||||
t.Run("no store reached says why", noStoreReachedSaysWhy)
|
t.Run("no store reached says why", noStoreReachedSaysWhy)
|
||||||
|
// Issue 774: a node refused whole says what it held on the way.
|
||||||
|
t.Run("a node refused whole says what it held", aNodeRefusedWholeSaysWhatItHeld)
|
||||||
t.Run("another node not resolved is named", otherNodeNotResolvedIsNamed)
|
t.Run("another node not resolved is named", otherNodeNotResolvedIsNamed)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
xterm, err := os.ReadFile("../../catalogue/testdata/catalogue-835bf22b-xterm.module.json")
|
xterm, err := os.ReadFile("../../catalogue/testdata/catalogue-835bf22b-xterm.module.json")
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package frombus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||||
|
)
|
||||||
|
|
||||||
|
// aNode is a bus with n1 the hub, each of manifests placed on it and held at the indexer, in the order given.
|
||||||
|
func aNode(t *testing.T, manifests ...string) (*bus, map[string]string) {
|
||||||
|
t.Helper()
|
||||||
|
p := delivery.Placement{Node: "n1", Decided: delivery.NodeDecided{Hub: true, Address: "10.77.0.1"}}
|
||||||
|
b := &bus{records: map[string]delivery.NodeRecord{"n1": {Node: "n1", TunnelKey: publicKey(t), SealingKey: "sk", Capabilities: []string{"systemd"}}},
|
||||||
|
modules: map[string]delivery.Module{}, manifests: map[string][]byte{}, builds: map[string]*delivery.BuilderBuild{}}
|
||||||
|
fps := map[string]string{}
|
||||||
|
for i, raw := range manifests {
|
||||||
|
module := strings.SplitN(strings.TrimPrefix(raw, `{"module":"`), `"`, 2)[0]
|
||||||
|
p.Modules = append(p.Modules, delivery.PlacedModule{Module: module})
|
||||||
|
b.modules[module] = delivery.Module{Manifest: delivery.ManifestDigest([]byte(raw))}
|
||||||
|
b.manifests[delivery.ManifestDigest([]byte(raw))] = []byte(raw)
|
||||||
|
fps[module] = "src2:" + strings.Repeat(string(rune('1'+i)), 64)
|
||||||
|
}
|
||||||
|
b.placements = map[string]delivery.Placement{"n1": p}
|
||||||
|
return b, fps
|
||||||
|
}
|
||||||
|
|
||||||
|
// Issue 774, 2026-10-30 19:30: a node refused whole after holding a module says what it held and why, since the last
|
||||||
|
// refusal can be of the held module's stub (node-tools held, then "its build produced 0 bundle(s)", its own why unsaid).
|
||||||
|
func aNodeRefusedWholeSaysWhatItHeld(t *testing.T) {
|
||||||
|
refused := `{"module":"power","version":"1","items":[{"collection":"contribution:node-bar:block","attributes":{"shows":"state"},"template":"files/a"}]}`
|
||||||
|
needs := `{"module":"lamp","version":"1","requires":["nothing-provides-this"]}`
|
||||||
|
b, fps := aNode(t, refused, needs)
|
||||||
|
_, _, held, err := Compose(context.Background(), b, Mesh{}, Scope{Node: "n1"}, fps)
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "nothing provides") || !strings.Contains(err.Error(), `held before: power: `) ||
|
||||||
|
!strings.Contains(err.Error(), `"shows" is none of them`) {
|
||||||
|
t.Errorf("the node refused whole with %v (held %v), want lamp's refusal and power held before, with its why", err, held)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user