Compare commits
1
Commits
main
...
feat/put-version
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9deaee9eea |
@@ -86,14 +86,15 @@ var keptStates = []announce.State{
|
|||||||
type buckets map[string]jetstream.KeyValue
|
type buckets map[string]jetstream.KeyValue
|
||||||
|
|
||||||
// open opens every bucket the assigner reads and writes; a bucket it cannot open stops the start, named, since a pass
|
// open opens every bucket the assigner reads and writes; a bucket it cannot open stops the start, named, since a pass
|
||||||
// without it would write wants from half the mesh.
|
// without it would write wants from half the mesh. The puts' bucket is the exception: whatever keeps it from opening (it
|
||||||
func open(ctx context.Context, js jetstream.JetStream) (buckets, error) {
|
// does not exist yet, the credential is not granted it yet, the bus does not answer in time), the assigner writes wants
|
||||||
|
// without it and takes no put further until it starts again (ADR 0546).
|
||||||
|
func open(ctx context.Context, js jetstream.JetStream, logf func(string, ...any)) (buckets, error) {
|
||||||
out := buckets{}
|
out := buckets{}
|
||||||
for _, name := range append(append([]string{}, readBuckets...), ownBuckets...) {
|
for _, name := range append(append([]string{}, readBuckets...), ownBuckets...) {
|
||||||
kv, err := js.KeyValue(ctx, name)
|
kv, err := js.KeyValue(ctx, name)
|
||||||
if name == delivery.AssignerPutsBucket && errors.Is(err, jetstream.ErrBucketNotFound) {
|
if name == delivery.AssignerPutsBucket && err != nil {
|
||||||
// The puts (ADR 0546) are taken further only once their bucket exists: an assigner never stops writing wants
|
logf("the bucket %s cannot be opened (%v): no put is taken further until the assigner starts again", name, err)
|
||||||
// for want of it. The verb's first put is refused by the runtime until then.
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -134,13 +135,10 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
kvs, err := open(ctx, js)
|
kvs, err := open(ctx, js, logger.Printf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if kvs[delivery.AssignerPutsBucket] == nil {
|
|
||||||
logger.Printf("the bucket %s does not exist yet, so no put is taken further until the assigner starts again", delivery.AssignerPutsBucket)
|
|
||||||
}
|
|
||||||
// What the assigner keeps, said on the bus (ADR 0495 §2-§3): a reader is granted from it. Not fatal when it cannot
|
// What the assigner keeps, said on the bus (ADR 0495 §2-§3): a reader is granted from it. Not fatal when it cannot
|
||||||
// be said now: process.Conn says it again at every reconnect.
|
// be said now: process.Conn says it again at every reconnect.
|
||||||
said, err := process.Connect(account, nats.Name(account.Node+"."+account.Module+".keeps"), nats.RetryOnFailedConnect(true))
|
said, err := process.Connect(account, nats.Name(account.Node+"."+account.Module+".keeps"), nats.RetryOnFailedConnect(true))
|
||||||
@@ -196,6 +194,8 @@ func follow(ctx context.Context, js jetstream.JetStream, kvs buckets, say func(a
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("the bucket %s cannot be watched: %w", name, err)
|
return fmt.Errorf("the bucket %s cannot be watched: %w", name, err)
|
||||||
}
|
}
|
||||||
|
// The puts' watch never stops the wants (ADR 0546): when it ends, the puts are no longer heard, said in the journal.
|
||||||
|
optional := name == delivery.AssignerPutsBucket
|
||||||
go func() {
|
go func() {
|
||||||
defer kw.Stop()
|
defer kw.Stop()
|
||||||
for {
|
for {
|
||||||
@@ -205,6 +205,10 @@ func follow(ctx context.Context, js jetstream.JetStream, kvs buckets, say func(a
|
|||||||
return
|
return
|
||||||
case e, ok := <-kw.Updates():
|
case e, ok := <-kw.Updates():
|
||||||
if !ok {
|
if !ok {
|
||||||
|
if optional {
|
||||||
|
logger.Printf("the watch of %s ended: no put is taken further until the assigner starts again", name)
|
||||||
|
return
|
||||||
|
}
|
||||||
errs <- fmt.Errorf("the watch of %s ended", name)
|
errs <- fmt.Errorf("the watch of %s ended", name)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -225,9 +229,14 @@ func follow(ctx context.Context, js jetstream.JetStream, kvs buckets, say func(a
|
|||||||
}
|
}
|
||||||
followed := watched
|
followed := watched
|
||||||
if kvs[delivery.AssignerPutsBucket] != nil {
|
if kvs[delivery.AssignerPutsBucket] != nil {
|
||||||
followed = append(append([]string{}, watched...), delivery.AssignerPutsBucket)
|
if err := watch(delivery.AssignerPutsBucket); err != nil {
|
||||||
|
logger.Printf("%v: no put is taken further until the assigner starts again", err)
|
||||||
|
delete(kvs, delivery.AssignerPutsBucket)
|
||||||
|
} else {
|
||||||
|
followed = append(append([]string{}, watched...), delivery.AssignerPutsBucket)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for _, name := range followed {
|
for _, name := range watched {
|
||||||
var opts []jetstream.WatchOpt
|
var opts []jetstream.WatchOpt
|
||||||
if name == delivery.HaveBucket {
|
if name == delivery.HaveBucket {
|
||||||
opts = append(opts, jetstream.IncludeHistory()) // what each node ran healthy before, for a set-back
|
opts = append(opts, jetstream.IncludeHistory()) // what each node ran healthy before, for a set-back
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -10,7 +12,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nats-io/nats.go/jetstream"
|
||||||
|
|
||||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||||
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 1297/B2a: the buckets the program opens are exactly the manifest's reads and states, as the broker names them
|
// 1297/B2a: the buckets the program opens are exactly the manifest's reads and states, as the broker names them
|
||||||
@@ -48,6 +53,27 @@ func TestOpensWhatItsManifestNames(t *testing.T) {
|
|||||||
if got, want := slices.Sorted(slices.Values(kept)), slices.Sorted(slices.Values(ownBuckets)); !reflect.DeepEqual(got, want) {
|
if got, want := slices.Sorted(slices.Values(kept)), slices.Sorted(slices.Values(ownBuckets)); !reflect.DeepEqual(got, want) {
|
||||||
t.Errorf("the program announces %v, keeps %v", got, want)
|
t.Errorf("the program announces %v, keeps %v", got, want)
|
||||||
}
|
}
|
||||||
|
// ADR 0546: whatever keeps the puts' bucket from opening (not there, not granted, no answer in time), the assigner opens
|
||||||
|
// the rest and starts, writing wants; any other bucket it cannot open stops the start, named.
|
||||||
|
for _, c := range []struct {
|
||||||
|
missing string
|
||||||
|
err error
|
||||||
|
starts bool
|
||||||
|
}{
|
||||||
|
{delivery.AssignerPutsBucket, jetstream.ErrBucketNotFound, true},
|
||||||
|
{delivery.AssignerPutsBucket, errors.New("nats: permissions violation for subscription to \"$JS.API.STREAM.INFO.KV_mesh-delivery-assigner_puts\""), true},
|
||||||
|
{delivery.AssignerPutsBucket, context.DeadlineExceeded, true},
|
||||||
|
{delivery.AssignerWantsBucket, errors.New("nats: permissions violation"), false},
|
||||||
|
} {
|
||||||
|
var logged string
|
||||||
|
kvs, err := open(context.Background(), refusingJS{missing: c.missing, err: c.err}, func(f string, a ...any) { logged = fmt.Sprintf(f, a...) })
|
||||||
|
switch {
|
||||||
|
case c.starts && (err != nil || kvs[c.missing] != nil || len(kvs) != len(readBuckets)+len(ownBuckets)-1 || !strings.Contains(logged, "no put is taken further")):
|
||||||
|
t.Errorf("%s refused with %v: the start answered %v, %d buckets, logged %q", c.missing, c.err, err, len(kvs), logged)
|
||||||
|
case !c.starts && (err == nil || !strings.Contains(err.Error(), c.missing)):
|
||||||
|
t.Errorf("%s refused with %v: the start answered %v", c.missing, c.err, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1297/B2a: without a credential the program does not start, and says why.
|
// 1297/B2a: without a credential the program does not start, and says why.
|
||||||
@@ -57,3 +83,17 @@ func TestNoCredential(t *testing.T) {
|
|||||||
t.Errorf("run without a credential = %v", err)
|
t.Errorf("run without a credential = %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// refusingJS opens every bucket but one, which it refuses with err.
|
||||||
|
type refusingJS struct {
|
||||||
|
jetstream.JetStream
|
||||||
|
missing string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r refusingJS) KeyValue(_ context.Context, name string) (jetstream.KeyValue, error) {
|
||||||
|
if name == r.missing {
|
||||||
|
return nil, r.err
|
||||||
|
}
|
||||||
|
return &memKV{values: map[string][]byte{}}, nil
|
||||||
|
}
|
||||||
|
|||||||
BIN
Binary file not shown.
Reference in New Issue
Block a user