On 2026-10-09 the control node's route-proxy failed every lookup against 10.10.0.1 alone for twenty minutes while the machine's file listed both mesh resolvers; its copy lives under the runtime's root-only directory and no tool read it. docker_resolv_conf reads /etc/resolv.conf as the container sees it through the runtime's archive (docker cp, no exec), parses it, and says whether it lists the same resolvers as the machine.
84 lines
3.2 KiB
Go
84 lines
3.2 KiB
Go
package main
|
|
|
|
import (
|
|
"archive/tar"
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func archiveOf(t *testing.T, h tar.Header, body string) string {
|
|
t.Helper()
|
|
var buf bytes.Buffer
|
|
w := tar.NewWriter(&buf)
|
|
h.Size = int64(len(body))
|
|
if h.Typeflag == tar.TypeSymlink {
|
|
h.Size = 0
|
|
}
|
|
if err := w.WriteHeader(&h); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if h.Typeflag != tar.TypeSymlink {
|
|
if _, err := w.Write([]byte(body)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
w.Close()
|
|
return buf.String()
|
|
}
|
|
|
|
// Issue 348: route-proxy asked 10.10.0.1 alone while the machine listed both mesh resolvers; its copy
|
|
// of the file could not be read by anything on the mesh.
|
|
func TestAContainersResolverFileIsReadThroughTheArchiveAndComparedWithTheMachines(t *testing.T) {
|
|
inside := "# Generated by Docker Engine.\nnameserver 10.10.0.1\nsearch novox.internal\noptions timeout:1 attempts:2\n"
|
|
f := (&fake{}).on("docker cp route-proxy:/etc/resolv.conf -",
|
|
Ran{Stdout: archiveOf(t, tar.Header{Name: "resolv.conf", Typeflag: tar.TypeReg, Mode: 0o644}, inside)})
|
|
c := client(f, 1000)
|
|
c.ReadFile = func(p string) ([]byte, error) {
|
|
if p != HostResolvConf {
|
|
t.Fatalf("read %s", p)
|
|
}
|
|
return []byte("nameserver 10.10.0.1\nnameserver 10.10.0.2\noptions timeout:1 attempts:2 edns0\n"), nil
|
|
}
|
|
got, err := c.ResolvConfOf(context.Background(), "route-proxy")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rc := got["resolv_conf"].(ResolvConf)
|
|
if strings.Join(rc.Nameservers, ",") != "10.10.0.1" || strings.Join(rc.Search, ",") != "novox.internal" ||
|
|
strings.Join(rc.Options, ",") != "timeout:1,attempts:2" || rc.Raw != inside {
|
|
t.Fatalf("parsed: %+v", rc)
|
|
}
|
|
if got["same_as_machine"] != false || got["note"] == nil {
|
|
t.Fatalf("a copy listing fewer resolvers than the machine is not said: %v", got)
|
|
}
|
|
if f.ran("docker exec") {
|
|
t.Fatal("read by exec, not through the archive")
|
|
}
|
|
}
|
|
|
|
func TestAResolverFileThatCannotBeReadIsSaidPlainly(t *testing.T) {
|
|
c := client((&fake{}).on("docker cp", Ran{Status: 1, Stderr: "Error response from daemon: No such container: nope\n"}), 1000)
|
|
if _, err := c.ResolvConfOf(context.Background(), "nope"); err == nil || !strings.Contains(err.Error(), "could not be read") {
|
|
t.Fatalf("a missing container: %v", err)
|
|
}
|
|
link := archiveOf(t, tar.Header{Name: "resolv.conf", Typeflag: tar.TypeSymlink, Linkname: "/run/systemd/resolve/stub-resolv.conf"}, "")
|
|
c = client((&fake{}).on("docker cp", Ran{Stdout: link}), 1000)
|
|
if _, err := c.ResolvConfOf(context.Background(), "x"); err == nil || !strings.Contains(err.Error(), "link to") {
|
|
t.Fatalf("a link inside: %v", err)
|
|
}
|
|
c = client((&fake{}).on("docker cp", Ran{Stdout: "not a tar"}), 1000)
|
|
if _, err := c.ResolvConfOf(context.Background(), "x"); err == nil {
|
|
t.Fatal("garbage was read as a file")
|
|
}
|
|
ok := archiveOf(t, tar.Header{Name: "resolv.conf", Typeflag: tar.TypeReg, Mode: 0o644}, "nameserver 10.10.0.1\n")
|
|
c = client((&fake{}).on("docker cp", Ran{Stdout: ok}), 1000)
|
|
c.ReadFile = func(string) ([]byte, error) { return nil, errors.New("permission denied") }
|
|
got, err := c.ResolvConfOf(context.Background(), "x")
|
|
if err != nil || !strings.Contains(got["machine"].(string), "could not be read") || got["same_as_machine"] != nil {
|
|
t.Fatalf("an unreadable machine file: %v, %v", got, err)
|
|
}
|
|
}
|