At each mesh-grants restart and user-list reload (16:37:24, 16:55:15) nats-server 2.11's reloadAuthorization left the mesh's account without its imports, $JS.API.> among them, for a few seconds: requests had no responders, JetStream calls said it was not enabled, and a build-test result on ace was lost. Quick fix, chosen as asked; the thorough fix is a reload that keeps the account's imports, in the bus's holder. The package busretry asks a request again, backing off within about 30 s, only while its error says it reached no one (no responders, JetStream not enabled, no response from the stream, or either in words): nothing was done, so even a create or an update is safe to ask again. A timeout or any answer is never asked again. After the bound it is said on standard error and returned. Used by process.State's requests, process.Conn's calls (asked again only while $JS.API.INFO says the bus itself answers nothing, so a callee that is not running still fails at once) and asks, and the bundle's state requests through the runtime.
347 lines
12 KiB
Go
347 lines
12 KiB
Go
package process
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/nats-io/nats.go/jetstream"
|
|
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
"git.novox.be/novox/mesh-sdk/go/asker"
|
|
"git.novox.be/novox/mesh-sdk/go/busretry"
|
|
)
|
|
|
|
// fakeEntry is a fakeKV's KeyValueEntry: only Key, Value and Revision are read by State.
|
|
type fakeEntry struct {
|
|
jetstream.KeyValueEntry
|
|
key string
|
|
value []byte
|
|
rev uint64
|
|
}
|
|
|
|
func (e fakeEntry) Key() string { return e.key }
|
|
func (e fakeEntry) Value() []byte { return e.value }
|
|
func (e fakeEntry) Revision() uint64 { return e.rev }
|
|
|
|
// fakeKV is an in-memory jetstream.KeyValue: embedding the nil interface satisfies every method this test
|
|
// never calls, so only Get, Create and Update need a body (1188/AC2's "a fake KV").
|
|
type fakeKV struct {
|
|
jetstream.KeyValue
|
|
mu sync.Mutex
|
|
values map[string][]byte
|
|
revs map[string]uint64
|
|
next uint64
|
|
// initial is what a Watch hands first, before its nil marker; watcher is the last one made.
|
|
initial []jetstream.KeyValueEntry
|
|
watcher *fakeWatcher
|
|
// withTTL says the last create of a key carried an option: its time to live.
|
|
withTTL map[string]bool
|
|
}
|
|
|
|
func newFakeKV() *fakeKV { return &fakeKV{values: map[string][]byte{}, revs: map[string]uint64{}} }
|
|
|
|
func (f *fakeKV) Get(_ context.Context, key string) (jetstream.KeyValueEntry, error) {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
v, ok := f.values[key]
|
|
if !ok {
|
|
return nil, jetstream.ErrKeyNotFound
|
|
}
|
|
return fakeEntry{key: key, value: v, rev: f.revs[key]}, nil
|
|
}
|
|
|
|
func (f *fakeKV) Create(_ context.Context, key string, value []byte, opts ...jetstream.KVCreateOpt) (uint64, error) {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
if f.withTTL == nil {
|
|
f.withTTL = map[string]bool{}
|
|
}
|
|
f.withTTL[key] = len(opts) > 0
|
|
if _, ok := f.values[key]; ok {
|
|
return 0, jetstream.ErrKeyExists
|
|
}
|
|
f.next++
|
|
f.values[key], f.revs[key] = value, f.next
|
|
return f.next, nil
|
|
}
|
|
|
|
func (f *fakeKV) Update(_ context.Context, key string, value []byte, revision uint64) (uint64, error) {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
if f.revs[key] != revision {
|
|
return 0, jetstream.ErrKeyRevisionMismatch
|
|
}
|
|
f.next++
|
|
f.values[key], f.revs[key] = value, f.next
|
|
return f.next, nil
|
|
}
|
|
|
|
func (f *fakeKV) Put(_ context.Context, key string, value []byte) (uint64, error) {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
f.next++
|
|
f.values[key], f.revs[key] = value, f.next
|
|
return f.next, nil
|
|
}
|
|
|
|
func (f *fakeKV) Delete(_ context.Context, key string, _ ...jetstream.KVDeleteOpt) error {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
delete(f.values, key)
|
|
delete(f.revs, key)
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeKV) Keys(_ context.Context, _ ...jetstream.WatchOpt) ([]string, error) {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
if len(f.values) == 0 {
|
|
return nil, jetstream.ErrNoKeysFound
|
|
}
|
|
keys := make([]string, 0, len(f.values))
|
|
for k := range f.values {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Sort(sort.Reverse(sort.StringSlice(keys))) // as a bucket lists them: in no order the caller may rely on
|
|
return keys, nil
|
|
}
|
|
|
|
// fakeWatcher is a jetstream.KeyWatcher fed by a test.
|
|
type fakeWatcher struct {
|
|
jetstream.KeyWatcher
|
|
updates chan jetstream.KeyValueEntry
|
|
pattern string
|
|
}
|
|
|
|
func (w *fakeWatcher) Updates() <-chan jetstream.KeyValueEntry { return w.updates }
|
|
func (w *fakeWatcher) Stop() error { return nil }
|
|
|
|
func (f *fakeKV) Watch(_ context.Context, pattern string, _ ...jetstream.WatchOpt) (jetstream.KeyWatcher, error) {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
w := &fakeWatcher{updates: make(chan jetstream.KeyValueEntry, 16), pattern: pattern}
|
|
f.watcher = w
|
|
for _, e := range f.initial {
|
|
w.updates <- e
|
|
}
|
|
w.updates <- nil
|
|
return w, nil
|
|
}
|
|
|
|
func stateOn(kv jetstream.KeyValue) State {
|
|
return State{Module: "mesh-mail", Name: "sends", kv: func(context.Context) (jetstream.KeyValue, error) { return kv, nil }}
|
|
}
|
|
|
|
// 1188/AC2: given the process connection's state type, when used as go/asker's Store, then it compiles and
|
|
// behaves as the interface says, against a fake KV.
|
|
func TestStateSatisfiesAskerStore(t *testing.T) {
|
|
var _ asker.Store = State{}
|
|
|
|
s := stateOn(newFakeKV())
|
|
if e, err := s.Get("ask.1"); err != nil || e != nil {
|
|
t.Fatalf("Get of an absent key: %v, %v", e, err)
|
|
}
|
|
rev, err := s.Create("ask.1", map[string]any{"a": 1})
|
|
if err != nil {
|
|
t.Fatalf("Create: %v", err)
|
|
}
|
|
e, err := s.Get("ask.1")
|
|
if err != nil || e == nil || e.Revision != rev {
|
|
t.Fatalf("Get after Create: %+v, %v", e, err)
|
|
}
|
|
if _, err := s.Create("ask.1", map[string]any{"a": 2}); !errors.Is(err, stdio.ErrChanged) {
|
|
t.Fatalf("Create of an existing key: %v, want stdio.ErrChanged", err)
|
|
}
|
|
rev2, err := s.Update("ask.1", map[string]any{"a": 3}, rev)
|
|
if err != nil {
|
|
t.Fatalf("Update at the revision read: %v", err)
|
|
}
|
|
if _, err := s.Update("ask.1", map[string]any{"a": 4}, rev); !errors.Is(err, stdio.ErrChanged) {
|
|
t.Fatalf("Update at a stale revision: %v, want stdio.ErrChanged", err)
|
|
}
|
|
if rev2 == rev {
|
|
t.Fatal("Update did not advance the revision")
|
|
}
|
|
|
|
// 1952 S6 (ADR 0564 clauses 4, 7): a key created with its own time to live is created once, a second create
|
|
// refused as an act begun already; a renewal writes it whole with a fresh time to live; a time to live the bus
|
|
// cannot keep is refused.
|
|
kv := newFakeKV()
|
|
s = stateOn(kv)
|
|
if _, err := s.CreateTTL("settle.H3", map[string]any{"a": 1}, time.Minute); err != nil || !kv.withTTL["settle.H3"] {
|
|
t.Fatalf("CreateTTL: %v, with a time to live %v", err, kv.withTTL["settle.H3"])
|
|
}
|
|
if _, err := s.CreateTTL("settle.H3", map[string]any{"a": 2}, time.Minute); !errors.Is(err, stdio.ErrChanged) {
|
|
t.Fatalf("CreateTTL of a live key: %v, want stdio.ErrChanged", err)
|
|
}
|
|
kv.withTTL["settle.H3"] = false
|
|
if _, err := s.RenewTTL("settle.H3", map[string]any{"a": 3}, time.Minute); err != nil || !kv.withTTL["settle.H3"] {
|
|
t.Fatalf("RenewTTL: %v, with a time to live %v", err, kv.withTTL["settle.H3"])
|
|
}
|
|
if e, _ := s.Get("settle.H3"); e == nil || string(e.Value) != `{"a":3}` {
|
|
t.Fatalf("after RenewTTL the key reads %+v", e)
|
|
}
|
|
if _, err := s.CreateTTL("pulse.S1", 1, 10*time.Millisecond); err == nil || !strings.Contains(err.Error(), "shorter") {
|
|
t.Fatalf("a time to live under a second: %v", err)
|
|
}
|
|
}
|
|
|
|
// 1840/S1: given a state, when a key is put twice, then each Put returns a new revision and the key reads
|
|
// back the last value, whether or not it had one.
|
|
func TestStatePutWritesAKeyWhole(t *testing.T) {
|
|
s := stateOn(newFakeKV())
|
|
rev, err := s.Put("k", map[string]any{"a": 1})
|
|
if err != nil || rev == 0 {
|
|
t.Fatalf("Put of a new key: %d, %v", rev, err)
|
|
}
|
|
rev2, err := s.Put("k", map[string]any{"a": 2})
|
|
if err != nil || rev2 <= rev {
|
|
t.Fatalf("Put over a key: %d, %v, want a revision after %d", rev2, err, rev)
|
|
}
|
|
e, err := s.Get("k")
|
|
if err != nil || e == nil || e.Revision != rev2 || string(e.Value) != `{"a":2}` {
|
|
t.Fatalf("Get after Put: %+v, %v", e, err)
|
|
}
|
|
// Issue 774: a bus that answers nothing for a moment (a user-list reload) is waited out, not a lost write.
|
|
busretry.FirstWait = time.Millisecond
|
|
kv, blackedOut := newFakeKV(), 2
|
|
dark := State{Module: "mesh-mail", Name: "sends", kv: func(context.Context) (jetstream.KeyValue, error) {
|
|
if blackedOut > 0 {
|
|
blackedOut--
|
|
return nil, jetstream.ErrJetStreamNotEnabled
|
|
}
|
|
return kv, nil
|
|
}}
|
|
if rev, err := dark.Put("k", 1); err != nil || rev == 0 || blackedOut != 0 {
|
|
t.Fatalf("a Put through a short blackout: %d, %v", rev, err)
|
|
}
|
|
}
|
|
|
|
// 1840/S1: given a state, when its keys are listed, then they come sorted, and none is an empty list, not an error.
|
|
func TestStateKeysAreSortedAndEmptyWhenNone(t *testing.T) {
|
|
s := stateOn(newFakeKV())
|
|
if keys, err := s.Keys(); err != nil || len(keys) != 0 {
|
|
t.Fatalf("Keys of an empty state: %v, %v", keys, err)
|
|
}
|
|
for _, k := range []string{"b", "c", "a"} {
|
|
if _, err := s.Put(k, 1); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
keys, err := s.Keys()
|
|
if err != nil || strings.Join(keys, ",") != "a,b,c" {
|
|
t.Fatalf("Keys: %v, %v, want a,b,c", keys, err)
|
|
}
|
|
}
|
|
|
|
type watchEntry struct {
|
|
fakeEntry
|
|
op jetstream.KeyValueOp
|
|
}
|
|
|
|
func (e watchEntry) Operation() jetstream.KeyValueOp { return e.op }
|
|
|
|
// 1840/S2: given a state with current values (one of them deleted), when it is watched, then the handler gets
|
|
// each current value marked Current and not the deleted one, Watch returns, and a later put and delete arrive
|
|
// in order unmarked.
|
|
func TestStateWatchHandsCurrentValuesThenChanges(t *testing.T) {
|
|
kv := newFakeKV()
|
|
kv.initial = []jetstream.KeyValueEntry{
|
|
watchEntry{fakeEntry{key: "a", value: []byte(`1`), rev: 1}, jetstream.KeyValuePut},
|
|
watchEntry{fakeEntry{key: "gone", rev: 2}, jetstream.KeyValueDelete},
|
|
watchEntry{fakeEntry{key: "b", value: []byte(`2`), rev: 3}, jetstream.KeyValuePut},
|
|
}
|
|
got := make(chan stdio.StateChange, 8)
|
|
s := stateOn(kv)
|
|
if err := s.Watch("", func(c stdio.StateChange) error { got <- c; return nil }); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, want := range []stdio.StateChange{
|
|
{State: "sends", Key: "a", Op: "put", Value: []byte(`1`), Revision: 1, Current: true},
|
|
{State: "sends", Key: "b", Op: "put", Value: []byte(`2`), Revision: 3, Current: true},
|
|
} {
|
|
select {
|
|
case c := <-got:
|
|
if c.Key != want.Key || c.Op != want.Op || string(c.Value) != string(want.Value) || c.Revision != want.Revision || c.Current != want.Current || c.State != want.State {
|
|
t.Fatalf("got %+v, want %+v", c, want)
|
|
}
|
|
default:
|
|
t.Fatalf("Watch returned before handling %+v", want)
|
|
}
|
|
}
|
|
if len(got) != 0 {
|
|
t.Fatalf("a deleted key was handed: %+v", <-got)
|
|
}
|
|
kv.watcher.updates <- watchEntry{fakeEntry{key: "a", value: []byte(`3`), rev: 4}, jetstream.KeyValuePut}
|
|
kv.watcher.updates <- watchEntry{fakeEntry{key: "b", rev: 5}, jetstream.KeyValueDelete}
|
|
for _, want := range []stdio.StateChange{
|
|
{Key: "a", Op: "put", Revision: 4}, {Key: "b", Op: "delete", Revision: 5},
|
|
} {
|
|
select {
|
|
case c := <-got:
|
|
if c.Key != want.Key || c.Op != want.Op || c.Revision != want.Revision || c.Current {
|
|
t.Fatalf("got %+v, want %+v", c, want)
|
|
}
|
|
case <-time.After(5 * time.Second):
|
|
t.Fatalf("no change %+v", want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 1840/S2: a key narrowing reaches the bus as a subject pattern: `*` one name, `**` the rest, empty every key.
|
|
func TestStateWatchNarrowing(t *testing.T) {
|
|
for key, want := range map[string]string{"": ">", "a.b": "a.b", "a.*": "a.*", "a.**": "a.>", "**": ">"} {
|
|
if got := watchPattern(key); got != want {
|
|
t.Errorf("watchPattern(%q) = %q, want %q", key, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 1840/S2: given a handler that refuses a current value, then Watch returns its error.
|
|
func TestStateWatchReturnsAHandlersErrorOnCurrentValues(t *testing.T) {
|
|
kv := newFakeKV()
|
|
kv.initial = []jetstream.KeyValueEntry{watchEntry{fakeEntry{key: "a", value: []byte(`1`), rev: 1}, jetstream.KeyValuePut}}
|
|
boom := errors.New("boom")
|
|
if err := stateOn(kv).Watch("", func(stdio.StateChange) error { return boom }); !errors.Is(err, boom) {
|
|
t.Fatalf("Watch: %v, want the handler's error", err)
|
|
}
|
|
}
|
|
|
|
// 1840/S2: given a connection that closes before the current values arrive, then Watch returns an error.
|
|
func TestStateWatchEndsWithAnErrorWhenTheConnectionCloses(t *testing.T) {
|
|
s := State{Module: "mesh-mail", Name: "sends", closed: func() bool { return true },
|
|
kv: func(context.Context) (jetstream.KeyValue, error) { return silentKV{}, nil }}
|
|
if err := s.Watch("", func(stdio.StateChange) error { return nil }); err == nil {
|
|
t.Fatal("Watch on a closed connection returned no error")
|
|
}
|
|
}
|
|
|
|
// silentKV watches but never delivers.
|
|
type silentKV struct{ jetstream.KeyValue }
|
|
|
|
func (silentKV) Watch(context.Context, string, ...jetstream.WatchOpt) (jetstream.KeyWatcher, error) {
|
|
return &fakeWatcher{updates: make(chan jetstream.KeyValueEntry)}, nil
|
|
}
|
|
|
|
// 1840/S1: given a key, when it is deleted, then it reads as absent; deleting a key that was not there is no error.
|
|
func TestStateDeleteRemovesAKeyAndAnAbsentOneIsNoError(t *testing.T) {
|
|
s := stateOn(newFakeKV())
|
|
if _, err := s.Put("k", 1); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.Delete("k"); err != nil {
|
|
t.Fatalf("Delete of a key: %v", err)
|
|
}
|
|
if e, err := s.Get("k"); err != nil || e != nil {
|
|
t.Fatalf("Get after Delete: %+v, %v, want absent", e, err)
|
|
}
|
|
if err := s.Delete("never"); err != nil {
|
|
t.Fatalf("Delete of an absent key: %v", err)
|
|
}
|
|
}
|