Merge pull request 'Issue a key-value entry asked in requires as a state, by its local name (issue 1966, recovery)' (#66) from fix/1966-requires-state into main
This commit was merged in pull request #66.
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/membership"
|
||||
"git.novox.be/novox/mesh-sdk/go/pernode"
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
)
|
||||
@@ -71,21 +72,22 @@ func (c *Conn) issuedState(module, name string) (StateIssued, error) {
|
||||
return StateIssued{}, fmt.Errorf("%s has no membership issued on %s yet, so no state of it is reachable "+
|
||||
"until the mesh issues one (novox/hq ADR 0201)", module, c.node)
|
||||
}
|
||||
var names []string
|
||||
// What a membership issues by a local name is mesh-sdk's one rule (membership.StateIssued, issue 1966): the old
|
||||
// `state` list, then a key-value entry the module asks for in `requires` (ADR 0595 §1), by its local name.
|
||||
issuing := membership.Membership{Bindings: m.Bindings}
|
||||
for _, s := range m.State {
|
||||
if s.Name == name {
|
||||
return s, nil
|
||||
}
|
||||
names = append(names, s.Name)
|
||||
issuing.State = append(issuing.State, membership.StateIssued{Name: s.Name, Bucket: s.Bucket, Writes: s.Writes, PerNode: s.PerNode})
|
||||
}
|
||||
if s, ok := issuing.StateIssued(name); ok {
|
||||
return StateIssued{Name: s.Name, Bucket: s.Bucket, Writes: s.Writes, PerNode: s.PerNode}, nil
|
||||
}
|
||||
sort.Strings(names)
|
||||
issued := "none"
|
||||
if len(names) > 0 {
|
||||
if names := issuing.StateNames(); len(names) > 0 {
|
||||
issued = strings.Join(names, ", ")
|
||||
}
|
||||
return StateIssued{}, fmt.Errorf("%s keeps and reads no state called %q: it declares what it keeps under "+
|
||||
"`state` and what it reads under `reads` as <module>.<name>, and was issued: %s (novox/hq ADR 0201)",
|
||||
module, name, issued)
|
||||
"`state` or as a key-value entry in `requires`, and what it reads under `reads` as <module>.<name>, and was "+
|
||||
"issued: %s (novox/hq ADR 0201, ADR 0595 §1)", module, name, issued)
|
||||
}
|
||||
|
||||
func (c *Conn) bucket(ctx context.Context, s StateIssued) (jetstream.KeyValue, error) {
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/membership"
|
||||
"github.com/novox/mesh-tools/node-tools/internal/bus"
|
||||
mt "github.com/novox/mesh-tools/node-tools/internal/meshtest"
|
||||
)
|
||||
@@ -35,16 +37,24 @@ func TestABundleKeepsAndWatchesItsStateThroughTheRuntime(t *testing.T) {
|
||||
peeker.State = []bus.StateIssued{{Name: "keeper.servers", Bucket: "keeper_servers"}}
|
||||
mesh.Issue(t, keeper)
|
||||
mesh.Issue(t, peeker)
|
||||
// Issue 1966 (F1): a module that asks for its state as a key-value entry of `requires` (ADR 0595 §1) is issued it by
|
||||
// its binding alone, under its local name, as its own; no other module is.
|
||||
mesh.Bucket(t, "binder_holds")
|
||||
binder := mt.MembershipOf("binder", "anchor", false, nil)
|
||||
binder.Bindings = []membership.BusBinding{{Local: "holds", Storage: catalogue.StorageKeyValue, Template: catalogue.TemplateState, Bucket: "binder_holds"}}
|
||||
mesh.Issue(t, binder)
|
||||
|
||||
nodeTools := connect(t, "node-tools", "anchor")
|
||||
asker := connect(t, "console", "workstation")
|
||||
dir := t.TempDir()
|
||||
keeperLog, peekerLog := filepath.Join(dir, "keeper.log"), filepath.Join(dir, "peeker.log")
|
||||
keeperLog, peekerLog, binderLog := filepath.Join(dir, "keeper.log"), filepath.Join(dir, "peeker.log"), filepath.Join(dir, "binder.log")
|
||||
said := &lines{}
|
||||
stop, err := Run(nodeTools, []Served{
|
||||
{Module: "keeper", Entrypoints: []string{mt.Fixture("state-keeper.mjs")}},
|
||||
{Module: "peeker", Entrypoints: []string{mt.Fixture("state-keeper.mjs")}},
|
||||
{Module: "binder", Entrypoints: []string{mt.Fixture("state-keeper.mjs")}},
|
||||
}, map[string]map[string]string{
|
||||
"binder": {"STATE_NAME": "holds", "STATE_LOG": binderLog},
|
||||
"keeper": {"STATE_NAME": "servers", "STATE_LOG": keeperLog},
|
||||
"peeker": {"STATE_NAME": "keeper.servers", "STATE_LOG": peekerLog},
|
||||
}, said.logf)
|
||||
@@ -91,6 +101,16 @@ func TestABundleKeepsAndWatchesItsStateThroughTheRuntime(t *testing.T) {
|
||||
}
|
||||
same(t, got, `["all.one","anchor.two"]`)
|
||||
|
||||
// The bound module puts, gets and watches its key-value entry by its local name.
|
||||
waitFor(t, binderLog, "current")
|
||||
if _, err := call(t, asker, "binder.put@anchor", map[string]any{"state": "holds", "key": "h1", "value": 1}); err != nil {
|
||||
t.Fatalf("a put on a bound key-value entry: %v", err)
|
||||
}
|
||||
waitFor(t, binderLog, "now put h1 1")
|
||||
if got, err := call(t, asker, "binder.get@anchor", map[string]any{"state": "holds", "key": "h1"}); err != nil || !strings.Contains(got, `"value":1`) {
|
||||
t.Fatalf("a get on a bound key-value entry answered %s, %v", got, err)
|
||||
}
|
||||
|
||||
// Refused, with the reason, before anything is sent.
|
||||
for _, c := range []struct {
|
||||
key, why string
|
||||
@@ -100,6 +120,8 @@ func TestABundleKeepsAndWatchesItsStateThroughTheRuntime(t *testing.T) {
|
||||
map[string]any{"state": "keeper.servers", "key": "x", "value": 1}},
|
||||
{"keeper.get@anchor", `keeper keeps and reads no state called "other"`,
|
||||
map[string]any{"state": "other", "key": "x"}},
|
||||
{"keeper.get@anchor", `keeper keeps and reads no state called "holds"`,
|
||||
map[string]any{"state": "holds", "key": "h1"}},
|
||||
{"keeper.put@anchor", `carries a field "accessToken", which names a credential`,
|
||||
map[string]any{"state": "servers", "key": "x", "value": map[string]any{"headers": map[string]any{"accessToken": "s"}}}},
|
||||
{"keeper.put@anchor", "is not a key the bus can hold",
|
||||
|
||||
Reference in New Issue
Block a user