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) } }) } }