mesh/delivery merged: source.main moved to novox/mesh-host@798d73eb11e9
mesh/check checking df13a83a9117 on 798d73eb11e9
The second sandbox fault of the same kind on mesh-host #200 (postgres's secret reader handed, the module not): the engine stripped the whole catalogue from the input it answers, so the sandbox composed the node's own modules from the indexer's source manifests while the bus reader composes them taken in from their builds (artifacts, templates archive texts, what is handed). Both sides now go through composer.SentInput: the node's own taken-in manifests and kept builds stay in the input, and the sandbox composes them over the cases'. An input past what one bus answer carries is refused in words. A conformance row composes one node through the bus reader and through the sandbox and requires the same declaration. Issue 1126. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
149 lines
7.0 KiB
Go
149 lines
7.0 KiB
Go
package composer
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"os"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
"github.com/novox/mesh-host/internal/compose"
|
|
"github.com/novox/mesh-host/internal/compose/frombus"
|
|
"github.com/novox/mesh-host/internal/compose/frombus/frombustest"
|
|
)
|
|
|
|
const demo = `{"module": "demo", "version": "1", "claims": [{"name": "node-files"}]}`
|
|
|
|
// withInput is r with in as its input, as a caller puts it on the wire: the request's shape carries the input as JSON.
|
|
func withInput(t *testing.T, r Request, in compose.Input) Request {
|
|
t.Helper()
|
|
raw, err := json.Marshal(in)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := json.Unmarshal(raw, &r.Input); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return r
|
|
}
|
|
|
|
// The seats a node's assigned modules claim are held there when the request says the holdings come from the claims.
|
|
func TestHoldingsComeFromTheClaims(t *testing.T) {
|
|
m, err := catalogue.Decode([]byte(demo))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got := HoldingsOf(map[string]catalogue.Manifest{"demo": m}, []compose.Node{{Name: "b", Assigned: []string{"demo"}}, {Name: "a", Assigned: []string{"demo"}}})
|
|
if len(got) != 2 || got[0].Node != "a" || got[0].Claim != "node-files" || got[0].Scope != "node" {
|
|
t.Fatalf("holdings = %+v", got)
|
|
}
|
|
}
|
|
|
|
// A request the command cannot run is refused, never answered as a verdict: a case named twice, an unknown depth.
|
|
func TestARequestItCannotRunIsRefused(t *testing.T) {
|
|
if _, err := Run(Request{V: Version, Cases: []Case{{Name: "a"}, {Name: "a"}}}); err == nil {
|
|
t.Error("a case named twice runs")
|
|
}
|
|
if _, err := Run(Request{V: Version, Depth: "deep"}); err == nil {
|
|
t.Error("an unknown depth runs")
|
|
}
|
|
// A case's own placement (issue 1126) composes over a node the input holds, and is refused for one it does not.
|
|
placed := withInput(t, Request{V: Version, Machines: []string{"a"}, Depth: DepthResolve,
|
|
Cases: []Case{{Name: "trunk", Manifests: map[string]Manifest{"demo": {Raw: json.RawMessage(demo)}}},
|
|
{Name: "change", Manifests: map[string]Manifest{"demo": {Raw: json.RawMessage(demo)}},
|
|
Placements: map[string]*delivery.Placement{"a": {Node: "a", Modules: []delivery.PlacedModule{{Module: "ghost"}}}}}}},
|
|
compose.Input{Nodes: []compose.Node{{Name: "a"}}})
|
|
if v, err := Run(placed); err != nil || !v.Cases[0].Machines["a"].Composes || v.Cases[1].Machines["a"].Composes ||
|
|
!strings.Contains(v.Cases[1].Machines["a"].Reason, "ghost") {
|
|
t.Errorf("a case composes its own placement: %+v, %v", v, err)
|
|
}
|
|
placed.Cases[1].Placements = map[string]*delivery.Placement{"z": {Node: "z"}}
|
|
if _, err := Run(placed); err == nil || !strings.Contains(err.Error(), "holds no node for") {
|
|
t.Errorf("a case placing a node the input lacks: %v", err)
|
|
}
|
|
// Issue 1126 (check of mesh-host #200): the module the mesh provides itself (the private network) comes from mesh-sdk,
|
|
// a second source beside the indexer's manifests the cases carry, and the input carries no catalogue: a node it is
|
|
// assigned to still composes, and what it provides is provided.
|
|
wg := delivery.ProvidedModules()[0]
|
|
provided := withInput(t, Request{V: Version, Machines: []string{"a"}, Depth: DepthResolve,
|
|
Cases: []Case{{Name: "change", Manifests: map[string]Manifest{"demo": {Raw: json.RawMessage(demo)}}}}},
|
|
compose.Input{Nodes: []compose.Node{{Name: "a", Assigned: []string{wg, "demo"}}}})
|
|
if v, err := Run(provided); err != nil || !v.Cases[0].Machines["a"].Composes {
|
|
t.Errorf("a node assigned the mesh's own module: %+v, %v", v, err)
|
|
}
|
|
// Issue 1126 (checks of mesh-host #200, fix-a-bug-met-twice): the sandbox composes one node from the same inputs as
|
|
// the bus reader, through one path. A mesh whose restic is built (its files from an archive) and handed to the
|
|
// provisioner composes n1 through frombus, and the request a node-engine answers for it (the input as the engine
|
|
// sends it, the indexer's manifests in the case) declares the same paths for n1.
|
|
t.Run("the sandbox composes a node as the bus reader does", func(t *testing.T) {
|
|
b, err := frombustest.Provisioned()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ctx, scope := context.Background(), frombus.Scope{Node: frombustest.Node}
|
|
read, err := frombus.Input(ctx, b, frombus.Mesh{}, scope)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
d, problems, err := compose.Compose(read, frombustest.Node)
|
|
if err != nil || len(problems) > 0 {
|
|
t.Fatalf("the bus reader's input composes n1: %v %v", err, problems)
|
|
}
|
|
want := pathsOf(d)
|
|
sent := SentInput(read, frombustest.Node)
|
|
cases := Case{Name: "change", Manifests: map[string]Manifest{}}
|
|
for name, m := range b.Module {
|
|
cases.Manifests[name] = Manifest{Raw: b.Manifests[m.Manifest]}
|
|
}
|
|
r := withInput(t, Request{V: Version, Machines: []string{frombustest.Node}, Declarations: true, Cases: []Case{cases}}, sent)
|
|
v, err := Run(r)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got := v.Cases[0].Machines[frombustest.Node]
|
|
if !got.Composes || !reflect.DeepEqual(got.Declared, want) {
|
|
t.Errorf("the sandbox composed n1 %+v; the bus reader declared %+v", got, want)
|
|
}
|
|
})
|
|
v, err := Run(Request{V: Version, Cases: []Case{{Name: "only-read", Manifests: map[string]Manifest{"demo": {Raw: json.RawMessage(demo)}}}}})
|
|
if err != nil || len(v.Cases) != 1 || !v.Cases[0].Manifests["demo"].Accepted || v.Cases[0].Machines != nil || v.SDK == "" {
|
|
t.Fatalf("a request with no machines: %+v, %v", v, err)
|
|
}
|
|
}
|
|
|
|
// The composer judges no manifest (novox/hq ADR 0578 §5): xterm's module.json from mesh-catalog at 835bf22b, whose
|
|
// config value falls back to "font-size", is read whatever list of predefined keys a request carries (a group touching
|
|
// xterm failed its composition on this, group check 18df81380db3); a module whose verdict in the input refuses it is
|
|
// held, composed as its stub, and named in the verdict's words.
|
|
func TestTheComposerTakesTheVerdictsOfItsInput(t *testing.T) {
|
|
raw, err := os.ReadFile("testdata/catalogue-835bf22b-xterm.module.json")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
r := Request{V: Version, Predefined: catalogue.PredefinedList{"something-else": {Type: "string", Description: "x"}},
|
|
Cases: []Case{{Name: "change", Manifests: map[string]Manifest{"xterm": {Raw: raw}}}}}
|
|
v, err := Run(r)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := v.Cases[0].Manifests["xterm"]; !got.Accepted {
|
|
t.Fatalf("xterm: %+v, want accepted", got)
|
|
}
|
|
r.Cases[0].Verdicts = map[string]delivery.ManifestVerdict{"xterm": {Module: "xterm", State: delivery.VerdictRefused,
|
|
Problems: []string{"falls back to a key not listed"}, Judge: "src2:j"}}
|
|
shelf, err := parse(r.Cases[0].Manifests, map[string]ManifestRead{}, r.Cases[0].Verdicts)
|
|
if err != nil || !shelf["xterm"].Stubbed {
|
|
t.Fatalf("the refused module is not held as its stub: %+v %v", shelf["xterm"], err)
|
|
}
|
|
if v, err = Run(r); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := v.Cases[0].Manifests["xterm"]; got.Accepted ||
|
|
!strings.Contains(got.Refused, "the indexer's judge (src2:j) refused the manifest of xterm: falls back to a key not listed") {
|
|
t.Fatalf("xterm refused by its verdict: %+v, want refused in the verdict's words", got)
|
|
}
|
|
}
|