CAT-RD8: minio asks the assigner's seat-holders once and follows its events, no reads, no cache on a timer
This commit is contained in:
@@ -10,10 +10,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/sdk/views"
|
||||
)
|
||||
|
||||
// Words the manifest gives this bundle (modules/minio/module.json).
|
||||
@@ -24,17 +28,24 @@ const (
|
||||
)
|
||||
|
||||
func main() {
|
||||
s, err := newServer(os.Getenv(ConfigWord), os.Getenv(LinksWord), os.Getenv(ReceivesWord))
|
||||
seats := &views.Reader{Module: "minio", Logf: func(f string, a ...any) { fmt.Fprintf(os.Stderr, "[object-store] "+f+"\n", a...) }}
|
||||
seats.Follow(delivery.AssignerModule, delivery.SeatHoldersState, SeatHoldersTool)
|
||||
s, err := newServer(os.Getenv(ConfigWord), os.Getenv(LinksWord), os.Getenv(ReceivesWord),
|
||||
func() (map[string][]byte, error) { return seats.Values(delivery.SeatHoldersRead) })
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "[object-store] %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := stdio.Serve("", tools(s)); err != nil {
|
||||
seats.Start(context.Background())
|
||||
if err := stdio.Serve("", append(tools(s), seats.Tools("object_store")...)); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "[object-store] %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// SeatHoldersTool is the assigner's read tool of who holds which seat, which the manifest invokes (ADR 0615).
|
||||
const SeatHoldersTool = delivery.AssignerModule + ".assigner_read_seat_holders"
|
||||
|
||||
func tools(s *server) []stdio.Tool {
|
||||
return []stdio.Tool{{
|
||||
Name: "object-store.link",
|
||||
|
||||
@@ -38,9 +38,10 @@ func TestManifestClaimsTheSeat(t *testing.T) {
|
||||
if c["name"] != "object-store" || c["scope"] != "mesh" || len(serves) != 1 || serves[0] != "link" {
|
||||
t.Fatalf("claim: %v", c)
|
||||
}
|
||||
reads, _ := m["reads"].([]any)
|
||||
if _, invokes := m["invokes"]; invokes || len(reads) != 1 || reads[0] != "mesh-delivery-assigner.seat-holders" {
|
||||
t.Fatalf("reads %v and invokes %v: it reads the placement seat's holders and calls nothing", reads, m["invokes"])
|
||||
invokes, _ := m["invokes"].([]any)
|
||||
consumes, _ := m["consumes"].([]any)
|
||||
if _, reads := m["reads"]; reads || len(invokes) != 1 || invokes[0] != SeatHoldersTool || len(consumes) != 1 || consumes[0] != "mesh-delivery-assigner.views.seat-holders" {
|
||||
t.Fatalf("reads %v, invokes %v, consumes %v: it asks the assigner's seat-holders once and follows its events (ADR 0615)", m["reads"], invokes, consumes)
|
||||
}
|
||||
var goBundle map[string]any
|
||||
for _, a := range m["build"].(map[string]any)["artifacts"].([]any) {
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
@@ -21,7 +20,6 @@ import (
|
||||
const (
|
||||
DefaultSeconds = 300
|
||||
MaxSeconds = 600
|
||||
holdersFor = 60 * time.Second
|
||||
)
|
||||
|
||||
// storeConfig is the store as this machine reaches it. The file is rendered by the mesh from files/object-store.json
|
||||
@@ -60,41 +58,18 @@ func linkKeyID(bucket string) string {
|
||||
type server struct {
|
||||
configPath, linksDir, receives string
|
||||
now func() time.Time
|
||||
// seats reads the placement seat's state of who holds which seat, every key; a test replaces it.
|
||||
// seats is the picture of the placement seat's state of who holds which seat, every key: asked of the assigner once
|
||||
// and followed by its events (ADR 0615); a test replaces it.
|
||||
seats func() (map[string][]byte, error)
|
||||
|
||||
mu sync.Mutex
|
||||
keys map[string][]byte // seat-holders, as read
|
||||
readAt time.Time
|
||||
haveSet bool
|
||||
}
|
||||
|
||||
func newServer(config, links, receives string) (*server, error) {
|
||||
func newServer(config, links, receives string, seats func() (map[string][]byte, error)) (*server, error) {
|
||||
for word, v := range map[string]string{ConfigWord: config, LinksWord: links, ReceivesWord: receives} {
|
||||
if strings.TrimSpace(v) == "" {
|
||||
return nil, fmt.Errorf("%s is not given: the manifest names it", word)
|
||||
}
|
||||
}
|
||||
return &server{configPath: config, linksDir: links, receives: receives, now: time.Now, seats: askSeats}, nil
|
||||
}
|
||||
|
||||
func askSeats() (map[string][]byte, error) {
|
||||
state := stdio.State(delivery.SeatHoldersRead)
|
||||
names, err := state.Keys()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys := map[string][]byte{}
|
||||
for _, k := range names {
|
||||
e, err := state.Get(k)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if e != nil {
|
||||
keys[k] = e.Value
|
||||
}
|
||||
}
|
||||
return keys, nil
|
||||
return &server{configPath: config, linksDir: links, receives: receives, now: time.Now, seats: seats}, nil
|
||||
}
|
||||
|
||||
// link answers one ask.
|
||||
@@ -266,18 +241,13 @@ func has(list []string, v string) bool {
|
||||
}
|
||||
|
||||
// holds says whether node.module holds the seat, from the placement seat's state of who holds which seat (novox/hq ADR
|
||||
// 0571 clause 4), read at most once a minute. A read that failed is not cached: the next ask reads again.
|
||||
// 0571 clause 4), from the picture the assigner's first view and events keep. No first view yet is refused, saying so.
|
||||
func (s *server) holds(seat, node, module string) (bool, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if !s.haveSet || s.now().Sub(s.readAt) >= holdersFor {
|
||||
keys, err := s.seats()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
s.keys, s.readAt, s.haveSet = keys, s.now(), true
|
||||
keys, err := s.seats()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
holders, err := delivery.HoldersOf(s.keys, seat, node)
|
||||
holders, err := delivery.HoldersOf(keys, seat, node)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ func newRig(t *testing.T) *rig {
|
||||
write("links/mesh-shanks-brother.secret", "brother-link-secret\n")
|
||||
write("links/mesh-novox-nextcloud.secret", "nextcloud-link-secret\n")
|
||||
grants := write("grants/mesh.json", grantFile)
|
||||
s, err := newServer(cfg, filepath.Join(dir, "links"), grants)
|
||||
s, err := newServer(cfg, filepath.Join(dir, "links"), grants, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -165,36 +165,24 @@ func TestRefusals(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// 1516/AC4: with the controller unreadable a seat share is refused naming why; a good read serves a second ask within
|
||||
// 60 s from the cache and is read again after.
|
||||
func TestSeatsUnreadableAndCached(t *testing.T) {
|
||||
// 1516/AC4 and ADR 0615: with no first view of the seat-holders yet a seat share is refused naming why; once it is
|
||||
// answered every ask is served from the picture, with no read of the owner's state and none on a timer.
|
||||
func TestSeatsUnreadableAndFollowed(t *testing.T) {
|
||||
r := newRig(t)
|
||||
r.seatsErr = errors.New("the controller did not answer")
|
||||
r.seatsErr = errors.New("no first view of mesh-delivery-assigner.seat-holders yet")
|
||||
_, err := r.s.link("g14.mesh-files", ask("mesh-shanks-brother", "put", nil))
|
||||
if err == nil || !strings.Contains(err.Error(), "the controller did not answer") || !strings.Contains(err.Error(), "node-files") {
|
||||
if err == nil || !strings.Contains(err.Error(), "no first view") || !strings.Contains(err.Error(), "node-files") {
|
||||
t.Fatalf("not refused naming why: %v", err)
|
||||
}
|
||||
r.seatsErr = nil
|
||||
put := func() {
|
||||
t.Helper()
|
||||
for range 3 {
|
||||
if _, err := r.s.link("g14.mesh-files", ask("mesh-shanks-brother", "put", nil)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
r.now = r.now.Add(10 * time.Minute) // time changes nothing: the picture is current by its events
|
||||
}
|
||||
put() // a failed read is not cached: this reads again
|
||||
put() // served from the cache
|
||||
if r.seatsCalls != 2 {
|
||||
t.Fatalf("the controller was asked %d times, want 2 (one failure, one good read)", r.seatsCalls)
|
||||
}
|
||||
r.now = r.now.Add(59 * time.Second)
|
||||
put()
|
||||
if r.seatsCalls != 2 {
|
||||
t.Fatalf("an ask within 60 s read the seats again (%d calls)", r.seatsCalls)
|
||||
}
|
||||
r.now = r.now.Add(2 * time.Second)
|
||||
put()
|
||||
if r.seatsCalls != 3 {
|
||||
t.Fatalf("the seats were not read again after 60 s (%d calls)", r.seatsCalls)
|
||||
if r.seatsCalls != 4 {
|
||||
t.Fatalf("the picture was asked %d times, want 4 (one refused, three served)", r.seatsCalls)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,8 +39,11 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"reads": [
|
||||
"mesh-delivery-assigner.seat-holders"
|
||||
"invokes": [
|
||||
"mesh-delivery-assigner.assigner_read_seat_holders"
|
||||
],
|
||||
"consumes": [
|
||||
"mesh-delivery-assigner.views.seat-holders"
|
||||
],
|
||||
"contributes": {
|
||||
"route": {
|
||||
|
||||
Executable
BIN
Binary file not shown.
Reference in New Issue
Block a user