Check route-proxy's composed targets over the live shapes in an existing test's place, and pin the mesh-sdk that keeps unassigned bare ports (issue 774, research 072 row 34)
mesh/delivery merged: source.main moved to novox/mesh-controller@dc90b3f5bd13
mesh/delivery-group group fix/route-ports merged: every member is merged
mesh/check failed: integration: mesh-vault/cmd/mesh-vault.TestTheVaultWholeMintsOnTheBusWhoeverHoldsTheLease: never: the account minted under the cont…

The table covers gitea and grafana moved by the placement, influxdb's bare port, mailu's and
photos' fixed mappings and home-assistant on the host's network; it replaces the loopback test it
subsumes, so route-proxy's test count does not grow.

The controller, NATS-native: no piece moves; route-proxy only.
This commit is contained in:
2026-10-31 13:22:00 +01:00
parent 14d10051e5
commit 0e9c4c8299
4 changed files with 81 additions and 85 deletions
@@ -1,71 +0,0 @@
package main
import (
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/provisions"
)
// A route is dialled at the machine port the module's placement assigned, never its software's own (issue 774, research
// 072 row 34; 2026-10-31: gitea's 3000 for git, published at 20000, and grafana's 3000 on another machine, published at
// 20012, answered 502): over loopback when the module runs beside the proxy, at its machine's address otherwise. The
// plan is composed by the SDK as the seat mesh-provisioner composes it, and read as this proxy reads it; a port the
// placement assigned none is refused there, so no plan names it.
func TestAComposedPlanIsDialledAtTheAssignedPort(t *testing.T) {
proxy := catalogue.Manifest{Module: "route-proxy", Version: "1",
Provides: []catalogue.Offer{{Name: "route", Scope: catalogue.ScopeMesh, AnsweredBy: catalogue.AnsweredByHelper}}}
world := catalogue.World{Offered: map[string][]catalogue.Provider{
"route": {{Node: "anchor", At: "anchor.internal", Module: "route-proxy"}}}}
for _, c := range []struct {
name, node, at, module string
ports map[int]int
target, refused string
}{
{"a module beside the proxy", "anchor", "anchor.internal", "gitea", map[int]int{3000: 20000},
"http://127.0.0.1:20000", ""},
{"a module on another machine", "home", "home.internal", "grafana", map[int]int{3000: 20012},
"http://home.internal:20012", ""},
{"a module whose port the placement assigned none", "home", "home.internal", "grafana", nil,
"", "grafana contributing to route: its port 3000 on home has no machine port assigned"},
} {
module := catalogue.Manifest{Module: c.module, Version: "1", Requires: []string{"route"},
Listens: []catalogue.Listening{{Name: "web", Port: 3000, Protocol: "tcp", From: "mesh"}},
Contributes: map[string]map[string]any{"route": {"label": c.module, "endpoint": "web"}}}
node := catalogue.Node{Name: c.node, At: c.at, PublicDomain: "example.org",
Capabilities: map[string]bool{"container-runtime": true}}
r, err := catalogue.Resolve(map[string]catalogue.Manifest{module.Module: module, proxy.Module: proxy},
[]string{module.Module}, node, world)
if err != nil {
t.Fatalf("%s: %v", c.name, err)
}
r.Account = "op"
for i, n := range r.Needs {
r.Needs[i].AnsweredByHelper = n.Name == "route" // as the plan sets it (compose's answeredByHelper)
}
composed, err := r.Compose(catalogue.Rendering{Hands: true, Provisioner: "mesh-provisioner-client",
ArtifactStore: "store:5000", Planned: true, Ports: map[string]map[int]int{c.module: c.ports}})
if c.refused != "" {
if err == nil || !strings.Contains(err.Error(), c.refused) {
t.Errorf("%s: composed with %v, want refused with %q", c.name, err, c.refused)
}
continue
}
if err != nil {
t.Fatalf("%s: %v", c.name, err)
}
value, err := provisions.EncodePlan(provisions.Plan{Node: c.node, Module: c.module, Plan: composed.Plans[c.module]})
if err != nil {
t.Fatalf("%s: %v", c.name, err)
}
given, refused, err := routesOfPlan("anchor", c.node+"."+c.module, value)
if err != nil || len(refused) != 0 {
t.Fatalf("%s: the plan gives %v, refused %v (%v)", c.name, given, refused, err)
}
routes, _ := routesOf(given)
if got := targetOf(routes, c.module+".example.org"); got != c.target {
t.Errorf("%s: dialled at %q, want %q (routes %v)", c.name, got, c.target, routes)
}
}
}
@@ -10,6 +10,9 @@ import (
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/provisions"
"git.novox.be/novox/mesh-controller/modules/route-proxy/contract"
)
@@ -151,17 +154,81 @@ func TestARouteWithNoInternalNameGetsNoAlias(t *testing.T) {
}
}
// A workload beside the proxy is ordinary, and reaching it over loopback is both correct and the
// only thing that works when there is no private network.
func TestAConsumerOnTheProxysOwnMachineIsReachedOverLoopback(t *testing.T) {
routes, _, err := routesFrom(`{"given":[
{"from":"app","node":"anchor","values":{"name":"app.example","port":9000}}
]}`)
if err != nil {
t.Fatal(err)
}
if targetOf(routes, "app.example") != "http://127.0.0.1:9000" {
t.Fatalf("a workload on this machine was not reachable: %v", routes)
// A route is dialled where the machine publishes its module's endpoint, as the module's declaration publishes it (issue
// 774, research 072 row 34; 2026-10-31: gitea's 3000, published at 20000, and grafana's 3000 on another machine,
// published at 20012, answered 502): over loopback beside the proxy, which is the only thing that works without a
// private network, and at its machine's address otherwise. Each plan is composed by the SDK as the seat
// mesh-provisioner composes it, its ports as compose's portsFor gives them (the placement's for a port the mesh may
// move, the manifest's own otherwise), and read as this proxy reads it: the live shapes the old proxy served alike.
func TestAComposedPlanIsDialledWhereTheMachinePublishesIt(t *testing.T) {
proxy := catalogue.Manifest{Module: "route-proxy", Version: "1",
Provides: []catalogue.Offer{{Name: "route", Scope: catalogue.ScopeMesh, AnsweredBy: catalogue.AnsweredByHelper}}}
world := catalogue.World{Offered: map[string][]catalogue.Provider{
"route": {{Node: "anchor", At: "anchor.internal", Module: "route-proxy"}}}}
for _, c := range []struct {
name, node, module string
port int // the endpoint's port, as listens says it
published []any // the container's ports; nil for the host's network
placed map[int]int // what mesh-placement holds for the module
target string
}{
{"beside the proxy, moved by the placement (gitea)", "anchor", "gitea", 3000, []any{"3000"}, map[int]int{3000: 20000},
"http://127.0.0.1:20000"},
{"on another machine, moved by the placement (grafana)", "home", "grafana", 3000, []any{"3000"},
map[int]int{3000: 20012}, "http://home.internal:20012"},
{"a bare port the placement holds none for (influxdb)", "home", "influxdb", 8086, []any{"8086"}, nil,
"http://home.internal:8086"},
{"a fixed mapping (mailu 7080:80)", "anchor", "mailu", 7080, []any{"25", "7080:80"}, nil, "http://127.0.0.1:7080"},
{"a fixed mapping the placement names another port of (photos 9102:9000)", "anchor", "photos", 9102,
[]any{"9102:9000"}, map[int]int{9000: 20011}, "http://127.0.0.1:9102"},
{"the host's network (home-assistant)", "anchor", "home-assistant", 8123, nil, nil, "http://127.0.0.1:8123"},
} {
container := map[string]any{"type": "container", "name": c.module, "image": "x"}
if c.published != nil {
container["ports"] = c.published
} else {
container["network"] = "host"
}
module := catalogue.Manifest{Module: c.module, Version: "1", Requires: []string{"route"},
Listens: []catalogue.Listening{{Name: "web", Port: c.port, Protocol: "tcp", From: "mesh"}},
Contributes: map[string]map[string]any{"route": {"label": c.module, "endpoint": "web"}},
Resources: []map[string]any{container}}
node := catalogue.Node{Name: c.node, At: c.node + ".internal", PublicDomain: "example.org",
Capabilities: map[string]bool{"container-runtime": true}}
r, err := catalogue.Resolve(map[string]catalogue.Manifest{module.Module: module, proxy.Module: proxy},
[]string{module.Module}, node, world)
if err != nil {
t.Fatalf("%s: %v", c.name, err)
}
r.Account = "op"
for i, n := range r.Needs {
r.Needs[i].AnsweredByHelper = n.Name == "route" // as the plan sets it (compose's answeredByHelper)
}
ports := map[int]int{}
for _, l := range module.Listens { // compose's portsFor
where, mayAssign := module.MachineSide(l.Port)
if at, known := c.placed[l.Port]; mayAssign && known {
where = at
}
ports[l.Port] = where
}
composed, err := r.Compose(catalogue.Rendering{Hands: true, Provisioner: "mesh-provisioner-client",
ArtifactStore: "store:5000", Planned: true, Ports: map[string]map[int]int{c.module: ports}})
if err != nil {
t.Fatalf("%s: %v", c.name, err)
}
value, err := provisions.EncodePlan(provisions.Plan{Node: c.node, Module: c.module, Plan: composed.Plans[c.module]})
if err != nil {
t.Fatalf("%s: %v", c.name, err)
}
given, refused, err := routesOfPlan("anchor", c.node+"."+c.module, value)
if err != nil || len(refused) != 0 {
t.Fatalf("%s: the plan gives %v, refused %v (%v)", c.name, given, refused, err)
}
routes, _ := routesOf(given)
if got := targetOf(routes, c.module+".example.org"); got != c.target {
t.Errorf("%s: dialled at %q, want %q", c.name, got, c.target)
}
}
}
+1 -1
View File
@@ -3,7 +3,7 @@ module route-proxy
go 1.26.0
require (
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031120835-b61102245706
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031122137-bb01a656d024
github.com/nats-io/nats.go v1.54.0
golang.org/x/crypto v0.57.0
)
+2 -2
View File
@@ -1,7 +1,7 @@
git.novox.be/novox/mesh-controller/modules/route-proxy/contract v0.1.0 h1:89jE1oBK+l7gFJjz6wx8mUD3KxrqSDa7pUTlQkzC1do=
git.novox.be/novox/mesh-controller/modules/route-proxy/contract v0.1.0/go.mod h1:Yr+Xp89kpMiKH6kA06EU+d9I2CyzrK9yMX7J3pSv0hE=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031120835-b61102245706 h1:wQ+AhLzvRYyWY58lYaAp/P3OdAZZKIHDKj6oEDd/O6U=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031120835-b61102245706/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031122137-bb01a656d024 h1:Uhg2DGxqh9+44ccSbF87v7joSodSwUEGUhhqtrGbP+k=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031122137-bb01a656d024/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=