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:
@@ -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"
|
"strings"
|
||||||
"testing"
|
"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"
|
"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
|
// A route is dialled where the machine publishes its module's endpoint, as the module's declaration publishes it (issue
|
||||||
// only thing that works when there is no private network.
|
// 774, research 072 row 34; 2026-10-31: gitea's 3000, published at 20000, and grafana's 3000 on another machine,
|
||||||
func TestAConsumerOnTheProxysOwnMachineIsReachedOverLoopback(t *testing.T) {
|
// published at 20012, answered 502): over loopback beside the proxy, which is the only thing that works without a
|
||||||
routes, _, err := routesFrom(`{"given":[
|
// private network, and at its machine's address otherwise. Each plan is composed by the SDK as the seat
|
||||||
{"from":"app","node":"anchor","values":{"name":"app.example","port":9000}}
|
// 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.
|
||||||
if err != nil {
|
func TestAComposedPlanIsDialledWhereTheMachinePublishesIt(t *testing.T) {
|
||||||
t.Fatal(err)
|
proxy := catalogue.Manifest{Module: "route-proxy", Version: "1",
|
||||||
}
|
Provides: []catalogue.Offer{{Name: "route", Scope: catalogue.ScopeMesh, AnsweredBy: catalogue.AnsweredByHelper}}}
|
||||||
if targetOf(routes, "app.example") != "http://127.0.0.1:9000" {
|
world := catalogue.World{Offered: map[string][]catalogue.Provider{
|
||||||
t.Fatalf("a workload on this machine was not reachable: %v", routes)
|
"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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ module route-proxy
|
|||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
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
|
github.com/nats-io/nats.go v1.54.0
|
||||||
golang.org/x/crypto v0.57.0
|
golang.org/x/crypto v0.57.0
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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 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-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.20261031122137-bb01a656d024 h1:Uhg2DGxqh9+44ccSbF87v7joSodSwUEGUhhqtrGbP+k=
|
||||||
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/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 h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
||||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
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=
|
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
||||||
|
|||||||
Reference in New Issue
Block a user