Resolver modules: one that serves, and two ways of deciding what a machine asks
Three manifests and the rule that keeps them apart. Serving and asking are genuinely different roles, and systemd-resolved can only do the second — it cannot answer a wildcard, it routes the mesh's suffix to something that can. A module that treated them as one role could not work, which is the mistake worth naming rather than discovering. So `the-dns-port` and `the-resolver-configuration` are two claims. A machine gets one of each, and two of either is refused by the mesh rather than fought over on the machine — which is what ADR 0009's table meant by listing resolvers beside the seat and pid 1. That table names the resource `/etc/resolv.conf`, which is what it is; a claim is a name in the catalogue's own form, and the catalogue refuses the path as one. Neither module knows anything about the machine it is on, which is what lets them be static manifests: they name `mesh0` and `127.0.0.54`, both chosen by the mesh, rather than an address only that machine has. Not 127.0.0.1 and not 127.0.0.53 — taking either would be a module claiming something it did not say it claims. A service can now reflect a file another module put on the machine, written `<module>.<id>`. The resolver has to restart when the mesh rewrites the names; without it, it would serve the names it started with for ever, with every machine that joined afterwards unreachable and every check passing.
This commit is contained in:
@@ -314,10 +314,22 @@ func (r Resolution) Declaration(with Rendering) ([]map[string]any, error) {
|
||||
copied["id"] = m.Module + "." + fmt.Sprint(resource["id"])
|
||||
// A service saying what it reflects names resources within its own module, so those
|
||||
// are prefixed too or they would point at nothing.
|
||||
//
|
||||
// **Unless it already names one.** A module may reflect a file another module put on
|
||||
// the machine — the case this exists for is a resolver restarting when the mesh
|
||||
// rewrites the names, which are computed by the mesh and belong to its module, not to
|
||||
// the daemon's. Written `<module>.<id>`, and a dot is what marks it as already
|
||||
// answered: prefixing it again would point at nothing, silently, and the daemon would
|
||||
// serve the old names for ever while everything reported success.
|
||||
if reflects, ok := resource["restart-on"].([]any); ok {
|
||||
var renamed []any
|
||||
for _, id := range reflects {
|
||||
renamed = append(renamed, m.Module+"."+fmt.Sprint(id))
|
||||
named := fmt.Sprint(id)
|
||||
if strings.Contains(named, ".") {
|
||||
renamed = append(renamed, named)
|
||||
continue
|
||||
}
|
||||
renamed = append(renamed, m.Module+"."+named)
|
||||
}
|
||||
copied["restart-on"] = renamed
|
||||
}
|
||||
|
||||
@@ -458,3 +458,47 @@ func TestAGeneratorThatOpensNothingNeedsNoMethod(t *testing.T) {
|
||||
t.Fatalf("a generator that says nothing about ports opened one: %+v", rules)
|
||||
}
|
||||
}
|
||||
|
||||
// A module may reflect a file another module put on the machine.
|
||||
//
|
||||
// The case this exists for: a resolver restarting when the mesh rewrites the names. Those are
|
||||
// computed by the mesh and belong to its module, not to the daemon's — so without this the
|
||||
// daemon would serve the names it started with for ever, while every check reported success.
|
||||
func TestAServiceCanReflectAnotherModulesFile(t *testing.T) {
|
||||
out, err := Resolution{Node: "anchor", Modules: []Manifest{{
|
||||
Module: "dnsmasq",
|
||||
Resources: []map[string]any{
|
||||
{"id": "config", "type": "file", "path": "/etc/dnsmasq.conf",
|
||||
"content": "x", "mode": "0644"},
|
||||
{"id": "run", "type": "service", "unit": "dnsmasq.service", "state": "running",
|
||||
"restart-on": []any{"config", "mesh-resolver.nodes"}},
|
||||
},
|
||||
}}}.Declaration(Rendering{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, r := range out {
|
||||
if r["type"] != "service" {
|
||||
continue
|
||||
}
|
||||
reflects := r["restart-on"].([]any)
|
||||
var own, other bool
|
||||
for _, id := range reflects {
|
||||
switch id.(string) {
|
||||
case "dnsmasq.config":
|
||||
own = true
|
||||
case "mesh-resolver.nodes":
|
||||
other = true
|
||||
}
|
||||
}
|
||||
if !own {
|
||||
t.Fatalf("its own file was not prefixed with its module: %v", reflects)
|
||||
}
|
||||
if !other {
|
||||
t.Fatalf("a file it named in full was prefixed again and now points at nothing: %v",
|
||||
reflects)
|
||||
}
|
||||
return
|
||||
}
|
||||
t.Fatal("no service in the declaration")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user