Replay the silent web application: its declared HTTP check raises it within two looks (hq ADR 0240 Phase B, issue 145) #55
@@ -105,6 +105,16 @@ var Register = []Replay{
|
||||
Asserts: "a container whose program exits at start is said unhealthy by the node-engine, and its build fails " +
|
||||
"its gate on the first machine and is put back there",
|
||||
Package: ".", Test: "TestReplayCrashLoop", Files: []string{"cmd/mesh-controller/replays_test.go"}},
|
||||
// A module's again: the silent web application, which no liveness and no TCP check sees, raised by the
|
||||
// HTTP check its module declares (ADR 0240 Phase B).
|
||||
{ID: "R145", Issue: 145, Kind: Liveness, Repository: "mesh-controller",
|
||||
Fix: "feat/health-the-field", Before: "origin/main",
|
||||
With: []Also{{Repository: "mesh-host", Fix: "feat/health-the-field", Before: "origin/main"}},
|
||||
What: "a web application accepted TCP and answered nothing for eleven hours while the mesh said its machine " +
|
||||
"healthy: its port was open and its program ran",
|
||||
Asserts: "a web server whose application never answers is said unhealthy by its declared HTTP check within two " +
|
||||
"looks, and the controller raises the module's condition on the second",
|
||||
Package: ".", Test: "TestReplaySilentWebApp", Files: []string{"cmd/mesh-controller/replays_test.go"}},
|
||||
}
|
||||
|
||||
// Find is the replay of that id, or of that issue.
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package replays
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// **R145 — a web application that accepts TCP and answers nothing is raised within two looks** (novox/hq
|
||||
// ADR 0240 Phase B, "how it is checked", rule 4; issue 145).
|
||||
//
|
||||
// For eleven hours a web application's port was open and its program ran while every request hung, and the
|
||||
// mesh said its machine was healthy; a person found it. Liveness cannot see it and a TCP check cannot either.
|
||||
// The replay is the whole chain, each half at its commit:
|
||||
//
|
||||
// 1. a web server whose application never answers is raised here, as the node-engine raises a module's;
|
||||
// 2. the node-engine at MESH_REPLAY_HOST looks at it with the HTTP check its module declares, two looks,
|
||||
// and writes what it states (its TestReplaySilentWebAppIsSaidUnhealthy) — an engine older than the
|
||||
// declared check states it alive and nothing more;
|
||||
// 3. the controller at MESH_REPLAY_CONTROLLER hears that statement on two looks in a row and raises the
|
||||
// module's condition on the second (its TestReplaySilentWebAppIsRaisedWithinTwoLooks).
|
||||
//
|
||||
// Needs a container runtime, Go, and MESH_TEST_POSTGRES for the controller's store; the container is removed
|
||||
// after, whatever happened.
|
||||
func TestReplaySilentWebApp(t *testing.T) {
|
||||
host := orDefault(os.Getenv("MESH_REPLAY_HOST"), filepath.Join("..", "..", "mesh-host"))
|
||||
controller := orDefault(os.Getenv("MESH_REPLAY_CONTROLLER"), filepath.Join("..", "..", "mesh-controller"))
|
||||
for _, dir := range []string{host, controller} {
|
||||
if _, err := os.Stat(filepath.Join(dir, "go.mod")); err != nil {
|
||||
t.Skipf("no checkout at %s (MESH_REPLAY_HOST and MESH_REPLAY_CONTROLLER name them)", dir)
|
||||
}
|
||||
}
|
||||
if os.Getenv("MESH_TEST_POSTGRES") == "" {
|
||||
t.Skip("no MESH_TEST_POSTGRES: the controller's half raises its condition in a store")
|
||||
}
|
||||
docker, ok := DockerFromEnv()
|
||||
if !ok {
|
||||
t.Skip("no container runtime: the web application is a container")
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(t.Context(), 10*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
// 1. The silent web application (mesh-host internal/liveness SilentWebImage, SilentWebProgram).
|
||||
const image = "busybox:1.36"
|
||||
if err := docker.Pull(ctx, image); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
name := fmt.Sprintf("mesh-replay-silent-web-%d", time.Now().UnixNano())
|
||||
id, address, err := docker.Start(ctx, Run{Image: image, Name: name, Restart: "unless-stopped",
|
||||
Labels: map[string]string{"mesh-host.id": "app.server"},
|
||||
Cmd: []string{"sh", "-c", "mkdir -p /www/cgi-bin && printf '#!/bin/sh\\nsleep 3600\\n' > /www/cgi-bin/app && " +
|
||||
"chmod +x /www/cgi-bin/app && exec httpd -f -p 8080 -h /www"}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer docker.Remove(context.Background(), id)
|
||||
|
||||
// 2. The node-engine at its commit looks at it, and says what it states.
|
||||
statement := filepath.Join(t.TempDir(), "statement.json")
|
||||
engine := exec.CommandContext(ctx, "go", "test", "-count=1", "-run", "^TestReplaySilentWebAppIsSaidUnhealthy$",
|
||||
"./internal/liveness/")
|
||||
engine.Dir = host
|
||||
engine.Env = append(os.Environ(), "MESH_REPLAY_CONTAINER="+name, "MESH_REPLAY_ADDRESS="+address,
|
||||
"MESH_REPLAY_STATEMENT="+statement, "GOFLAGS=")
|
||||
out, err := engine.CombinedOutput()
|
||||
switch {
|
||||
case err == nil && !strings.Contains(string(out), "no tests to run"):
|
||||
t.Logf("the node-engine said: %s", firstLineOf(statementOf(statement)))
|
||||
case judgesNothing(string(out)) || strings.Contains(string(out), "no tests to run"):
|
||||
// An engine older than the declared check: it states the application alive, and nothing more.
|
||||
t.Fatalf("the node-engine at this commit does not look at a declared HTTP check")
|
||||
default:
|
||||
t.Fatalf("the node-engine did not say the silent web application unhealthy: %v\n%s", err, lastOf(string(out), 12))
|
||||
}
|
||||
|
||||
// 3. The controller at its commit raises the module's condition from what the engine said.
|
||||
raise := exec.CommandContext(ctx, "go", "test", "-count=1", "-run", "^TestReplaySilentWebAppIsRaisedWithinTwoLooks$",
|
||||
"./cmd/mesh-controller/")
|
||||
raise.Dir = controller
|
||||
raise.Env = append(os.Environ(), "MESH_REPLAY_STATEMENT="+statement, "GOFLAGS=-mod=vendor", "GOPROXY=off")
|
||||
out, err = raise.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("the controller did not raise the silent web application within two looks: %v\n%s", err, lastOf(string(out), 12))
|
||||
}
|
||||
if strings.Contains(string(out), "no tests to run") {
|
||||
t.Fatal("the controller at this commit has no half of the replay to run")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user