140 lines
5.9 KiB
Go
140 lines
5.9 KiB
Go
package assign
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
)
|
|
|
|
func fpOf(c byte) string { return delivery.SourcePrefix2 + strings.Repeat(string(c), 64) }
|
|
|
|
var (
|
|
fNew = fpOf('b')
|
|
fOld = fpOf('a')
|
|
)
|
|
|
|
// ready is a module mail at fNew on ace and g14 for which every rule holds: built, not held, granted, its provider
|
|
// store healthy on ace and the canary ace running fNew healthy.
|
|
func ready() (Module, Nodes) {
|
|
m := Module{Name: "mail", FP: fNew, Built: true, Granted: true, Nodes: []string{"g14", "ace"},
|
|
Manifest: catalogue.Manifest{Module: "mail", Requires: []string{"database"}}}
|
|
n := Nodes{
|
|
Online: map[string]bool{"ace": true, "g14": true},
|
|
Haves: map[string]map[string]delivery.Have{
|
|
"ace": {"mail": {FP: fNew, Phase: delivery.PhaseRunning, Health: delivery.HealthOK}, "store": {FP: fpOf('s'), Phase: delivery.PhaseRunning, Health: delivery.HealthOK}},
|
|
"g14": {"mail": {FP: fOld, Phase: delivery.PhaseRunning, Health: delivery.HealthOK}},
|
|
},
|
|
Providers: map[string]map[string][]string{"database": {"store": {"ace"}}},
|
|
}
|
|
return m, n
|
|
}
|
|
|
|
func wantedOld() map[string]delivery.Want {
|
|
return map[string]delivery.Want{"ace": {FP: fOld}, "g14": {FP: fOld}}
|
|
}
|
|
|
|
// 1297/B4: with every rule holding, both nodes' wants move to the new fingerprint (the canary ace runs it healthy, so
|
|
// g14 follows at once: step 0, ADR 0528 clause 3).
|
|
func TestEveryRuleHoldsSoTheWantsMove(t *testing.T) {
|
|
m, n := ready()
|
|
out := Wants(m, n, wantedOld(), nil)
|
|
for _, node := range []string{"ace", "g14"} {
|
|
if out.Wants[node].FP != fNew || out.Wants[node].Rule != 0 {
|
|
t.Errorf("%s's want = %+v, want %s", node, out.Wants[node], fNew)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 1297/B4, one test per rule: given every rule but N holds, no want moves to F, and the rule N is named.
|
|
func TestEachRuleHoldsTheWantBack(t *testing.T) {
|
|
for rule, breakIt := range map[int]func(*Module, *Nodes){
|
|
delivery.RuleBuildDone: func(m *Module, _ *Nodes) { m.Built = false },
|
|
delivery.RuleNotHeld: func(m *Module, _ *Nodes) { m.Held = true },
|
|
delivery.RuleCanary: func(_ *Module, n *Nodes) {
|
|
n.Haves["ace"]["mail"] = delivery.Have{FP: fOld, Phase: delivery.PhaseRunning, Health: delivery.HealthOK}
|
|
},
|
|
delivery.RuleProviders: func(_ *Module, n *Nodes) {
|
|
n.Haves["ace"]["store"] = delivery.Have{FP: fpOf('s'), Phase: delivery.PhaseRunning, Health: delivery.HealthWaits}
|
|
},
|
|
delivery.RuleGrant: func(m *Module, _ *Nodes) { m.Granted = false },
|
|
} {
|
|
m, n := ready()
|
|
breakIt(&m, &n)
|
|
out := Wants(m, n, wantedOld(), nil)
|
|
if w, ok := out.Wants["g14"]; ok && w.FP == fNew {
|
|
t.Errorf("rule %d broken: g14's want moved to F", rule)
|
|
}
|
|
if got := out.Rules["g14"].Rule; got != rule {
|
|
t.Errorf("rule %d broken: g14 is held back by rule %d (%s)", rule, got, out.Rules["g14"].Words)
|
|
}
|
|
if w := out.Wants["g14"]; w.FP != "" && (w.FP != fOld || w.Newest != fNew || w.Rule != rule) {
|
|
t.Errorf("rule %d broken: g14's want is %+v", rule, w)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 1297/B4, ADR 0435 §3: a canary that is offline is passed over: the next node by name is the canary, and moves first.
|
|
func TestAnOfflineCanaryIsPassedOver(t *testing.T) {
|
|
m, n := ready()
|
|
n.Online["ace"] = false
|
|
n.Haves["ace"]["mail"] = delivery.Have{FP: fOld, Phase: delivery.PhaseRunning, Health: delivery.HealthOK}
|
|
n.Providers["database"]["store"] = []string{"ace", "g14"}
|
|
n.Haves["g14"]["store"] = delivery.Have{FP: fpOf('s'), Phase: delivery.PhaseRunning, Health: delivery.HealthOK}
|
|
if c := Canary(m.Nodes, n.Online); c != "g14" {
|
|
t.Fatalf("canary = %q, want g14", c)
|
|
}
|
|
out := Wants(m, n, wantedOld(), nil)
|
|
if out.Wants["g14"].FP != fNew {
|
|
t.Errorf("the canary g14's want = %+v", out.Wants["g14"])
|
|
}
|
|
if out.Rules["ace"].Rule != delivery.RuleCanary {
|
|
t.Errorf("ace, behind the canary g14, is held back by %+v", out.Rules["ace"])
|
|
}
|
|
}
|
|
|
|
// 1297/B4: a failed health check of F holds F and sets the nodes that want it back to their last healthy fingerprint.
|
|
func TestAFailedHealthHoldsAndSetsBack(t *testing.T) {
|
|
m, n := ready()
|
|
n.Haves["ace"]["mail"] = delivery.Have{FP: fNew, Phase: delivery.PhaseRunning, Health: delivery.HealthFailed, Detail: "connection refused"}
|
|
out := Wants(m, n, map[string]delivery.Want{"ace": {FP: fNew}, "g14": {FP: fOld}}, map[string]string{"ace": fOld})
|
|
if out.Held == nil || out.Held.Node != "ace" || out.Held.Words != "connection refused" {
|
|
t.Fatalf("held = %+v", out.Held)
|
|
}
|
|
if w := out.Wants["ace"]; w.FP != fOld || w.Newest != fNew || w.Rule != delivery.RuleNotHeld {
|
|
t.Errorf("ace's want = %+v, want set back to the last healthy fingerprint", w)
|
|
}
|
|
if w, ok := out.Wants["g14"]; ok && w.FP == fNew {
|
|
t.Errorf("g14 moved to a held fingerprint: %+v", w)
|
|
}
|
|
}
|
|
|
|
// 1297/B4: a module holding data is not set back; its want stays and says why.
|
|
func TestADataModuleIsNotSetBack(t *testing.T) {
|
|
m, n := ready()
|
|
m.Manifest.Data = &catalogue.Data{Own: []catalogue.DataItem{{ID: "mail", Class: catalogue.ClassIrreplaceable}}}
|
|
n.Haves["ace"]["mail"] = delivery.Have{FP: fNew, Phase: delivery.PhaseRunning, Health: delivery.HealthFailed}
|
|
out := Wants(m, n, map[string]delivery.Want{"ace": {FP: fNew}}, map[string]string{"ace": fOld})
|
|
if out.Held == nil {
|
|
t.Fatal("F was not held")
|
|
}
|
|
if w := out.Wants["ace"]; w.FP != fNew || !strings.Contains(w.Words, "holds data") {
|
|
t.Errorf("ace's want = %+v, want it left at F, saying it holds data", w)
|
|
}
|
|
}
|
|
|
|
// 1297/B4, ADR 0408 §4: a node that leaves the module's nodes gets its want written removed, never deleted; a want that
|
|
// is already as the rules give it is not written again.
|
|
func TestALeavingNodeIsRemovedAndNothingIsWrittenTwice(t *testing.T) {
|
|
m, n := ready()
|
|
m.Nodes = []string{"ace"}
|
|
out := Wants(m, n, map[string]delivery.Want{"ace": {V: 1, FP: fNew}, "g14": {FP: fOld}}, nil)
|
|
if w := out.Wants["g14"]; !w.Removed {
|
|
t.Errorf("g14's want = %+v, want removed", w)
|
|
}
|
|
if _, ok := out.Wants["ace"]; ok {
|
|
t.Errorf("ace's want, unchanged, was written again: %+v", out.Wants["ace"])
|
|
}
|
|
}
|