Test the place half of the rendering over an Input (issue 1363). Fixtures from mesh-control 6c2a8d62 sendable_integration_test.go (the store, the given port) and local_network_test.go (the networks); written after the code, each rule broken in turn and a test seen to fail. Unit level 1.05 s of 2 s.

This commit is contained in:
2026-10-20 06:35:09 +00:00
parent d3436abd82
commit 7823403869
+323
View File
@@ -0,0 +1,323 @@
package compose
import (
"errors"
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/overlay"
)
// aWebModule publishes one port in the short form, which the mesh is free to move (MachineSide).
func aWebModule() catalogue.Manifest {
return catalogue.Manifest{Module: "web", Version: "1",
Listens: []catalogue.Listening{{Name: "http", Port: 8080, From: catalogue.FromMesh}},
Resources: []map[string]any{{"id": "server", "type": "container", "name": "web", "ports": []any{"8080"},
"image": "registry.example/web@sha256:" + strings.Repeat("a", 64)}}}
}
// aStoreModule is mesh-controller's sendable_integration_test.go TestConsumersAreToldTheGivenPort store (cmd/mesh-controller
// at 6c2a8d62): publishing 5432:5432, offering a database to the mesh.
func aStoreModule() catalogue.Manifest {
return catalogue.Manifest{Module: "store", Version: "1",
Provides: []catalogue.Offer{{Name: "database", Scope: catalogue.ScopeMesh}},
Listens: []catalogue.Listening{{Port: 5432, From: catalogue.FromMesh}},
Guards: []int{5432},
Resources: []map[string]any{{"id": "server", "type": "container", "name": "mesh-store",
"ports": []any{"5432:5432"},
"image": "registry.example/pg@sha256:" + strings.Repeat("b", 64)}}}
}
func placed(t *testing.T, in Input, node string) Placed {
t.Helper()
got, err := Places(in, node)
if err != nil {
t.Fatalf("%v", err)
}
return got
}
// 1363/AC1 and AC2: a port the Input holds as allocated on the node is the one composed; with none, no number is invented: the
// manifest's own is composed and the port is named foreseen, as the Foreseeing mode does.
func TestAnAllocatedPortIsComposedAndAnUnallocatedOneIsNamedForeseenNotInvented(t *testing.T) {
mk := func() Input {
in := aMesh(t)
in.Catalogue["web"] = aWebModule()
nodeOf(t, &in, "laptop").Assigned = append(nodeOf(t, &in, "laptop").Assigned, "web")
return in
}
in := mk()
got := placed(t, in, "laptop")
if p := got.Rendering.Ports["web"][8080]; p != 8080 {
t.Errorf("a port nobody allocated is composed as %d, not as the manifest's own", p)
}
if len(got.ForeseenPorts) != 1 || got.ForeseenPorts[0] != "web:8080" {
t.Errorf("foreseen ports are %v", got.ForeseenPorts)
}
nodeOf(t, &in, "laptop").Ports = map[string]map[int]int{"web": {8080: 20123}}
got = placed(t, in, "laptop")
if p := got.Rendering.Ports["web"][8080]; p != 20123 || len(got.ForeseenPorts) != 0 {
t.Errorf("the allocated port is composed as %d (foreseen %v)", p, got.ForeseenPorts)
}
// A port the module does not publish is not the mesh's to move, so it is never foreseen.
m := aWebModule()
m.Resources = nil
in.Catalogue["web"] = m
nodeOf(t, &in, "laptop").Ports = nil
got = placed(t, in, "laptop")
if got.Rendering.Ports["web"][8080] != 8080 || len(got.ForeseenPorts) != 0 {
t.Errorf("an unpublished port: %v foreseen %v", got.Rendering.Ports, got.ForeseenPorts)
}
unchanged(t, mk, func(in Input) { placed(t, in, "laptop") })
}
// 1363/AC3: a port the operator gave is used, over the allocated one, and a consumer elsewhere is told it
// (mesh-controller sendable_integration_test.go TestConsumersAreToldTheGivenPort).
func TestAPortTheOperatorGaveIsUsedAndNotReallocated(t *testing.T) {
in := aMesh(t)
in.Catalogue["store"] = aStoreModule()
in.Catalogue["app"] = catalogue.Manifest{Module: "app", Version: "1", Requires: []string{"database"}}
anchor := nodeOf(t, &in, "anchor")
anchor.Assigned = append(anchor.Assigned, "store")
nodeOf(t, &in, "laptop").Assigned = append(nodeOf(t, &in, "laptop").Assigned, "app")
anchor.Ports = map[string]map[int]int{"store": {5432: 20999}}
anchor.Settings = map[string][]catalogue.Layer{"store": {{From: "anchor",
Placement: map[string]any{catalogue.PortsSetting: map[string]any{"5432": 5433}}}}}
got := placed(t, in, "anchor")
if p := got.Rendering.Ports["store"][5432]; p != 5433 {
t.Errorf("the store is on %d, not the port it was given", p)
}
if g := got.Rendering.Given["store"][5432]; g != 5433 {
t.Errorf("the given port is %d", g)
}
if len(got.ForeseenPorts) != 0 {
t.Errorf("a given port is foreseen: %v", got.ForeseenPorts)
}
resolved, err := Resolve(in, "laptop")
if err != nil {
t.Fatal(err)
}
if told := need(t, resolved.Plan, "app", "database").Serves["port"]; told != 5433 {
t.Errorf("the consumer is told the store is on %v", told)
}
// One holder per machine port across the node's modules: two given the same refused, in the controller's words.
in.Catalogue["store2"] = func() catalogue.Manifest {
m := aStoreModule()
m.Module, m.Provides = "store2", nil
m.Resources = []map[string]any{{"id": "server", "type": "container", "name": "other", "ports": []any{"5432:5432"},
"image": "registry.example/pg@sha256:" + strings.Repeat("c", 64)}}
return m
}()
anchor.Assigned = append(anchor.Assigned, "store2")
anchor.Settings["store2"] = []catalogue.Layer{{From: "anchor",
Placement: map[string]any{catalogue.PortsSetting: map[string]any{"5432": 5433}}}}
_, err = Places(in, "anchor")
if err == nil || !errors.Is(err, ErrPortTaken) || !strings.Contains(err.Error(), "store and store2 are both given machine port 5433 on anchor") &&
!strings.Contains(err.Error(), "store2 and store are both given machine port 5433 on anchor") {
t.Fatalf("two modules given one machine port: %v", err)
}
}
// 1363/AC4: the seats held and taken reach the Rendering: where each mesh-scoped seat's holder put its ports, from the node's
// given and assigned ports, never the manifest's own; and on an adopted node a holder assigned but not taken counts only by its
// given ports (novox/hq ADR 0100).
func TestSeatsHeldAndTakenReachTheRendering(t *testing.T) {
in := aMesh(t)
holder := aStoreModule()
holder.Claims = []catalogue.Claim{{Name: "mesh-store", Scope: catalogue.ScopeMesh}}
in.Catalogue["store"] = holder
anchor := nodeOf(t, &in, "anchor")
anchor.Assigned = append(anchor.Assigned, "store")
// The shelf is read whole, in this node's set or not: laptop, running none, is told where the anchor's store is only by the
// settings and ports of its own machine, which it has none of.
anchor.Ports = map[string]map[int]int{"store": {5432: 20999}}
got := placed(t, in, "anchor")
if at := got.Rendering.Seats["mesh-store"][5432]; at != 20999 {
t.Errorf("the seat's holder is told to be on %d", at)
}
if len(placed(t, in, "laptop").Rendering.Seats) != 0 {
t.Errorf("laptop is told of a holder it has no port for: %v", placed(t, in, "laptop").Rendering.Seats)
}
// Adopted, and the holder assigned but not taken: the found container's ports, not the mesh's assignment.
anchor.Adopted = true
if len(placed(t, in, "anchor").Rendering.Seats) != 0 {
t.Errorf("an untaken holder on an adopted node counts by the mesh's assignment: %v", placed(t, in, "anchor").Rendering.Seats)
}
anchor.Taken = []string{"store"}
got = placed(t, in, "anchor")
if at := got.Rendering.Seats["mesh-store"][5432]; at != 20999 || !got.Rendering.Adopted || !got.Rendering.Taken["store"] {
t.Errorf("a taken holder: seats %v adopted %v taken %v", got.Rendering.Seats, got.Rendering.Adopted, got.Rendering.Taken)
}
}
// 1363/AC4: local networks and outward links (mesh-controller local_network_test.go TestNodeShowsWhichAttachedNetworksCountAndWhy,
// the values): a private network on a link facing outside counts, a public subnet and the private network's own link never do; a
// machine that said none judges to nothing.
func TestLocalNetworksAndOutwardLinksReachTheRendering(t *testing.T) {
in := aMesh(t)
laptop := nodeOf(t, &in, "laptop")
laptop.OutwardLinks = []string{"eth0", "eth1"}
laptop.Networks = []catalogue.AttachedNetwork{
{Link: "eth0", Network: "192.168.1.10/24"}, {Link: "eth1", Network: "203.0.113.7/24"}, {Link: overlay.Interface, Network: "10.10.0.4/24"}}
laptop.NetworksSaid = true
got := placed(t, in, "laptop").Rendering
if len(got.LocalNetworks) != 1 || got.LocalNetworks[0] != "192.168.1.0/24" {
t.Errorf("the local networks are %v", got.LocalNetworks)
}
if len(got.OutwardLinks) != 2 || got.OutwardLinks[0] != "eth0" {
t.Errorf("the outward links are %v", got.OutwardLinks)
}
laptop.NetworksSaid = false
if got := placed(t, in, "laptop").Rendering; len(got.LocalNetworks) != 0 {
t.Errorf("a machine that never reported has local networks %v", got.LocalNetworks)
}
}
// 1363/AC4: accounts, the names of the mesh, the mesh range and the operator's export reach the Rendering.
func TestAccountsNamesAndTheOperatorsExportReachTheRendering(t *testing.T) {
in := aMesh(t)
nodeOf(t, &in, "anchor").Account = "jochens"
in.OperatorExport = &catalogue.KeptExport{}
in.OverlayCIDR = "10.99.0.0/16"
got := placed(t, in, "anchor").Rendering
if got.Accounts["anchor"] != "jochens" || len(got.Accounts) != 1 {
t.Errorf("accounts are %v", got.Accounts)
}
if got.Names["anchor.internal"] != "10.77.0.1" || got.Names["laptop.internal"] != "10.77.0.2" || len(got.Names) != 2 {
t.Errorf("names are %v", got.Names)
}
if len(got.Machines) != 2 || got.Machines["laptop.internal"] != "10.77.0.2" {
t.Errorf("machines are %v", got.Machines)
}
if len(got.Mesh) != 2 || got.Mesh[0] != "10.77.0.1" || got.Mesh[1] != "10.77.0.2" {
t.Errorf("the mesh's addresses are %v", got.Mesh)
}
if got.MeshRange != "10.99.0.0/16" || got.Suffix != "internal" || got.TunnelInterface != overlay.Interface {
t.Errorf("range %q suffix %q tunnel %q", got.MeshRange, got.Suffix, got.TunnelInterface)
}
if got.Kept != nil {
t.Error("the operator's export reaches a node where nothing keeps it")
}
keeper := catalogue.Manifest{Module: "keeper", Version: "1", Keeps: "/var/lib/keeper/export.json"}
in.Catalogue["keeper"] = keeper
nodeOf(t, &in, "anchor").Assigned = append(nodeOf(t, &in, "anchor").Assigned, "keeper")
if got := placed(t, in, "anchor").Rendering; got.Kept != in.OperatorExport {
t.Errorf("a module that keeps the export is not given it: %v", got.Kept)
}
// And the names of a machine that left the network leave with it.
nodeOf(t, &in, "laptop").Assigned = nil
if got := placed(t, in, "anchor").Rendering; len(got.Names) != 1 || got.Names["laptop.internal"] != "" {
t.Errorf("a machine off the network is named: %v", got.Names)
}
}
// The names the operator gave to peers of an adopted tunnel answer until they enrol (novox/hq issue 112); a node of the mesh owns
// its name.
func TestACarriedPeerIsNamedUntilItEnrols(t *testing.T) {
in := aMesh(t)
in.CarriedPeers = []CarriedPeer{{PublicKey: "K1", Address: "192.0.2.5", Named: "printer"},
{PublicKey: "K2", Address: "192.0.2.6", Named: "laptop"}, {PublicKey: "K3", Address: "192.0.2.7", Named: "late", EnrolledAs: "late"}}
got := placed(t, in, "anchor").Rendering
if got.Names["printer.internal"] != "192.0.2.5" || got.Names["laptop.internal"] != "10.77.0.2" || got.Names["late.internal"] != "" {
t.Errorf("names are %v", got.Names)
}
}
// A node-engine older than a contract is not sent the field it would refuse (novox/hq ADR 0240 and after).
func TestTheNodeEnginesContractGatesWhatItIsSent(t *testing.T) {
in := aMesh(t)
laptop := nodeOf(t, &in, "laptop")
if r := placed(t, in, "laptop").Rendering; r.ReadsHealth || r.JudgesRoot || r.ReadsBusy || r.Hands {
t.Errorf("an engine that never stated anything reads %+v", r)
}
laptop.Stated, laptop.Contract = true, ReadinessContract
if r := placed(t, in, "laptop").Rendering; !r.ReadsHealth || r.JudgesRoot || r.ReadsBusy || r.Hands {
t.Errorf("an engine at the readiness contract reads %+v", r)
}
laptop.Contract = HandedContract
if r := placed(t, in, "laptop").Rendering; !r.ReadsHealth || !r.JudgesRoot || !r.ReadsBusy || !r.Hands {
t.Errorf("an engine at the handed contract reads %+v", r)
}
}
// The artifact store as a node reaches it: over the private network by its holder's name and the port it put it on, or on loopback
// for the holder alone while nothing is on the network (novox/hq 04-ISSUES/102).
func TestTheArtifactStoreIsReachedOverTheNetworkOrOnLoopbackForItsHolder(t *testing.T) {
in := aMesh(t)
in.Catalogue["artifacts"] = catalogue.Manifest{Module: "artifacts", Version: "1",
Serves: map[string]map[string]any{catalogue.ArtifactStoreProvision: {"port": 5100}}}
anchor := nodeOf(t, &in, "anchor")
anchor.Assigned = append(anchor.Assigned, "artifacts")
if got := placed(t, in, "laptop").Rendering.ArtifactStore; got != "anchor.internal:5100" {
t.Errorf("laptop reaches the store at %q", got)
}
nodeOf(t, &in, "laptop").Assigned = nil
anchor.Assigned = []string{"artifacts"} // off the network: the holder alone reaches it
if got := placed(t, in, "anchor").Rendering.ArtifactStore; got != "127.0.0.1:5100" {
t.Errorf("the holder reaches the store at %q", got)
}
if got := placed(t, in, "laptop").Rendering.ArtifactStore; got != "" {
t.Errorf("a machine off the network reaches the store at %q", got)
}
in.Built = []string{"mesh-sdk"}
if got := placed(t, in, "anchor").Rendering.Built; !got["mesh-sdk"] {
t.Errorf("built is %v", got)
}
}
// A zone a module declares is placed where the mesh put it: settled from that node's settings, at the node's private address, on
// the port the answering listen is published on there (novox/hq ADR 0199); the same zone declared twice is refused, by name.
func TestAZoneIsPlacedWhereTheMeshPutItAndTheSameZoneTwiceIsRefused(t *testing.T) {
in := aMesh(t)
lab := catalogue.Manifest{Module: "lab", Version: "1", Zone: &catalogue.Zone{Name: "${setting:zone}", Listen: "dns"},
Listens: []catalogue.Listening{{Name: "dns", Port: 53, Protocol: "udp", From: catalogue.FromMesh}}}
in.Catalogue["lab"] = lab
anchor := nodeOf(t, &in, "anchor")
anchor.Assigned = append(anchor.Assigned, "lab")
anchor.Settings = map[string][]catalogue.Layer{"lab": {{From: "anchor", Values: map[string]any{"zone": "Lab.Example."}}}}
anchor.Ports = map[string]map[int]int{"lab": {53: 5353}}
zones := placed(t, in, "laptop").Rendering.Zones
if len(zones) != 1 || zones[0] != (catalogue.ZoneAt{Zone: "lab.example", Node: "anchor", Module: "lab", Address: "10.77.0.1", Port: 5353}) {
t.Fatalf("zones are %+v", zones)
}
laptop := nodeOf(t, &in, "laptop")
laptop.Assigned = append(laptop.Assigned, "lab")
laptop.Settings = map[string][]catalogue.Layer{"lab": {{From: "laptop", Values: map[string]any{"zone": "lab.example"}}}}
if _, err := Places(in, "laptop"); err == nil || !strings.Contains(err.Error(), "the mesh's zones cannot be forwarded") {
t.Fatalf("one zone declared twice: %v", err)
}
}
// Every holder of a replicated seat is listed by internal name at its private address (novox/hq ADR 0223): the holders on record,
// and only the sole claimant when there are none; a holder off the private network is left out.
func TestEveryHolderOfAReplicatedSeatIsListedOnTheNetwork(t *testing.T) {
in := aMesh(t)
resolver := catalogue.Manifest{Module: "resolver", Version: "1",
Provides: []catalogue.Offer{{Name: "wildcard-resolution", Scope: catalogue.ScopeMesh}},
Claims: []catalogue.Claim{{Name: "mesh-dns-resolver", Scope: catalogue.ScopeMesh}}}
in.Catalogue["resolver"] = resolver
anchor, laptop := nodeOf(t, &in, "anchor"), nodeOf(t, &in, "laptop")
anchor.Assigned = append(anchor.Assigned, "resolver")
// One claimant and nothing on record: the sole claimant is listed.
if got := placed(t, in, "laptop").Rendering.Holders["mesh-dns-resolver"]; len(got) != 1 || got["anchor.internal"] != "10.77.0.1" {
t.Errorf("the sole claimant: %v", got)
}
// Both on record: both are listed (two claimants with nothing on record would be refused at resolution).
laptop.Assigned = append(laptop.Assigned, "resolver")
in.Holdings = []catalogue.Held{
{Claim: "mesh-dns-resolver", Scope: catalogue.ScopeMesh, Node: "anchor", Module: "resolver"},
{Claim: "mesh-dns-resolver", Scope: catalogue.ScopeMesh, Node: "laptop", Module: "resolver"}}
got := placed(t, in, "laptop").Rendering.Holders["mesh-dns-resolver"]
if len(got) != 2 || got["anchor.internal"] != "10.77.0.1" || got["laptop.internal"] != "10.77.0.2" {
t.Errorf("both holders on record: %v", got)
}
// A holder off the private network is left out: a resolver named at an address nothing answers waits out its timeout.
in.Nodes = append(in.Nodes, Node{Name: "ace", Place: Place{Address: "10.77.0.3"}, Assigned: []string{"resolver"}})
in.Holdings = append(in.Holdings, catalogue.Held{Claim: "mesh-dns-resolver", Scope: catalogue.ScopeMesh, Node: "ace", Module: "resolver"})
if got := placed(t, in, "laptop").Rendering.Holders["mesh-dns-resolver"]; len(got) != 2 || got["ace.internal"] != "" {
t.Errorf("a holder off the network is listed: %v", got)
}
}