mesh/delivery merged: source.main moved to novox/mesh-host@58b4f8b6bc07
mesh/check being carried to bf667a602d21 on 58b4f8b6bc07 | from 9a8b3870bad0
A reboot left the have running, so a unit that failed to start at boot was never started again and nothing said so. Baseline now asks the machine; the judge's look calls Agent.Reconcile once per down edge, leaving held and unwanted modules alone.
90 lines
4.5 KiB
Go
90 lines
4.5 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"testing"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
"github.com/novox/mesh-host/internal/liveness"
|
|
)
|
|
|
|
// 1957/AC2 and AC4 (unit; ADR 0568 §2, §4): the judge's look acts on a resource that goes down, once per edge. A repeated
|
|
// statement acts nothing, the edge clears when the resource is healthy again, and a resource held, restarting, starting or
|
|
// missing (the other path's) is no edge.
|
|
func testOnlyAResourceNewlyDownIsAnEdge(t *testing.T) {
|
|
state := func(id, s, reason string, missing bool) liveness.State {
|
|
return liveness.State{Resource: liveness.Resource{Module: "dnsmasq", ID: id}, State: s, Reason: reason, Missing: missing}
|
|
}
|
|
down := state("dnsmasq.unit", liveness.Unhealthy, liveness.ReasonDown, false)
|
|
steps := []struct {
|
|
name string
|
|
st []liveness.State
|
|
want []string
|
|
}{
|
|
{"down: an edge", []liveness.State{down}, []string{"dnsmasq"}},
|
|
{"said again while down: none", []liveness.State{down}, nil},
|
|
{"healthy again: clears", []liveness.State{state("dnsmasq.unit", liveness.Healthy, "", false)}, nil},
|
|
{"down again: an edge", []liveness.State{down}, []string{"dnsmasq"}},
|
|
{"held by a window: none", []liveness.State{state("dnsmasq.unit", liveness.Held, "", false)}, nil},
|
|
{"restarting: none", []liveness.State{state("dnsmasq.unit", liveness.Unhealthy, liveness.ReasonRestarting, false)}, nil},
|
|
{"starting: none", []liveness.State{state("dnsmasq.unit", liveness.Starting, "", false)}, nil},
|
|
{"missing: the other path's", []liveness.State{state("dnsmasq.unit", liveness.Unhealthy, liveness.ReasonDown, true)}, nil},
|
|
}
|
|
var last map[string]bool
|
|
for _, s := range steps {
|
|
var got []string
|
|
got, last = newlyInactive(last, liveness.Statement{Resources: s.st})
|
|
if len(got) != len(s.want) || (len(got) == 1 && got[0] != s.want[0]) {
|
|
t.Fatalf("%s: acted on %v, want %v", s.name, got, s.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
type fakeRuntime struct {
|
|
units map[string]liveness.Observed
|
|
containers map[string]liveness.Observed
|
|
err error
|
|
}
|
|
|
|
func (f fakeRuntime) Containers(_ context.Context, _ []string) (map[string]liveness.Observed, error) {
|
|
return f.containers, f.err
|
|
}
|
|
func (f fakeRuntime) Units(_ context.Context, _, _ string, _ []string) (map[string]liveness.Observed, error) {
|
|
return f.units, f.err
|
|
}
|
|
|
|
// 1957/AC1 (unit; ADR 0568 §1): the machine's facts say a declared-running resource is not active when its unit is stopped
|
|
// or unknown to the manager; everything up, a restart in progress, a read that failed and a container held by a
|
|
// maintenance window are not.
|
|
func testTheMachineShowsAResourceNotActive(t *testing.T) {
|
|
svc := []byte(`{"id":"dnsmasq.unit","type":"service","unit":"dnsmasq.service","state":"running","module":"dnsmasq"}`)
|
|
ctr := []byte(`{"id":"dnsmasq.box","type":"container","name":"box","image":"x@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","module":"dnsmasq"}`)
|
|
up := liveness.Observed{Found: true, Running: true}
|
|
for _, c := range []struct {
|
|
name string
|
|
rt fakeRuntime
|
|
held map[string]bool
|
|
want bool
|
|
}{
|
|
{"all up", fakeRuntime{units: map[string]liveness.Observed{"dnsmasq.service": up}, containers: map[string]liveness.Observed{"box": up}}, nil, false},
|
|
{"unit inactive", fakeRuntime{units: map[string]liveness.Observed{"dnsmasq.service": {Found: true}}, containers: map[string]liveness.Observed{"box": up}}, nil, true},
|
|
{"unit unknown to the manager", fakeRuntime{containers: map[string]liveness.Observed{"box": up}}, nil, true},
|
|
{"unit restarting", fakeRuntime{units: map[string]liveness.Observed{"dnsmasq.service": {Found: true, Restarting: true}}, containers: map[string]liveness.Observed{"box": up}}, nil, false},
|
|
{"container not running", fakeRuntime{units: map[string]liveness.Observed{"dnsmasq.service": up}, containers: map[string]liveness.Observed{"box": {Found: true}}}, nil, true},
|
|
{"container held by a window", fakeRuntime{units: map[string]liveness.Observed{"dnsmasq.service": up}, containers: map[string]liveness.Observed{"box": {Found: true}}}, map[string]bool{"box": true}, false},
|
|
{"unread", fakeRuntime{err: errors.New("the service manager could not be read")}, nil, false},
|
|
} {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
d, err := declarationOf(delivery.Composed{Node: "n1", Module: "dnsmasq", Resources: []json.RawMessage{svc, ctr}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := anyInactive(context.Background(), c.rt, liveness.LongRunning(d, nil), c.held); got != c.want {
|
|
t.Fatalf("inactive = %v, want %v", got, c.want)
|
|
}
|
|
})
|
|
}
|
|
}
|