Merge pull request 'A mesh seat's holder elsewhere answers before this machine's own provider (hq issue 258)' (#59) from fix/a-mesh-seat-answers-before-the-machines-own into main
This commit was merged in pull request #59.
This commit is contained in:
@@ -341,7 +341,7 @@ func Resolve(catalogue map[string]Manifest, assigned []string, node Node, world
|
||||
// trust boundary the moment both ends are containers**, and treating it as one gave the
|
||||
// commonest arrangement of all — a service and its database on one node — the weakest
|
||||
// handling, silently.
|
||||
if satisfied[want] && !isModule(catalogue, want) {
|
||||
if satisfied[want] && !isModule(catalogue, want) && !answeredElsewhere(want, node, world, brokered) {
|
||||
here := func(name string) bool { return chosen[name] || assignedHere[name] }
|
||||
local := providersHere(catalogue, here, want)
|
||||
// Which of them it matters to choose between. A plain capability — a shell, a display
|
||||
@@ -1134,3 +1134,28 @@ func machineReachRemedy(catalogue map[string]Manifest, want, node string) string
|
||||
}
|
||||
return fmt.Sprintf("assign one to %s: %s", node, strings.Join(named, "; "))
|
||||
}
|
||||
|
||||
// answeredElsewhere says that a mesh-wide provision this machine could answer itself is answered by
|
||||
// another machine all the same: one this machine was pinned to, or the holder of the mesh seat that
|
||||
// delivers it (novox/hq ADR 0110, ADR 0194).
|
||||
//
|
||||
// **A provider on the machine was taken before either was asked.** The branch for a provision
|
||||
// answered here bound the consumer to the local provider and consulted a pin only between two local
|
||||
// ones, and the seat's holder never; both were read only for a provider on another machine. So when
|
||||
// every machine ran a provider of `wildcard-resolution` — each machine's own resolver, still assigned
|
||||
// while the mesh moved to one — every machine bound its resolver configuration to itself, with the
|
||||
// mesh's one resolver recorded, held and pinned (novox/hq issue 258). The seat is the mesh's choice of
|
||||
// who answers, made once; a provider that merely runs here does not overrule it, and neither does it
|
||||
// overrule a pin somebody set on this machine.
|
||||
//
|
||||
// Not in the first pass, which asks only what this machine offers and has no providers to read.
|
||||
func answeredElsewhere(want string, node Node, world World, brokered map[string]bool) bool {
|
||||
if world.Unchecked || !brokered[want] {
|
||||
return false
|
||||
}
|
||||
if c, pinned := world.Pinned[want]; pinned {
|
||||
return c.Node != node.Name
|
||||
}
|
||||
holder, held := HolderAmong(want, world.Offered[want], world.Held)
|
||||
return held && holder.Node != node.Name
|
||||
}
|
||||
|
||||
@@ -15,7 +15,8 @@ import (
|
||||
// same arrangement, and to the two things a resolver here must never do — read resolv.conf for
|
||||
// its upstreams, or take an address systemd-resolved holds.
|
||||
|
||||
// resolverShelf is the three resolver modules beside something that answers `mesh-addressing`.
|
||||
// resolverShelf is the three resolver modules and the container runtime beside something that
|
||||
// answers `mesh-addressing`.
|
||||
// The networking module that really does is composed in the controller and cannot be imported
|
||||
// here, so a stand-in offers the same word; what is under test is the manifests, not the network.
|
||||
func resolverShelf(t *testing.T) map[string]Manifest {
|
||||
@@ -23,7 +24,7 @@ func resolverShelf(t *testing.T) map[string]Manifest {
|
||||
shelf := map[string]Manifest{
|
||||
"net": {Module: "net", Version: "1", Provides: []Offer{{Name: "mesh-addressing"}}},
|
||||
}
|
||||
for _, name := range []string{"dnsmasq", "resolv-conf", "resolved-split-dns"} {
|
||||
for _, name := range []string{"dnsmasq", "resolv-conf", "resolved-split-dns", "docker"} {
|
||||
shelf[name] = catalogueManifest(t, name)
|
||||
}
|
||||
return shelf
|
||||
@@ -117,8 +118,9 @@ func TestTheResolverForwardsToFixedUpstreamsAndNeverReadsResolvConf(t *testing.T
|
||||
// that file, the machine pointed at the resolver by address, and the runtime given no resolver of
|
||||
// its own but kept running across a restart (ADR 0196).
|
||||
func TestTheResolverAndWhatAsksItComposeOnOneMachine(t *testing.T) {
|
||||
got, err := Resolve(resolverShelf(t), []string{"dnsmasq", "resolv-conf"},
|
||||
Node{Name: "anchor", At: "anchor.internal"}, World{})
|
||||
got, err := Resolve(resolverShelf(t), []string{"dnsmasq", "resolv-conf", "docker"},
|
||||
Node{Name: "anchor", At: "anchor.internal", Capabilities: map[string]bool{
|
||||
"package-manager": true, "service-manager": true, "privileged": true}}, World{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -167,13 +169,19 @@ func TestTheResolverAndWhatAsksItComposeOnOneMachine(t *testing.T) {
|
||||
t.Errorf("the resolver was not told to forward the zone to its answerer:\n%s", z)
|
||||
}
|
||||
|
||||
// The runtime's own file, written into (novox/hq ADR 0102) with one key, by what decides how the
|
||||
// machine resolves: a restart keeps every container running. No `dns` — a container copies its
|
||||
// machine's resolvers (ADR 0196), and the mesh's resolver is not written into the runtime twice.
|
||||
// The runtime's own file, written into (novox/hq ADR 0102) with one key, by the runtime's own
|
||||
// module — a module does not write another software's configuration (issue 190): a restart keeps
|
||||
// every container running. No `dns` — a container copies its machine's resolvers (ADR 0196), and
|
||||
// neither the resolver nor what decides how the machine resolves writes the runtime's file.
|
||||
if ids["dnsmasq.runtime-dns"] != nil {
|
||||
t.Errorf("the resolver still writes the runtime's dns: %v", ids["dnsmasq.runtime-dns"])
|
||||
}
|
||||
runtime := ids["resolv-conf.runtime-config"]
|
||||
for id := range ids {
|
||||
if strings.HasPrefix(id, "resolv-conf.runtime") {
|
||||
t.Errorf("what the machine asks still writes the runtime's file: %s", id)
|
||||
}
|
||||
}
|
||||
runtime := ids["docker.daemon"]
|
||||
if runtime == nil || runtime["path"] != "/etc/docker/daemon.json" || runtime["into"] != "json" {
|
||||
t.Fatalf("live-restore is not written into the runtime's file: %v", runtime)
|
||||
}
|
||||
@@ -195,7 +203,7 @@ func TestTheResolverAndWhatAsksItComposeOnOneMachine(t *testing.T) {
|
||||
t.Errorf("the runtime is ordered restarted, which stops every container (ADR 0102): %v", r)
|
||||
}
|
||||
for _, on := range asStrings(r["reload-on"]) {
|
||||
if on == "resolv-conf.runtime-config" {
|
||||
if on == "docker.daemon" {
|
||||
reloaded = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package catalogue
|
||||
|
||||
import "testing"
|
||||
|
||||
// A mesh-wide provision whose seat is held on another machine is answered by that holder, even on a
|
||||
// machine that runs a provider of its own (novox/hq issue 258). Every machine ran its own resolver
|
||||
// while the mesh moved to one; each bound its resolver configuration to itself, with the mesh's
|
||||
// resolver held elsewhere and pinned.
|
||||
func resolverMesh() map[string]Manifest {
|
||||
return map[string]Manifest{
|
||||
"resolver": {Module: "resolver", Version: "1",
|
||||
Provides: []Offer{{Name: "wildcard-resolution", Scope: ScopeMesh}},
|
||||
Claims: []Claim{{Name: "mesh-dns-resolver", Scope: ScopeMesh}}},
|
||||
"asker": {Module: "asker", Version: "1", Requires: []string{"wildcard-resolution"}},
|
||||
}
|
||||
}
|
||||
|
||||
func resolverWorld(held string, pin *Chosen) World {
|
||||
w := World{
|
||||
Offered: map[string][]Provider{"wildcard-resolution": {
|
||||
{Node: "anchor", At: "anchor.internal", Module: "resolver"},
|
||||
{Node: "laptop", At: "laptop.internal", Module: "resolver"},
|
||||
}},
|
||||
}
|
||||
// Held is who holds it across the mesh; Holdings is the same holder on record, which lets this
|
||||
// machine's own claimant stand beside it (ADR 0131).
|
||||
w.Held = []Held{{Claim: "mesh-dns-resolver", Scope: ScopeMesh, Node: held, Module: "resolver"}}
|
||||
w.Holdings = w.Held
|
||||
if pin != nil {
|
||||
w.Pinned = map[string]Chosen{"wildcard-resolution": *pin}
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
func answeredFrom(t *testing.T, world World) string {
|
||||
t.Helper()
|
||||
laptop := Node{Name: "laptop", At: "laptop.internal"}
|
||||
got, err := Resolve(resolverMesh(), []string{"resolver", "asker"}, laptop, world)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, n := range got.Needs {
|
||||
if n.Name == "wildcard-resolution" {
|
||||
return n.From
|
||||
}
|
||||
}
|
||||
t.Fatalf("no binding for wildcard-resolution: %+v", got.Needs)
|
||||
return ""
|
||||
}
|
||||
|
||||
func TestTheSeatsHolderElsewhereAnswersBeforeThisMachinesOwnProvider(t *testing.T) {
|
||||
if from := answeredFrom(t, resolverWorld("anchor", nil)); from != "anchor" {
|
||||
t.Fatalf("bound to %s; the seat is held on anchor", from)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPinElsewhereAnswersBeforeThisMachinesOwnProvider(t *testing.T) {
|
||||
if from := answeredFrom(t, resolverWorld("laptop", &Chosen{Node: "anchor", Module: "resolver"})); from != "anchor" {
|
||||
t.Fatalf("bound to %s; this machine was pinned to anchor", from)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTheHolderOnThisMachineStillAnswersHere(t *testing.T) {
|
||||
if from := answeredFrom(t, resolverWorld("laptop", nil)); from != "laptop" {
|
||||
t.Fatalf("bound to %s; the seat is held here", from)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user