Dial a route at the machine port its module's placement assigned, from the mesh-sdk whose plans carry it (issue 774, research 072 row 34)
route-proxy from plans dialled every route at the software's own port: gitea at 127.0.0.1:3000 (published at 20000) and grafana on ace at 3000 (published at 20012), and public sites answered 502. The proxy already reaches a module beside it over loopback and one elsewhere at the plan's address; the plan now carries the assigned port, and a test composes the plan with the SDK and reads it as the proxy does, so the two cannot drift again. The controller, NATS-native: no piece moves; route-proxy only.
This commit is contained in:
@@ -0,0 +1,71 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,9 +5,9 @@ package main
|
|||||||
//
|
//
|
||||||
// **A route is a provision, in route-proxy's own contract.** A module that must be reachable requires `route`; the
|
// **A route is a provision, in route-proxy's own contract.** A module that must be reachable requires `route`; the
|
||||||
// controller composes its plan on its node, and the plan's entry for the route names this proxy as its provider and
|
// controller composes its plan on its node, and the plan's entry for the route names this proxy as its provider and
|
||||||
// carries what the module contributes, composed (its names joined to the node's domains, its endpoint's port), and
|
// carries what the module contributes, composed (its names joined to the node's domains, the machine port its
|
||||||
// where the module's machine is on the private network, as one opaque payload the seat never reads (mesh-sdk
|
// placement assigned its endpoint, never the software's own), and where the module's machine is on the private
|
||||||
// catalogue.PlanProvision.Contributed). The proxy decodes each contribution with its contract (contract.DecodeRequest)
|
// network, as one opaque payload the seat never reads (mesh-sdk catalogue.PlanProvision.Contributed). The proxy decodes each contribution with its contract (contract.DecodeRequest)
|
||||||
// and serves every entry naming it. There is no file and no second source: a route the plans do not name is not
|
// and serves every entry naming it. There is no file and no second source: a route the plans do not name is not
|
||||||
// served, and a route the contract refuses is said refused (contract.EventRefused), never served half.
|
// served, and a route the contract refuses is said refused (contract.EventRefused), never served half.
|
||||||
|
|
||||||
|
|||||||
@@ -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.20261031084708-acbf80ad910a
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031120835-b61102245706
|
||||||
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.20261031084708-acbf80ad910a h1:DcSvfEmdPBKqCg5OmXRNb269EkkPe7BsEp+RykEhkgE=
|
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.20261031084708-acbf80ad910a/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031120835-b61102245706/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