The assigner holds a move of a module of policy record from the judgement beside its digest, so it is judged by ADR 0377 §3's tests against the composition the node's have names, a held one never taking its place, and written again when the have changes or a person sets the policy (ADR 0539 clauses 2 and 14). The node-engine's own file operator-keys, from its setting operator-signers, carries the operator's public keys (clause 3).
128 lines
6.7 KiB
Go
128 lines
6.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
|
|
sdkdelivery "git.novox.be/novox/mesh-sdk/go/delivery"
|
|
)
|
|
|
|
// mailOn is a composition of mesh-mail on ace at fingerprint c, from the given resources, each saying its module.
|
|
func mailOn(t *testing.T, c byte, data bool, resources ...string) sdkdelivery.Composed {
|
|
t.Helper()
|
|
out := sdkdelivery.Composed{Node: "ace", Module: "mesh-mail", FP: sdkdelivery.SourcePrefix2 + strings.Repeat(string(c), 64), Data: data}
|
|
for _, r := range resources {
|
|
out.Resources = append(out.Resources, json.RawMessage(r))
|
|
}
|
|
return out
|
|
}
|
|
|
|
const (
|
|
mailContainer = `{"id":"mesh-mail.server","type":"container","module":"mesh-mail","name":"mail","image":"mail@sha256:1111111111111111111111111111111111111111111111111111111111111111","volumes":["/var/lib/mesh-mail/spool:/spool"]}`
|
|
mailContainer2 = `{"id":"mesh-mail.server","type":"container","module":"mesh-mail","name":"mail","image":"mail@sha256:2222222222222222222222222222222222222222222222222222222222222222","volumes":["/var/lib/mesh-mail/spool:/spool"]}`
|
|
mailSpool = `{"id":"mesh-mail.spool","type":"directory","module":"mesh-mail","path":"/var/lib/mesh-mail/spool"}`
|
|
mailSpoolMoved = `{"id":"mesh-mail.spool","type":"directory","module":"mesh-mail","path":"/srv/mesh-mail/spool"}`
|
|
mailTools = `{"id":"mesh-mail.tools","type":"file","module":"mesh-mail","path":"/usr/lib/mesh-mail/tools.json","content":"a"}`
|
|
mailTools2 = `{"id":"mesh-mail.tools","type":"file","module":"mesh-mail","path":"/usr/lib/mesh-mail/tools.json","content":"b"}`
|
|
mailConfig = `{"id":"mesh-mail.conf","type":"file","module":"mesh-mail","path":"/var/lib/mesh-mail/spool/conf","content":"a"}`
|
|
mailConfig2 = `{"id":"mesh-mail.conf","type":"file","module":"mesh-mail","path":"/var/lib/mesh-mail/spool/conf","content":"b"}`
|
|
)
|
|
|
|
// 1945/AC12: captured pairs of compositions are judged by ADR 0377 §3's tests: a changed container recreates; with no
|
|
// container changed, a directory moved, a file within the data a container mounts changed, or the data declaration
|
|
// changed moves data; a tools file nothing mounts changed is neither.
|
|
func TestAMoveIsJudgedByWhatItRecreatesOrMoves(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
from, to sdkdelivery.Composed
|
|
want sdkdelivery.Judgement
|
|
}{
|
|
{"a new image", mailOn(t, 'a', true, mailContainer, mailSpool, mailTools), mailOn(t, 'b', true, mailContainer2, mailSpool, mailTools), sdkdelivery.JudgedRecreates},
|
|
{"a container gone", mailOn(t, 'a', true, mailContainer, mailSpool), mailOn(t, 'b', true, mailSpool), sdkdelivery.JudgedRecreates},
|
|
{"its spool moved", mailOn(t, 'a', true, mailContainer, mailSpool), mailOn(t, 'b', true, mailContainer, mailSpoolMoved), sdkdelivery.JudgedMovesData},
|
|
{"a file in its spool", mailOn(t, 'a', true, mailContainer, mailSpool, mailConfig), mailOn(t, 'b', true, mailContainer, mailSpool, mailConfig2), sdkdelivery.JudgedMovesData},
|
|
{"its data declaration", mailOn(t, 'a', false, mailContainer, mailSpool), mailOn(t, 'b', true, mailContainer, mailSpool), sdkdelivery.JudgedMovesData},
|
|
{"its tools only", mailOn(t, 'a', true, mailContainer, mailSpool, mailTools), mailOn(t, 'b', true, mailContainer, mailSpool, mailTools2), sdkdelivery.JudgedNeither},
|
|
}
|
|
for _, c := range cases {
|
|
if got, words := judged(c.from, c.to); got != c.want || words == "" {
|
|
t.Errorf("%s: judged %s (%q), want %s", c.name, got, words, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 1945/AC12: a move is judged against the composition the node's have names: one the node never ran is neither; one
|
|
// whose composition is gone cannot be judged and says why; and with A running, B held and C composed, C is judged
|
|
// against A, and judged again when the have changes.
|
|
func TestAMoveIsJudgedAgainstWhatTheNodeRuns(t *testing.T) {
|
|
at, _, _, haves := aPlace(t, "ace")
|
|
ctx := context.Background()
|
|
a := mailOn(t, 'a', true, mailContainer, mailSpool, mailTools)
|
|
b := mailOn(t, 'b', true, mailContainer2, mailSpool, mailTools)
|
|
c := mailOn(t, 'c', true, mailContainer, mailSpool, mailTools2)
|
|
write := func(comp sdkdelivery.Composed) (string, string) {
|
|
key, _ := sdkdelivery.ComposedKey("ace", comp.Module, comp.FP)
|
|
if _, err := writeComposed(ctx, at, key, comp); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
d, _ := sdkdelivery.ComposedDigestOf(comp)
|
|
return key, d
|
|
}
|
|
judgedNow := func(key string) sdkdelivery.ComposedDigest {
|
|
e, err := at.digests.Get(ctx, key)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
v, err := sdkdelivery.DecodeComposedDigest(e.Value())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return v
|
|
}
|
|
keyA, digestA := write(a)
|
|
if v := judgedNow(keyA); v.Judgement != sdkdelivery.JudgedNeither || v.Against != "" {
|
|
t.Errorf("a module the node never ran: %+v", v)
|
|
}
|
|
if v := judgedNow(keyA); v.Policy != "roll" || v.PolicyFrom != "default" {
|
|
t.Errorf("the policy beside the judgement: %+v", v)
|
|
}
|
|
haves["mesh-mail"] = sdkdelivery.Have{FP: a.FP, Composed: digestA, Phase: sdkdelivery.PhaseRunning, Health: sdkdelivery.HealthOK}
|
|
keyB, _ := write(b)
|
|
if v := judgedNow(keyB); v.Judgement != sdkdelivery.JudgedRecreates || v.Against != digestA {
|
|
t.Errorf("B against A: %+v", v)
|
|
}
|
|
// B is held (the assigner writes no want for it); the node still runs A.
|
|
keyC, digestC := write(c)
|
|
if v := judgedNow(keyC); v.Judgement != sdkdelivery.JudgedNeither || v.Against != digestA {
|
|
t.Errorf("C against A, B held: %+v", v)
|
|
}
|
|
// The have changes to C: every judgement of the module is written again, against what runs now (the have watch).
|
|
haves["mesh-mail"] = sdkdelivery.Have{FP: c.FP, Composed: digestC, Phase: sdkdelivery.PhaseRunning, Health: sdkdelivery.HealthOK}
|
|
if n, err := rejudge(ctx, at, "mesh-mail"); err != nil || n == 0 {
|
|
t.Fatalf("rejudged %d (%v)", n, err)
|
|
}
|
|
if v := judgedNow(keyC); v.Against != digestC || v.Judgement != sdkdelivery.JudgedNeither {
|
|
t.Errorf("C once the node runs it: %+v", v)
|
|
}
|
|
if v := judgedNow(keyB); v.Against != digestC || v.Judgement != sdkdelivery.JudgedRecreates {
|
|
t.Errorf("B once the node runs C: %+v", v)
|
|
}
|
|
// A person sets the module's policy: written beside every composition of it.
|
|
at.policy = func(string) (string, string, string) { return "record", "person", "its spool" }
|
|
if _, err := rejudge(ctx, at, "mesh-mail"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if v := judgedNow(keyB); v.Policy != "record" || v.PolicyFrom != "person" || v.PolicyWhy != "its spool" {
|
|
t.Errorf("the person's policy beside B: %+v", v)
|
|
}
|
|
// A have naming a composition the buckets no longer hold.
|
|
haves["mesh-mail"] = sdkdelivery.Have{FP: sdkdelivery.SourcePrefix2 + strings.Repeat("9", 64), Composed: "sha256:" + strings.Repeat("9", 64),
|
|
Phase: sdkdelivery.PhaseRunning, Health: sdkdelivery.HealthOK}
|
|
write(b)
|
|
if v := judgedNow(keyB); v.Judgement != sdkdelivery.JudgedCannot || !strings.Contains(v.Words, "gone") {
|
|
t.Errorf("against a composition that is gone: %+v", v)
|
|
}
|
|
}
|