package bootstrap import ( "context" "errors" "strings" "testing" "time" "github.com/novox/mesh-host/internal/declaration" ) // An installed package is not a capability (novox/hq 04-ISSUES/007). The daemon is asked, and a // machine where it does not answer is refused before anything is loaded, written or applied. // // The refusal has to be plain, because the person reading it is standing in front of a machine // that will not work: it says what was asked, what came back, that re-running is safe, and names // the record that explains why an installed docker is not enough. func TestPreflightRefusesPlainlyWhenTheRuntimeDoesNotAnswer(t *testing.T) { silent := func(context.Context, string, ...string) (string, error) { return "", errors.New("Cannot connect to the Docker daemon at unix:///var/run/docker.sock") } // No wait, so this is one attempt: what is being tested is the refusal, not the patience. err := waitForRuntime(context.Background(), silent, time.Second, 0, func(string) {}) if err == nil { t.Fatal("a machine whose container runtime does not answer was accepted") } for _, wanted := range []string{ "no container runtime that answers", "Cannot connect to the Docker daemon", "04-ISSUES/007", "idempotent", } { if !strings.Contains(err.Error(), wanted) { t.Errorf("the refusal does not mention %q:\n%v", wanted, err) } } } // And a runtime that is merely slow to start is waited for rather than refused. // // A socket-activated daemon queued behind the network is not absent, it is a few seconds away. // Refusing on the first attempt would make a correct bootstrap fail for being observed too early — // and `docker load` against such a daemon blocks silently rather than failing, which is how one // became a 35-minute silence (04-ISSUES/024). func TestARuntimeThatIsStillStartingIsWaitedFor(t *testing.T) { previous := runtimeAskEvery runtimeAskEvery = time.Millisecond defer func() { runtimeAskEvery = previous }() attempts := 0 slow := func(context.Context, string, ...string) (string, error) { attempts++ if attempts < 3 { return "", errors.New("Cannot connect to the Docker daemon") } return "27.0.3\n", nil } var said []string if err := waitForRuntime(context.Background(), slow, time.Second, time.Second, func(line string) { said = append(said, line) }); err != nil { t.Fatalf("a runtime that answered on the third ask was refused: %v", err) } if attempts != 3 { t.Errorf("the runtime was asked %d time(s)", attempts) } if !strings.Contains(strings.Join(said, "\n"), "27.0.3") { t.Errorf("the version the daemon reported was not said back: %v", said) } } // What has to be reachable is what the bundle actually names, not "the internet". // // The mesh's own image is carried and nothing serves it, so asking a registry about it would be // asking a question with no answer — which is the whole point of naming an image by the digest of // its own configuration. func TestOnlyTheRegistriesTheBundleNamesAreAskedAbout(t *testing.T) { parsed, err := declaration.ParseFileTrusted([]byte(`{"declaration":1,"resources":[ {"id":"store","type":"container","name":"mesh-store","image":"postgres@sha256:` + strings.Repeat("7", 64) + `"}, {"id":"broker","type":"container","name":"mesh-broker","image":"192.0.2.250:5000/lavinmq@sha256:` + strings.Repeat("8", 64) + `"}, {"id":"control-plane","type":"container","name":"mesh-control","image":"` + held + `"} ]}`)) if err != nil { t.Fatal(err) } got := registriesIn(parsed) want := []string{DefaultRegistry, "192.0.2.250:5000"} if len(got) != len(want) { t.Fatalf("asked about %v, want %v", got, want) } for i := range want { if got[i] != want[i] { t.Errorf("asked about %v, want %v", got, want) } } } func TestWhereAnImageWouldBeFetchedFrom(t *testing.T) { // The container runtime's own rule: the part before the first slash is a registry host if it // has a dot, a port, or is localhost. Getting this wrong means dialling a hostname that is // really the first half of a repository name, and refusing a machine that is fine. for _, c := range []struct { reference string host string served bool }{ {"postgres@sha256:" + strings.Repeat("a", 64), DefaultRegistry, true}, {"cloudamqp/lavinmq@sha256:" + strings.Repeat("a", 64), DefaultRegistry, true}, {"192.0.2.250:5000/postgres@sha256:" + strings.Repeat("a", 64), "192.0.2.250:5000", true}, {"localhost/mesh-control@sha256:" + strings.Repeat("a", 64), "localhost:443", true}, {"registry.example.com/a/b@sha256:" + strings.Repeat("a", 64), "registry.example.com:443", true}, // Held by this machine. Nothing serves it, and nothing can. {"sha256:" + strings.Repeat("a", 64), "", false}, {"", "", false}, } { host, served := registryOf(c.reference) if host != c.host || served != c.served { t.Errorf("%q → (%q, %v), want (%q, %v)", c.reference, host, served, c.host, c.served) } } }