Take the catalogue's composition rules into the node-engine
ADR 0593 §7: the composer with its refusals leaves mesh-sdk, so mesh-sdk carries no composition rule. The catalogue functions only the node-engine's composition reaches (Resolve, Resolution's Compose, Declaration, DataItems, LeftOut, Rules and the rest, the settings, placeholder and template refusals, the rendering helpers) are copied with their tests into internal/compose/resolution; a method of a mesh-sdk shape becomes a function taking it first. The composition calls its own copies, and the captured catalogue its tests read lives here now, shared by mesh-compose's tests. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
@@ -0,0 +1,250 @@
|
||||
package resolution
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
mcat "git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
)
|
||||
|
||||
// labelled is a module that asks to be published under a subdomain rather than a full hostname.
|
||||
func labelled(module, label string, port int) mcat.Manifest {
|
||||
return mcat.Manifest{Module: module, Version: "1",
|
||||
Contributes: map[string]map[string]any{
|
||||
"reverse-proxy": {"label": label, "port": port},
|
||||
}}
|
||||
}
|
||||
|
||||
// withDomain is a workstation that composes its routed names under one public domain.
|
||||
func withDomain(domain string) mcat.Node {
|
||||
n := workstation()
|
||||
n.PublicDomain = domain
|
||||
return n
|
||||
}
|
||||
|
||||
func TestALabelComposesWithTheNodesPublicDomain(t *testing.T) {
|
||||
got, err := Resolve(shelf(proxy(), labelled("board", "git", 8080)),
|
||||
[]string{"board"}, withDomain("example.tld"), mcat.World{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
given := received(t, mustDeclare(t, got))
|
||||
if len(given) != 1 {
|
||||
t.Fatalf("the proxy was told about %d of 1: %v", len(given), given)
|
||||
}
|
||||
if given[0].Values["name"] != "git.example.tld" {
|
||||
t.Fatalf("the label did not compose with the domain: %v", given[0].Values)
|
||||
}
|
||||
// The port the module gave is still there — composing the name must not drop what the proxy
|
||||
// needs to reach the workload.
|
||||
if given[0].Values["port"] != float64(8080) {
|
||||
t.Fatalf("composing the name dropped the port: %v", given[0].Values)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTheApexLabelComposesToTheBareDomain(t *testing.T) {
|
||||
got, err := Resolve(shelf(proxy(), labelled("board", "@", 4000)),
|
||||
[]string{"board"}, withDomain("example.tld"), mcat.World{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
given := received(t, mustDeclare(t, got))
|
||||
if len(given) != 1 {
|
||||
t.Fatalf("the proxy was told about %d of 1: %v", len(given), given)
|
||||
}
|
||||
// `@` is the zone-file apex: served at the bare public domain, no subdomain, no leading dot.
|
||||
if given[0].Values["name"] != "example.tld" {
|
||||
t.Fatalf("the apex label did not compose to the bare domain: %v", given[0].Values)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOneNodeSettingMovesTheCatalogueBetweenDomains(t *testing.T) {
|
||||
// The ADR's name-agnostic check: the same catalogue resolves against two different public
|
||||
// domains by changing one node setting and nothing else.
|
||||
one, err := Resolve(shelf(proxy(), labelled("board", "git", 8080)),
|
||||
[]string{"board"}, withDomain("example.tld"), mcat.World{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
two, err := Resolve(shelf(proxy(), labelled("board", "git", 8080)),
|
||||
[]string{"board"}, withDomain("other.example"), mcat.World{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
first := received(t, mustDeclare(t, one))[0].Values
|
||||
second := received(t, mustDeclare(t, two))[0].Values
|
||||
|
||||
if first["name"] != "git.example.tld" || second["name"] != "git.other.example" {
|
||||
t.Fatalf("the name did not track the domain: %v then %v", first["name"], second["name"])
|
||||
}
|
||||
// And nothing else moved: same label, same port. The domain is the one fact that changed.
|
||||
if first["label"] != second["label"] || first["label"] != "git" {
|
||||
t.Fatalf("the label changed with the domain: %v then %v", first["label"], second["label"])
|
||||
}
|
||||
if first["port"] != second["port"] {
|
||||
t.Fatalf("the port changed with the domain: %v then %v", first["port"], second["port"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestALegacyFullNameContributionPassesThroughUnchanged(t *testing.T) {
|
||||
// Backward compatibility: a contribution that still carries a full `name` and no `label` is
|
||||
// granted that name as-is, even on a node that has a public domain. This is what lets the
|
||||
// catalogue migrate one module at a time while the running mesh keeps working.
|
||||
legacy := mcat.Manifest{Module: "board", Version: "1",
|
||||
Contributes: map[string]map[string]any{
|
||||
"reverse-proxy": {"name": "git.pinned.example", "port": 8080},
|
||||
}}
|
||||
got, err := Resolve(shelf(proxy(), legacy), []string{"board"}, withDomain("example.tld"), mcat.World{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
given := received(t, mustDeclare(t, got))
|
||||
if given[0].Values["name"] != "git.pinned.example" {
|
||||
t.Fatalf("a full name was rewritten: %v", given[0].Values)
|
||||
}
|
||||
if _, grewLabel := given[0].Values["label"]; grewLabel {
|
||||
t.Fatalf("a legacy contribution grew a label: %v", given[0].Values)
|
||||
}
|
||||
}
|
||||
|
||||
func TestALabelWithNoPublicDomainComposesNothing(t *testing.T) {
|
||||
// A routed module on a node with no public domain has nothing to join its label to. It composes
|
||||
// no name — which reads downstream exactly as a route that named no host, the same as before
|
||||
// this existed — rather than an fabricated `git.` with a dangling dot.
|
||||
got, err := Resolve(shelf(proxy(), labelled("board", "git", 8080)),
|
||||
[]string{"board"}, workstation(), mcat.World{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
given := received(t, mustDeclare(t, got))
|
||||
if _, named := given[0].Values["name"]; named {
|
||||
t.Fatalf("a name was composed with no domain to compose it from: %v", given[0].Values)
|
||||
}
|
||||
if given[0].Values["label"] != "git" {
|
||||
t.Fatalf("the label was lost: %v", given[0].Values)
|
||||
}
|
||||
}
|
||||
|
||||
// withPrivateAddress is a workstation on the private network, at the given internal name — the
|
||||
// same fact a route's own consumers already receive as `${bound:...:at}`.
|
||||
func withPrivateAddress(at string) mcat.Node {
|
||||
n := workstation()
|
||||
n.At = at
|
||||
return n
|
||||
}
|
||||
|
||||
func TestALabelComposesWithTheNodesPrivateAddressToo(t *testing.T) {
|
||||
// A predecessor proxy answered a route on both a public and a private-network hostname for the
|
||||
// same convenience the mesh restores here: reaching a service over the VPN without a public TLS
|
||||
// round trip. Composed independently of the public name, from the node's own `At`.
|
||||
got, err := Resolve(shelf(proxy(), labelled("board", "git", 8080)),
|
||||
[]string{"board"}, withPrivateAddress("anchor.internal"), mcat.World{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
given := received(t, mustDeclare(t, got))
|
||||
if given[0].Values["internal-name"] != "git.anchor.internal" {
|
||||
t.Fatalf("the label did not compose with the private address: %v", given[0].Values)
|
||||
}
|
||||
}
|
||||
|
||||
func TestThePublicAndInternalNamesComposeIndependently(t *testing.T) {
|
||||
// A node with both a public domain and a private address gets both names from one label; a
|
||||
// node with only one of the two gets only the matching one — neither composition depends on
|
||||
// the other being possible.
|
||||
both := withPrivateAddress("anchor.internal")
|
||||
both.PublicDomain = "example.tld"
|
||||
got, err := Resolve(shelf(proxy(), labelled("board", "git", 8080)),
|
||||
[]string{"board"}, both, mcat.World{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
given := received(t, mustDeclare(t, got))
|
||||
if given[0].Values["name"] != "git.example.tld" {
|
||||
t.Fatalf("the public name did not compose alongside the internal one: %v", given[0].Values)
|
||||
}
|
||||
if given[0].Values["internal-name"] != "git.anchor.internal" {
|
||||
t.Fatalf("the internal name did not compose alongside the public one: %v", given[0].Values)
|
||||
}
|
||||
|
||||
publicOnly, err := Resolve(shelf(proxy(), labelled("board", "git", 8080)),
|
||||
[]string{"board"}, withDomain("example.tld"), mcat.World{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
givenPublicOnly := received(t, mustDeclare(t, publicOnly))
|
||||
if _, has := givenPublicOnly[0].Values["internal-name"]; has {
|
||||
t.Fatalf("an internal name was composed with no private address to compose it from: %v",
|
||||
givenPublicOnly[0].Values)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTheApexLabelComposesToTheBarePrivateAddress(t *testing.T) {
|
||||
got, err := Resolve(shelf(proxy(), labelled("board", "@", 4000)),
|
||||
[]string{"board"}, withPrivateAddress("anchor.internal"), mcat.World{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
given := received(t, mustDeclare(t, got))
|
||||
if given[0].Values["internal-name"] != "anchor.internal" {
|
||||
t.Fatalf("the apex label did not compose to the bare private address: %v", given[0].Values)
|
||||
}
|
||||
}
|
||||
|
||||
// novox/hq ADR 0066 propagate, by ADR 0148's means: a granted route name is published into the
|
||||
// machine's roster mapped to the node that serves it, beside the `<node>.internal` names, and the
|
||||
// machine's resolver answers it to every container. Nothing is written into the container itself —
|
||||
// a routed name that moved would otherwise be wrong inside every container until each was recreated.
|
||||
func TestARoutedNameResolvesToTheServingNode(t *testing.T) {
|
||||
names := map[string]string{
|
||||
"anchor.internal": "10.42.0.1",
|
||||
"git.example.tld": "10.42.0.1",
|
||||
}
|
||||
got := containersOf(t, mcat.Resolution{Node: "anchor", Modules: []mcat.Manifest{{
|
||||
Module: "app",
|
||||
Resources: []map[string]any{{"id": "web", "type": "container", "name": "web",
|
||||
"image": "registry.example/web@sha256:" + strings.Repeat("a", 64)}},
|
||||
}}}, mcat.Rendering{Names: names})
|
||||
if len(got) != 1 {
|
||||
t.Fatalf("expected one container, got %d", len(got))
|
||||
}
|
||||
if given := namesOf(got[0]); len(given) != 0 {
|
||||
t.Fatalf("the routed name was copied into the container, where it would go stale: %v", given)
|
||||
}
|
||||
var sawRoute bool
|
||||
for _, e := range entriesFrom(names, nil, "internal") {
|
||||
if e.Name == "git.example.tld" && e.Address == "10.42.0.1" {
|
||||
sawRoute = true
|
||||
}
|
||||
}
|
||||
if !sawRoute {
|
||||
t.Fatalf("the routed name is not in the roster the machine's resolver answers from")
|
||||
}
|
||||
}
|
||||
|
||||
// novox/hq issue 139, ADR 0151: `<label>.<node>.internal` is answered by every machine's resolver as
|
||||
// "anything under that node's name goes to that node", so the node in a route's internal name must
|
||||
// be the one whose proxy answers it. Composed under the consumer's own name, a route served from
|
||||
// another machine got an internal name that resolved to a machine with nothing listening, while the
|
||||
// public name — published at the serving node's address — worked.
|
||||
func TestARoutesInternalNameIsComposedUnderTheNodeThatServesIt(t *testing.T) {
|
||||
hub := proxy()
|
||||
hub.Provides = mcat.FromAnywhere("reverse-proxy")
|
||||
got, err := Resolve(shelf(hub, labelled("board", "git", 8080)), []string{"board"},
|
||||
withPrivateAddress("laptop.internal"), mcat.World{Offered: map[string][]mcat.Provider{
|
||||
"reverse-proxy": {{Node: "anchor", At: "anchor.internal", Module: "traefik"}},
|
||||
}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Gathered the way the control plane gathers a consumer's contribution for a provider on
|
||||
// another machine.
|
||||
values, asks, err := ContributionsFrom(got, "reverse-proxy", "board", nil)
|
||||
if err != nil || !asks {
|
||||
t.Fatalf("the route was not contributed: %v %v", asks, err)
|
||||
}
|
||||
if values["internal-name"] != "git.anchor.internal" {
|
||||
t.Fatalf("the internal name does not say where the request arrives: %v", values)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user