novox/hq ADR 0148, step 3. Every container got the whole roster as --add-host entries at creation and nothing re-read them (issues 109, 135); once the roster was in the digest so that could be caught, one name moving anywhere replaced every container in the mesh (issue 151). A container resolves through its machine's resolver, which the resolver module tells the runtime about once per machine. A module's own hosts entries stay exactly as declared. Also brings the resolver tests up to the catalogue as it now is: the runtime is reloaded (never restarted) and given live-restore, and the resolver answers by address, not by interface (issue 110).
101 lines
4.0 KiB
Go
101 lines
4.0 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func containersOf(t *testing.T, r Resolution, with Rendering) []map[string]any {
|
|
t.Helper()
|
|
out, err := r.Declaration(with)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var found []map[string]any
|
|
for _, res := range out {
|
|
if res["type"] == "container" {
|
|
found = append(found, res)
|
|
}
|
|
}
|
|
return found
|
|
}
|
|
|
|
func namesOf(r map[string]any) []string {
|
|
var out []string
|
|
if given, ok := r["hosts"].([]any); ok {
|
|
for _, h := range given {
|
|
out = append(out, h.(string))
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// No mesh name is written into a container (novox/hq ADR 0148). It resolves them through its
|
|
// machine's resolver at the moment it asks, so a name that moves is answered differently by the
|
|
// next lookup, in every container, with nothing recreated.
|
|
//
|
|
// Checked the way the record says: the declaration a container gets does not move when the mesh's
|
|
// roster does. A roster with one machine and a roster with three produce the same container, byte
|
|
// for byte, so the digest a host computes from it cannot move either — which is what stopped one
|
|
// name moving from replacing every container in the mesh (issue 151).
|
|
func TestAContainerIsTheSameWhateverTheMeshsRosterSays(t *testing.T) {
|
|
module := Manifest{Module: "app", Resources: []map[string]any{{"id": "web", "type": "container",
|
|
"name": "web", "image": "registry.example/web@sha256:" + strings.Repeat("a", 64)}}}
|
|
one := containersOf(t, Resolution{Node: "laptop", Modules: []Manifest{module}},
|
|
Rendering{Names: map[string]string{"laptop.internal": "10.42.0.2"}})
|
|
three := containersOf(t, Resolution{Node: "laptop", Modules: []Manifest{module}},
|
|
Rendering{Names: map[string]string{
|
|
"anchor.internal": "10.42.0.1", "laptop.internal": "10.42.0.2", "git.example.tld": "10.42.0.1",
|
|
}})
|
|
if len(one) != 1 || len(three) != 1 {
|
|
t.Fatalf("expected one container each, got %d and %d", len(one), len(three))
|
|
}
|
|
if given := namesOf(three[0]); len(given) != 0 {
|
|
t.Fatalf("the mesh's names were copied into the container: %v", given)
|
|
}
|
|
if !reflect.DeepEqual(one[0], three[0]) {
|
|
t.Fatalf("the container moved with the roster:\n%v\n%v", one[0], three[0])
|
|
}
|
|
}
|
|
|
|
// The names a module declares for itself are its own: part of what the module is, kept exactly as
|
|
// written, and the mesh does not know what they mean. They are the one thing in a container's
|
|
// hosts that does move its identity, because they do not move when the mesh's roster does.
|
|
func TestAContainersOwnNamesAreKeptAsWritten(t *testing.T) {
|
|
got := containersOf(t, Resolution{Node: "laptop", Modules: []Manifest{{
|
|
Module: "app",
|
|
Resources: []map[string]any{{"id": "web", "type": "container", "name": "web",
|
|
"image": "registry.example/web@sha256:" + strings.Repeat("a", 64),
|
|
"hosts": []any{"something.else:203.0.113.9"}}},
|
|
}}}, Rendering{Names: map[string]string{"anchor.internal": "10.42.0.1"}})
|
|
|
|
given := namesOf(got[0])
|
|
if len(given) != 1 || given[0] != "something.else:203.0.113.9" {
|
|
t.Fatalf("the container's own names were not kept as written: %v", given)
|
|
}
|
|
}
|
|
|
|
// No resource is given a `hosts` key it did not declare. A file or a service carrying one is a
|
|
// declaration the host refuses outright — it takes no unknown field — so an invented key breaks
|
|
// the whole machine rather than one resource.
|
|
func TestNothingIsGivenNamesItDidNotDeclare(t *testing.T) {
|
|
out, err := Resolution{Node: "laptop", Modules: []Manifest{{
|
|
Module: "app",
|
|
Resources: []map[string]any{
|
|
{"id": "conf", "type": "file", "path": "/etc/app.conf", "content": "x", "mode": "0644"},
|
|
{"id": "run", "type": "service", "unit": "app.service", "state": "running"},
|
|
{"id": "web", "type": "container", "name": "web",
|
|
"image": "registry.example/web@sha256:" + strings.Repeat("a", 64)},
|
|
},
|
|
}}}.Declaration(Rendering{Names: map[string]string{"anchor.internal": "10.42.0.1"}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, r := range out {
|
|
if _, given := r["hosts"]; given {
|
|
t.Fatalf("a %v was given names it never declared: %v", r["type"], r)
|
|
}
|
|
}
|
|
}
|