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)

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 70b8bff570
commit d550c6da7a
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=