One name per thing, per the HQ glossary: the module/container/image/binary/repo becomes mesh-controller, the seat the-controller, and the store+broker pair the foundation (embedded base bundles, default template and example lock renamed with their go:embed directives). No behaviour change — a pure vocabulary rename. Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
171 lines
5.9 KiB
Go
171 lines
5.9 KiB
Go
package bootstrap
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/novox/mesh-host/internal/declaration"
|
|
)
|
|
|
|
func foundation(t *testing.T) *declaration.Declaration {
|
|
t.Helper()
|
|
out, err := Rewrite(theRealBundle(t), held)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return out.Declaration
|
|
}
|
|
|
|
// **A container that is up is not a control plane that replies**, and this project has paid for
|
|
// that distinction more than once. A runtime reports a container running from the moment its
|
|
// process starts — before it has opened a database, and before it has failed to.
|
|
func TestAContainerThatIsUpIsNotAControlPlaneThatReplies(t *testing.T) {
|
|
previous := answerEvery
|
|
answerEvery = time.Millisecond
|
|
defer func() { answerEvery = previous }()
|
|
|
|
runtime := &asked{answer: func(_ string, args []string) (string, error) {
|
|
switch args[0] {
|
|
case "inspect":
|
|
return "true running\n", nil
|
|
case "exec":
|
|
// Up, and saying nothing. The program inside is not answering.
|
|
return "", errors.New("exit status 1")
|
|
}
|
|
return "", fmt.Errorf("unexpected command: %v", args)
|
|
}}
|
|
|
|
_, err := Verify(context.Background(), foundation(t), runtime.run,
|
|
time.Second, 0, func(string) {})
|
|
if err == nil {
|
|
t.Fatal("every container was running, nothing answered, and the foundation was reported up")
|
|
}
|
|
for _, wanted := range []string{"mesh-controller", "Running is not replying", "docker logs"} {
|
|
if !strings.Contains(err.Error(), wanted) {
|
|
t.Errorf("the failure does not mention %q:\n%v", wanted, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Exit zero and silence is not an answer either. A program that returns nothing has not been asked
|
|
// anything, and treating it as success is the same fault one level down.
|
|
func TestAControlPlaneThatSaysNothingHasNotAnswered(t *testing.T) {
|
|
previous := answerEvery
|
|
answerEvery = time.Millisecond
|
|
defer func() { answerEvery = previous }()
|
|
|
|
runtime := &asked{answer: func(_ string, args []string) (string, error) {
|
|
if args[0] == "inspect" {
|
|
return "true running\n", nil
|
|
}
|
|
return " \n", nil
|
|
}}
|
|
|
|
if _, err := Verify(context.Background(), foundation(t), runtime.run,
|
|
time.Second, 0, func(string) {}); err == nil {
|
|
t.Fatal("a control plane that exited zero without saying anything was accepted")
|
|
}
|
|
}
|
|
|
|
// The foundation answering is the whole point, and what it said is reported rather than asserted.
|
|
func TestAFoundationThatIsUpAndAnsweringIsAccepted(t *testing.T) {
|
|
runtime := &asked{answer: func(_ string, args []string) (string, error) {
|
|
if args[0] == "inspect" {
|
|
return "true running\n", nil
|
|
}
|
|
return "1 node, 0 waiting\n", nil
|
|
}}
|
|
|
|
verified, err := Verify(context.Background(), foundation(t), runtime.run,
|
|
time.Second, 0, func(string) {})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Three long-running containers: the store, the broker and the TEMPORARY control plane. The
|
|
// run-once and scheduled shapes are excluded on purpose — a step that has exited is not a
|
|
// fault. The name is `temp-mesh-controller` because the permanent one is a module and takes the
|
|
// plain name (novox/hq ADR 0067), which is what makes the two of them coexist at all.
|
|
want := []string{"mesh-store", "mesh-broker", "temp-mesh-controller"}
|
|
if len(verified.Running) != len(want) {
|
|
t.Fatalf("confirmed %v running, want %v", verified.Running, want)
|
|
}
|
|
for i := range want {
|
|
if verified.Running[i] != want[i] {
|
|
t.Errorf("confirmed %v running, want %v", verified.Running, want)
|
|
}
|
|
}
|
|
if verified.Answered != "1 node, 0 waiting" {
|
|
t.Errorf("the control plane's reply is reported as %q", verified.Answered)
|
|
}
|
|
}
|
|
|
|
// A control plane that is still opening its stores is waited for, not refused. Refusing on the
|
|
// first attempt would make a correct bootstrap fail for being observed too early.
|
|
func TestAControlPlaneThatIsStillStartingIsWaitedFor(t *testing.T) {
|
|
previous := answerEvery
|
|
answerEvery = time.Millisecond
|
|
defer func() { answerEvery = previous }()
|
|
|
|
attempts := 0
|
|
runtime := &asked{answer: func(_ string, args []string) (string, error) {
|
|
if args[0] == "inspect" {
|
|
return "true running\n", nil
|
|
}
|
|
attempts++
|
|
if attempts < 3 {
|
|
return "", errors.New("exit status 1")
|
|
}
|
|
return "1 node\n", nil
|
|
}}
|
|
|
|
if _, err := Verify(context.Background(), foundation(t), runtime.run,
|
|
time.Second, time.Second, func(string) {}); err != nil {
|
|
t.Fatalf("a control plane that answered on the third ask was refused: %v", err)
|
|
}
|
|
}
|
|
|
|
// A container that exited is named with what it IS, so somebody can go and read its logs rather
|
|
// than being told only that something is not what it should be.
|
|
func TestAContainerThatExitedIsNamedWithItsState(t *testing.T) {
|
|
runtime := &asked{answer: func(_ string, args []string) (string, error) {
|
|
if args[0] == "inspect" && args[len(args)-1] == "mesh-broker" {
|
|
return "false exited\n", nil
|
|
}
|
|
if args[0] == "inspect" {
|
|
return "true running\n", nil
|
|
}
|
|
return "", fmt.Errorf("unexpected command: %v", args)
|
|
}}
|
|
|
|
_, err := Verify(context.Background(), foundation(t), runtime.run,
|
|
time.Second, 0, func(string) {})
|
|
if err == nil {
|
|
t.Fatal("a container that had exited was reported as part of a running foundation")
|
|
}
|
|
if !strings.Contains(err.Error(), "mesh-broker") || !strings.Contains(err.Error(), "exited") {
|
|
t.Errorf("the failure does not say which container is in what state: %v", err)
|
|
}
|
|
}
|
|
|
|
// The control plane is asked by running the binary in its own image directly, because the image is
|
|
// `FROM scratch` and has no shell for a command line to be interpreted by.
|
|
func TestTheControlPlaneIsAskedByRunningItsOwnBinary(t *testing.T) {
|
|
runtime := &asked{answer: func(_ string, args []string) (string, error) {
|
|
if args[0] == "inspect" {
|
|
return "true running\n", nil
|
|
}
|
|
return "1 node\n", nil
|
|
}}
|
|
if _, err := Verify(context.Background(), foundation(t), runtime.run,
|
|
time.Second, 0, func(string) {}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !runtime.ran("docker exec " + TempPrefix + "mesh-controller " + controlPlaneBinary + " status") {
|
|
t.Errorf("the control plane was never asked anything: %v", runtime.commands)
|
|
}
|
|
}
|