With no send left nothing else writes a missed grant, so the take-in of a done key returns it and tries again every half minute under an urgent condition until it is written. plan-moved and sent go with the plans. The controller's first-start deletions are said on a granted subject, a node hears only its backup holder, and the operator's account writes the hand path, an object replaced included (ADR 0539 clauses 2, 7, 10 and 13).
150 lines
5.8 KiB
Go
150 lines
5.8 KiB
Go
package broker
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
)
|
|
|
|
// 1945/AC1: a node's composition bucket keeps five revisions of a key and is named for its node; the bucket of digests
|
|
// likewise; a node name a subject would widen is refused.
|
|
func TestTheCompositionBuckets(t *testing.T) {
|
|
cfg, err := ComposedConfig("ace")
|
|
if err != nil || cfg.Bucket != "composed-ace" || cfg.History != 5 {
|
|
t.Fatalf("ComposedConfig(ace) = %+v, %v", cfg, err)
|
|
}
|
|
if d := ComposedDigestsConfig(); d.Bucket != delivery.ControllerComposedBucket || d.History != 5 {
|
|
t.Errorf("ComposedDigestsConfig = %+v", d)
|
|
}
|
|
if _, err := ComposedConfig("a.b"); err == nil {
|
|
t.Errorf("a dotted node got a bucket")
|
|
}
|
|
}
|
|
|
|
// 1945/AC1: a node-engine reads its own compositions and its own wants and nothing of another node's, nor the digests;
|
|
// the controller writes a node's bucket by its own subjects.
|
|
func TestANodeReadsOnlyItsOwnCompositions(t *testing.T) {
|
|
reads, err := ComposedReads("ace")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
all := strings.Join(reads, " ")
|
|
for _, want := range []string{"KV_composed-ace", "$KV.mesh-delivery-assigner_wants.want.ace.>"} {
|
|
if !strings.Contains(all, want) {
|
|
t.Errorf("a node-engine of ace cannot reach %s: %v", want, reads)
|
|
}
|
|
}
|
|
for _, never := range []string{"composed-g14", delivery.ControllerComposedBucket, "$KV.composed-ace", "want.*", "want.>"} {
|
|
if strings.Contains(all, never) {
|
|
t.Errorf("a node-engine of ace is granted %s: %v", never, reads)
|
|
}
|
|
}
|
|
if w, err := ComposedWrites("ace"); err != nil || w != "$KV.composed-ace.>" {
|
|
t.Errorf("ComposedWrites(ace) = %q, %v", w, err)
|
|
}
|
|
if _, err := ComposedReads("a*"); err == nil {
|
|
t.Errorf("a node name with a wildcard was granted")
|
|
}
|
|
}
|
|
|
|
// 1945 (for issue 1944): a node-engine asks its own machine's backup holder to back a data module up before it moves
|
|
// and when it last was, and no other machine's.
|
|
func TestANodeAgentAsksItsOwnBackupHolder(t *testing.T) {
|
|
asks, err := NodeAgentAsks("ace")
|
|
if err != nil || strings.Join(asks, " ") != "mesh.seat.node-backup.tool.now.ace mesh.seat.node-backup.tool.backed-up.ace" {
|
|
t.Errorf("NodeAgentAsks(ace) = %v, %v", asks, err)
|
|
}
|
|
p, err := PermissionsFor(Principal{Kind: KindNode, Node: "ace", PasswordHash: "x"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
all := strings.Join(p.Publish, " ")
|
|
for _, want := range append(asks, "$JS.API.DIRECT.GET.KV_composed-ace.>") {
|
|
if !strings.Contains(all, want) {
|
|
t.Errorf("a node-engine of ace may not publish %s", want)
|
|
}
|
|
}
|
|
if !strings.Contains(strings.Join(p.Subscribe, " "), "mesh.mod.restic.event.backed-up") {
|
|
t.Errorf("a node-engine of ace does not hear a backup taken: %v", p.Subscribe)
|
|
}
|
|
if strings.Contains(all, "node-backup.tool.now.*") || strings.Contains(all, "KV_composed-g14") {
|
|
t.Errorf("a node-engine of ace reaches another machine: %v", p.Publish)
|
|
}
|
|
}
|
|
|
|
// recordingRaiser records what a raise asserts.
|
|
type recordingRaiser struct{ streams, consumers []string }
|
|
|
|
func (r *recordingRaiser) EnsureStream(s Stream) error {
|
|
r.streams = append(r.streams, s.Name)
|
|
return nil
|
|
}
|
|
func (r *recordingRaiser) EnsureConsumer(c Consumer) error {
|
|
r.consumers = append(r.consumers, c.Stream+"/"+c.Name)
|
|
return nil
|
|
}
|
|
|
|
// 1945/AC14 (unit half): once the declarations' stream is retired, a raise asserts every stream of the mesh but it,
|
|
// and no node's consumer of a declaration; before, a raise asserts both (ADR 0539 clause 11).
|
|
func TestARaiseAfterTheDeclarationsAssertsNoneOfThem(t *testing.T) {
|
|
before := &recordingRaiser{}
|
|
if err := Raise(before, []string{"ace"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(strings.Join(before.streams, " "), DeclarationsStream) || !strings.Contains(strings.Join(before.consumers, " "), "NODES/ace") {
|
|
t.Fatalf("before the retirement a raise asserts %v and %v", before.streams, before.consumers)
|
|
}
|
|
after := &recordingRaiser{}
|
|
if err := RaiseAfterDeclarations(after); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if strings.Contains(strings.Join(after.streams, " "), DeclarationsStream) || strings.Contains(strings.Join(after.consumers, " "), "NODES/") {
|
|
t.Errorf("after the retirement a raise asserts %v and %v", after.streams, after.consumers)
|
|
}
|
|
if len(after.streams) != len(before.streams)-1 {
|
|
t.Errorf("after the retirement %d streams are asserted, before %d", len(after.streams), len(before.streams))
|
|
}
|
|
}
|
|
|
|
// 1945 (the review of A1, (b) and H1): the operator's account, a person who may invoke every tool, writes release 6's
|
|
// hand path: a want of any node, each node's compositions, the digests, and a node's object bucket with what replacing
|
|
// an object needs; a person with fewer tools writes none of it.
|
|
func TestTheOperatorWritesTheHandPath(t *testing.T) {
|
|
users, err := Users(Records{Nodes: []string{"ace", "novox"}, People: map[string][]string{"jochen": {"*"}, "guest": {"mesh-controller.status"}}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, u := range users {
|
|
if u.Kind != KindPerson {
|
|
continue
|
|
}
|
|
perms, err := PermissionsFor(u)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, s := range []string{"$KV.mesh-delivery-assigner_wants.want.>", "$KV.composed-ace.>", "$KV.composed-novox.>",
|
|
"$KV.mesh-controller_composed.>", "$O.declaration-files-ace.>", "$JS.API.DIRECT.GET.OBJ_declaration-files-ace.>",
|
|
"$JS.API.STREAM.MSG.GET.OBJ_declaration-files-ace", "$JS.API.STREAM.PURGE.OBJ_declaration-files-ace"} {
|
|
found := false
|
|
for _, p := range perms.Publish {
|
|
found = found || p == s
|
|
}
|
|
if found != (u.Module == "jochen") {
|
|
t.Errorf("%s may publish %s: %v", u.Module, s, found)
|
|
}
|
|
}
|
|
}
|
|
c, err := PermissionsFor(Principal{Kind: KindController, PasswordHash: "x"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
found := false
|
|
for _, p := range c.Publish {
|
|
found = found || p == "mesh.seat.mesh-controller.event.bucket-deleted"
|
|
}
|
|
if !found {
|
|
t.Errorf("the controller may not say bucket-deleted")
|
|
}
|
|
}
|