State a tool check that waits for the operator as waiting (issue 386, ADR 0283)
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate pass: builds mesh-host → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed

A module whose only fault was a secret the operator had not given yet was
stated unhealthy, failed its first-node gate and was put back. The engine
now reads the tool's answer through mesh-sdk go/health and states such a
resource waiting, with what it waits for, on one look; a malformed wait
stays unhealthy.
This commit is contained in:
jochen
2026-10-10 21:11:02 +02:00
parent 9d8aa9d6f0
commit 3abb07f1e2
13 changed files with 246 additions and 63 deletions
+12 -11
View File
@@ -13,6 +13,7 @@ import (
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/health"
"github.com/novox/mesh-host/internal/declaration"
)
@@ -40,8 +41,8 @@ func portOf(t *testing.T, url string) int {
func lookOnce(t *testing.T, j *Judge) {
t.Helper()
for _, d := range j.due() {
ok, why := j.probes().Look(t.Context(), d.module, d.check)
j.Record(d.id, d.started, ok, why)
ok, why, waits := j.probes().Look(t.Context(), d.module, d.check)
j.Record(d.id, d.started, ok, why, waits...)
}
}
@@ -182,11 +183,11 @@ func TestNeverMoreLooksThanTheBudget(t *testing.T) {
func TestAToolCheckIsAskedOfTheNodeTools(t *testing.T) {
asked := ""
p := &Probes{AskTool: func(_ context.Context, module, tool string) (bool, string, error) {
p := &Probes{AskTool: func(_ context.Context, module, tool string) (health.Answer, error) {
asked = module + "." + tool
return false, "the admin refused the minted secret", nil
return health.Unhealthy("the admin refused the minted secret"), nil
}}
ok, why := p.Look(t.Context(), "keycloak", &declaration.Health{Kind: declaration.HealthTool, Tool: "keycloak_admin_health",
ok, why, _ := p.Look(t.Context(), "keycloak", &declaration.Health{Kind: declaration.HealthTool, Tool: "keycloak_admin_health",
Interval: "30s", Timeout: "5s", Looks: 2, Grace: "0s"})
if ok || asked != "keycloak.keycloak_admin_health" || !strings.Contains(why, "refused the minted secret") {
t.Fatalf("asked %q: %v %q", asked, ok, why)
@@ -198,16 +199,16 @@ func TestAToolCheckIsAskedOfTheNodeTools(t *testing.T) {
func TestAToolCheckThatGetsNoAnswerSaysHowLongItWaited(t *testing.T) {
check := &declaration.Health{Kind: declaration.HealthTool, Tool: "sensors_health",
Interval: "30s", Timeout: "5s", Looks: 2, Grace: "0s"}
p := &Probes{AskTool: func(context.Context, string, string) (bool, string, error) {
return false, "", fmt.Errorf("no answer from mesh.mod.sensors.tool.sensors_health.anchor: %w", context.DeadlineExceeded)
p := &Probes{AskTool: func(context.Context, string, string) (health.Answer, error) {
return health.Answer{}, fmt.Errorf("no answer from mesh.mod.sensors.tool.sensors_health.anchor: %w", context.DeadlineExceeded)
}}
if ok, why := p.Look(t.Context(), "sensors", check); ok || why != "no answer within 5s" {
if ok, why, _ := p.Look(t.Context(), "sensors", check); ok || why != "no answer within 5s" {
t.Fatalf("%v %q", ok, why)
}
p.AskTool = func(context.Context, string, string) (bool, string, error) {
return false, "", errors.New("no link to the bus is open")
p.AskTool = func(context.Context, string, string) (health.Answer, error) {
return health.Answer{}, errors.New("no link to the bus is open")
}
if ok, why := p.Look(t.Context(), "sensors", check); ok || why != "no link to the bus is open" {
if ok, why, _ := p.Look(t.Context(), "sensors", check); ok || why != "no link to the bus is open" {
t.Fatalf("%v %q", ok, why)
}
}