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"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.novox.be/novox/mesh-sdk/go/membership"
|
||||||
"git.novox.be/novox/mesh-sdk/go/pernode"
|
"git.novox.be/novox/mesh-sdk/go/pernode"
|
||||||
"github.com/nats-io/nats.go/jetstream"
|
"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 "+
|
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)
|
"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 {
|
for _, s := range m.State {
|
||||||
if s.Name == name {
|
issuing.State = append(issuing.State, membership.StateIssued{Name: s.Name, Bucket: s.Bucket, Writes: s.Writes, PerNode: s.PerNode})
|
||||||
return s, nil
|
}
|
||||||
}
|
if s, ok := issuing.StateIssued(name); ok {
|
||||||
names = append(names, s.Name)
|
return StateIssued{Name: s.Name, Bucket: s.Bucket, Writes: s.Writes, PerNode: s.PerNode}, nil
|
||||||
}
|
}
|
||||||
sort.Strings(names)
|
|
||||||
issued := "none"
|
issued := "none"
|
||||||
if len(names) > 0 {
|
if names := issuing.StateNames(); len(names) > 0 {
|
||||||
issued = strings.Join(names, ", ")
|
issued = strings.Join(names, ", ")
|
||||||
}
|
}
|
||||||
return StateIssued{}, fmt.Errorf("%s keeps and reads no state called %q: it declares what it keeps under "+
|
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)",
|
"`state` or as a key-value entry in `requires`, and what it reads under `reads` as <module>.<name>, and was "+
|
||||||
module, name, issued)
|
"issued: %s (novox/hq ADR 0201, ADR 0595 §1)", module, name, issued)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) bucket(ctx context.Context, s StateIssued) (jetstream.KeyValue, error) {
|
func (c *Conn) bucket(ctx context.Context, s StateIssued) (jetstream.KeyValue, error) {
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"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"
|
"github.com/novox/mesh-tools/node-tools/internal/bus"
|
||||||
mt "github.com/novox/mesh-tools/node-tools/internal/meshtest"
|
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"}}
|
peeker.State = []bus.StateIssued{{Name: "keeper.servers", Bucket: "keeper_servers"}}
|
||||||
mesh.Issue(t, keeper)
|
mesh.Issue(t, keeper)
|
||||||
mesh.Issue(t, peeker)
|
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")
|
nodeTools := connect(t, "node-tools", "anchor")
|
||||||
asker := connect(t, "console", "workstation")
|
asker := connect(t, "console", "workstation")
|
||||||
dir := t.TempDir()
|
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{}
|
said := &lines{}
|
||||||
stop, err := Run(nodeTools, []Served{
|
stop, err := Run(nodeTools, []Served{
|
||||||
{Module: "keeper", Entrypoints: []string{mt.Fixture("state-keeper.mjs")}},
|
{Module: "keeper", Entrypoints: []string{mt.Fixture("state-keeper.mjs")}},
|
||||||
{Module: "peeker", 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{
|
}, map[string]map[string]string{
|
||||||
|
"binder": {"STATE_NAME": "holds", "STATE_LOG": binderLog},
|
||||||
"keeper": {"STATE_NAME": "servers", "STATE_LOG": keeperLog},
|
"keeper": {"STATE_NAME": "servers", "STATE_LOG": keeperLog},
|
||||||
"peeker": {"STATE_NAME": "keeper.servers", "STATE_LOG": peekerLog},
|
"peeker": {"STATE_NAME": "keeper.servers", "STATE_LOG": peekerLog},
|
||||||
}, said.logf)
|
}, said.logf)
|
||||||
@@ -91,6 +101,16 @@ func TestABundleKeepsAndWatchesItsStateThroughTheRuntime(t *testing.T) {
|
|||||||
}
|
}
|
||||||
same(t, got, `["all.one","anchor.two"]`)
|
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.
|
// Refused, with the reason, before anything is sent.
|
||||||
for _, c := range []struct {
|
for _, c := range []struct {
|
||||||
key, why string
|
key, why string
|
||||||
@@ -100,6 +120,8 @@ func TestABundleKeepsAndWatchesItsStateThroughTheRuntime(t *testing.T) {
|
|||||||
map[string]any{"state": "keeper.servers", "key": "x", "value": 1}},
|
map[string]any{"state": "keeper.servers", "key": "x", "value": 1}},
|
||||||
{"keeper.get@anchor", `keeper keeps and reads no state called "other"`,
|
{"keeper.get@anchor", `keeper keeps and reads no state called "other"`,
|
||||||
map[string]any{"state": "other", "key": "x"}},
|
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`,
|
{"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"}}}},
|
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",
|
{"keeper.put@anchor", "is not a key the bus can hold",
|
||||||
|
|||||||
Reference in New Issue
Block a user