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 `.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: `