From 0e9c4c8299cceaaa2b9ec28670605e4a81cd75b0 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 31 Oct 2026 13:22:00 +0100 Subject: [PATCH] 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. --- .../cmd/route-proxy/composed_test.go | 71 --------------- .../cmd/route-proxy/routes_test.go | 89 ++++++++++++++++--- modules/route-proxy/go.mod | 2 +- modules/route-proxy/go.sum | 4 +- 4 files changed, 81 insertions(+), 85 deletions(-) delete mode 100644 modules/route-proxy/cmd/route-proxy/composed_test.go diff --git a/modules/route-proxy/cmd/route-proxy/composed_test.go b/modules/route-proxy/cmd/route-proxy/composed_test.go deleted file mode 100644 index e9bab983..00000000 --- a/modules/route-proxy/cmd/route-proxy/composed_test.go +++ /dev/null @@ -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) - } - } -} diff --git a/modules/route-proxy/cmd/route-proxy/routes_test.go b/modules/route-proxy/cmd/route-proxy/routes_test.go index 836dbb56..edf01d0a 100644 --- a/modules/route-proxy/cmd/route-proxy/routes_test.go +++ b/modules/route-proxy/cmd/route-proxy/routes_test.go @@ -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) + } } } diff --git a/modules/route-proxy/go.mod b/modules/route-proxy/go.mod index 00a3c7e7..2978c2dc 100644 --- a/modules/route-proxy/go.mod +++ b/modules/route-proxy/go.mod @@ -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 ) diff --git a/modules/route-proxy/go.sum b/modules/route-proxy/go.sum index 5c38d7e5..a0b5454c 100644 --- a/modules/route-proxy/go.sum +++ b/modules/route-proxy/go.sum @@ -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=