Carry the machine-assigned port in a plan's contribution, so a provider serving from plans dials where the module listens (issue 774, research 072 row 34)

route-proxy served every route at the software's own port (gitea's 3000, published at 20000;
grafana's 3000 on another machine, published at 20012) and public sites answered 502. The plan's
contribution now carries the port the placement assigned, the number ${port:N} gives the module,
and a composition for plans refuses a port none assigned by module, node and port.
This commit is contained in:
2026-10-31 13:08:35 +01:00
parent fbbfae7629
commit b611022457
5 changed files with 107 additions and 5 deletions
+2
View File
@@ -58,6 +58,8 @@ func (c *composer) render(nodeName string, mode Mode) (Rendered, error) {
}
r := placed.Rendering
out.ForeseenPorts = placed.ForeseenPorts
// A plan carries its contributions' machine ports, refused where the placement assigned none (issue 774).
r.Planned = mode == Planning
r.Grants, r.Withheld, r.Unbound = grants, withheld, unbound
out.Withheld, out.Unbound = withheld, unbound
for _, g := range grantsForeseen {
+7
View File
@@ -245,6 +245,13 @@ func (c *composer) portsFor(node Node, plan catalogue.Resolution, settings catal
where = at
} else {
*foreseen = append(*foreseen, fmt.Sprintf("%s:%d", m.Module, l.Port))
if r.Unassigned == nil {
r.Unassigned = map[string]map[int]bool{}
}
if r.Unassigned[m.Module] == nil {
r.Unassigned[m.Module] = map[int]bool{}
}
r.Unassigned[m.Module][l.Port] = true
}
}
if ports[m.Module] == nil {
+9 -3
View File
@@ -52,12 +52,12 @@ func TestAnAllocatedPortIsComposedAndAnUnallocatedOneIsNamedForeseenNotInvented(
if p := got.Rendering.Ports["web"][8080]; p != 8080 {
t.Errorf("a port nobody allocated is composed as %d, not as the manifest's own", p)
}
if len(got.ForeseenPorts) != 1 || got.ForeseenPorts[0] != "web:8080" {
t.Errorf("foreseen ports are %v", got.ForeseenPorts)
if len(got.ForeseenPorts) != 1 || got.ForeseenPorts[0] != "web:8080" || !got.Rendering.Unassigned["web"][8080] {
t.Errorf("foreseen ports are %v (unassigned %v)", got.ForeseenPorts, got.Rendering.Unassigned)
}
nodeOf(t, &in, "laptop").Ports = map[string]map[int]int{"web": {8080: 20123}}
got = placed(t, in, "laptop")
if p := got.Rendering.Ports["web"][8080]; p != 20123 || len(got.ForeseenPorts) != 0 {
if p := got.Rendering.Ports["web"][8080]; p != 20123 || len(got.ForeseenPorts) != 0 || got.Rendering.Unassigned != nil {
t.Errorf("the allocated port is composed as %d (foreseen %v)", p, got.ForeseenPorts)
}
// A port the module does not publish is not the mesh's to move, so it is never foreseen.
@@ -70,6 +70,12 @@ func TestAnAllocatedPortIsComposedAndAnUnallocatedOneIsNamedForeseenNotInvented(
t.Errorf("an unpublished port: %v foreseen %v", got.Rendering.Ports, got.ForeseenPorts)
}
unchanged(t, mk, func(in Input) { placed(t, in, "laptop") })
// Only a composition for plans refuses a contribution whose port none assigned (issue 774): a declaration still composes.
for _, mode := range []Mode{Reading, Foreseeing, Planning} {
if got := rendered(t, mk(), "laptop", mode).Rendering; got.Planned != (mode == Planning) {
t.Errorf("mode %d composes with Planned %v", mode, got.Planned)
}
}
}
// 1363/AC3: a port the operator gave is used, over the allocated one, and a consumer elsewhere is told it