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.
97 lines
4.4 KiB
Go
97 lines
4.4 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
|
|
"github.com/novox/mesh-host/internal/liveness"
|
|
"github.com/novox/mesh-host/internal/store"
|
|
)
|
|
|
|
// The record check reads the engine's applied state: a composition one of whose resources has no record there is not
|
|
// recorded (issue 1945).
|
|
func TestACompositionIsRecordedOnlyWhenEveryResourceIs(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "state.json")
|
|
var s store.State
|
|
s.Record(store.Applied{ID: "ca-trust.trust", Type: "file", Target: "/etc/ca"})
|
|
if err := store.Save(path, s); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
c := delivery.Composed{Node: "ace", Module: "ca-trust", Resources: []json.RawMessage{
|
|
json.RawMessage(`{"id":"ca-trust.trust","type":"file","module":"ca-trust","path":"/etc/ca","mode":"0644","content":"x"}`)}}
|
|
recorded := recordedIn(path, func(string) {})
|
|
if !recorded(context.Background(), c) {
|
|
t.Fatal("a composition whose every resource has a record was not recorded")
|
|
}
|
|
c.Resources = append(c.Resources, json.RawMessage(
|
|
`{"id":"ca-trust.more","type":"file","module":"ca-trust","path":"/etc/ca2","mode":"0644","content":"y"}`))
|
|
if recorded(context.Background(), c) {
|
|
t.Fatal("a composition with a resource the state holds no record of was recorded")
|
|
}
|
|
}
|
|
|
|
// The modules applied again are those with a missing resource, each once; one down but there is not among them.
|
|
func TestTheModulesAppliedAgainAreThoseWithAMissingResource(t *testing.T) {
|
|
t.Run("1957: the machine shows a resource not active", testTheMachineShowsAResourceNotActive)
|
|
st := liveness.Statement{Resources: []liveness.State{
|
|
{Resource: liveness.Resource{Module: "matrix", ID: "matrix.homeserver"}, State: liveness.Unhealthy, Missing: true},
|
|
{Resource: liveness.Resource{Module: "matrix", ID: "matrix.db"}, State: liveness.Unhealthy, Missing: true},
|
|
{Resource: liveness.Resource{Module: "ca-trust", ID: "ca-trust.trust"}, State: liveness.Unhealthy, Missing: true},
|
|
{Resource: liveness.Resource{Module: "web", ID: "web.server"}, State: liveness.Unhealthy},
|
|
}}
|
|
if got := missingModules(st); len(got) != 2 || got[0] != "ca-trust" || got[1] != "matrix" {
|
|
t.Fatalf("applied again: %v", got)
|
|
}
|
|
}
|
|
|
|
// A module is applied again only for a resource newly missing: one still missing at the next change of the statement is
|
|
// not acted on again, so a resource that cannot be made is not applied (and backed up) at every unrelated change. One
|
|
// that comes back and goes again is newly missing again (the review of issue 1945).
|
|
func TestOnlyANewlyMissingResourceAppliesItsModuleAgain(t *testing.T) {
|
|
t.Run("1957: a resource newly down is an edge", testOnlyAResourceNewlyDownIsAnEdge)
|
|
missing := func(ids ...string) liveness.Statement {
|
|
var st liveness.Statement
|
|
for _, id := range ids {
|
|
st.Resources = append(st.Resources, liveness.State{Resource: liveness.Resource{Module: "matrix", ID: id},
|
|
State: liveness.Unhealthy, Missing: true})
|
|
}
|
|
return st
|
|
}
|
|
var last map[string]bool
|
|
var got []string
|
|
got, last = newlyMissing(last, missing("matrix.homeserver"))
|
|
if len(got) != 1 {
|
|
t.Fatalf("first missing: %v", got)
|
|
}
|
|
got, last = newlyMissing(last, missing("matrix.homeserver"))
|
|
if len(got) != 0 {
|
|
t.Fatalf("still missing was acted on again: %v", got)
|
|
}
|
|
got, last = newlyMissing(last, missing())
|
|
got, last = newlyMissing(last, missing("matrix.homeserver"))
|
|
if len(got) != 1 {
|
|
t.Fatalf("missing again after it came back: %v", got)
|
|
}
|
|
}
|
|
|
|
// A file the provisioner client fills is left to it by the apply and has no record until the client writes it: it does
|
|
// not make its composition unrecorded (the review of issue 1945).
|
|
func TestAFilledFileDoesNotMakeItsCompositionUnrecorded(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "state.json")
|
|
var s store.State
|
|
s.Record(store.Applied{ID: "ca-trust.trust", Type: "file", Target: "/etc/ca"})
|
|
if err := store.Save(path, s); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
c := delivery.Composed{Node: "ace", Module: "ca-trust", Filled: []string{"/etc/ca-filled"}, Resources: []json.RawMessage{
|
|
json.RawMessage(`{"id":"ca-trust.trust","type":"file","module":"ca-trust","path":"/etc/ca","mode":"0644","content":"x"}`),
|
|
json.RawMessage(`{"id":"ca-trust.filled","type":"file","module":"ca-trust","path":"/etc/ca-filled","mode":"0644","owner-at-runtime":"owned"}`)}}
|
|
if !recordedIn(path, func(string) {})(context.Background(), c) {
|
|
t.Fatal("a filled file the client has not written made its composition unrecorded")
|
|
}
|
|
}
|