D1 (assigner): the assigner names its own states from its credential's module
Its buckets (the seat's states, wants, asks, puts, waits, findings, policies, pins, marks, first views) are functions of the module SetModule was told at its start (its credential's; the tools program's served-as), no longer constants of its name. Builds: design 47 G A-296, §428 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
@@ -54,7 +54,7 @@ func (p *passer) entryEdges(node, module string) []seat.Edge {
|
||||
// actingReleased is the waits released by hand and not yet run, by `<key>@<part's fingerprint>`.
|
||||
func (p *passer) actingReleased() map[string]bool {
|
||||
out := map[string]bool{}
|
||||
for key, raw := range p.w.read(buckets.Waits) {
|
||||
for key, raw := range p.w.read(buckets.Waits()) {
|
||||
if w, err := acting.Decode(raw); err == nil && w.Released && (acting.Is(key) || acting.IsOrder(key)) {
|
||||
out[key+"@"+w.Fp] = true
|
||||
}
|
||||
@@ -66,11 +66,11 @@ func (p *passer) actingReleased() map[string]bool {
|
||||
// wait of one that ran, was replaced or was released, and says each.
|
||||
func (p *passer) actingWaits(ctx context.Context, n assign.Nodes, wanted map[string]map[string]meshwants.Want) {
|
||||
g := p.graph
|
||||
if g == nil || p.kvs[buckets.Waits] == nil {
|
||||
if g == nil || p.kvs[buckets.Waits()] == nil {
|
||||
return
|
||||
}
|
||||
now := p.clock()
|
||||
kept := p.w.read(buckets.Waits)
|
||||
kept := p.w.read(buckets.Waits())
|
||||
standing := map[string]bool{}
|
||||
for _, ref := range g.computed.Revision.Entries {
|
||||
entry := g.computed.Entries[ref.Digest]
|
||||
@@ -97,11 +97,11 @@ func (p *passer) actingWaits(ctx context.Context, n assign.Nodes, wanted map[str
|
||||
}
|
||||
w := acting.Wait{Step: e.Step, Node: ref.Node, Module: ref.Module, Part: e.Module, PartNode: e.Node, Fp: e.Fp, Entry: entry.Fp,
|
||||
Since: now, ClockFrom: now, Bound: acting.BoundOf(e).String()}
|
||||
if err := p.wantsSay(seat.EventWaitHeld, acting.Body(w, assignment.Module), key+"."+e.Fp+"."+entry.Fp); err != nil {
|
||||
if err := p.wantsSay(seat.EventWaitHeld, acting.Body(w, assignment.Module()), key+"."+e.Fp+"."+entry.Fp); err != nil {
|
||||
p.logf("the wait of %s on %s for %s is not said, and is said at the next pass: %v", ref.Module, ref.Node, e.Module, err)
|
||||
continue
|
||||
}
|
||||
p.put(ctx, buckets.Waits, key, func() ([]byte, error) { return acting.Encode(w) })
|
||||
p.put(ctx, buckets.Waits(), key, func() ([]byte, error) { return acting.Encode(w) })
|
||||
}
|
||||
}
|
||||
for key, raw := range kept {
|
||||
@@ -119,14 +119,14 @@ func (p *passer) actingWaits(ctx context.Context, n assign.Nodes, wanted map[str
|
||||
how = acting.HowReplaced
|
||||
}
|
||||
if !w.EndSaid {
|
||||
body := acting.Body(w, assignment.Module)
|
||||
body := acting.Body(w, assignment.Module())
|
||||
body.How = how
|
||||
if err := p.wantsSay(seat.EventWaitEnded, body, key+"."+w.Fp+"."+w.Entry+"."+how); err != nil {
|
||||
p.logf("the end of the wait of %s on %s for %s is not said, and is said at the next pass: %v", w.Module, w.Node, w.Part, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
p.remove(ctx, buckets.Waits, key)
|
||||
p.remove(ctx, buckets.Waits(), key)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ func (p *passer) actingReleaseSaid(ctx context.Context, key string, w acting.Wai
|
||||
if !w.Released || w.EndSaid {
|
||||
return
|
||||
}
|
||||
body := acting.Body(w, assignment.Module)
|
||||
body := acting.Body(w, assignment.Module())
|
||||
body.How = acting.HowReleased
|
||||
body.By = w.By
|
||||
if err := p.wantsSay(seat.EventWaitEnded, body, key+"."+w.Fp+"."+w.Entry+"."+acting.HowReleased); err != nil {
|
||||
@@ -143,7 +143,7 @@ func (p *passer) actingReleaseSaid(ctx context.Context, key string, w acting.Wai
|
||||
return
|
||||
}
|
||||
w.EndSaid = true
|
||||
p.put(ctx, buckets.Waits, key, func() ([]byte, error) { return acting.Encode(w) })
|
||||
p.put(ctx, buckets.Waits(), key, func() ([]byte, error) { return acting.Encode(w) })
|
||||
}
|
||||
|
||||
// edgeOf is the edge a kept wait stands on, as an acting edge.
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
// `now` and ended when the version runs.
|
||||
func TestAnEntryWaitsForItsActingPartAndTheWaitIsKeptSaidAndEnded(t *testing.T) {
|
||||
now := time.Date(2026, 11, 12, 9, 0, 0, 0, time.UTC)
|
||||
p := newPasser(kvSet{buckets.Waits: &memKV{values: map[string][]byte{}}}, now)
|
||||
p := newPasser(kvSet{buckets.Waits(): &memKV{values: map[string][]byte{}}}, now)
|
||||
var events []string
|
||||
var bodies []meshwants.Wait
|
||||
p.publishWants = func(event string, body any, id string) error {
|
||||
@@ -52,14 +52,14 @@ func TestAnEntryWaitsForItsActingPartAndTheWaitIsKeptSaidAndEnded(t *testing.T)
|
||||
t.Errorf("wait-held %+v", b)
|
||||
}
|
||||
key := acting.Key("ace", "nats", "mesh-host")
|
||||
if _, err := acting.Decode(p.w.read(buckets.Waits)[key]); err != nil {
|
||||
if _, err := acting.Decode(p.w.read(buckets.Waits())[key]); err != nil {
|
||||
t.Fatalf("the wait is kept: %v", err)
|
||||
}
|
||||
// a release by hand lets the entry move, and ends the wait once
|
||||
w, _ := acting.Decode(p.w.read(buckets.Waits)[key])
|
||||
w, _ := acting.Decode(p.w.read(buckets.Waits())[key])
|
||||
w.Released = true
|
||||
raw, _ := acting.Encode(w)
|
||||
p.w.put(buckets.Waits, key, raw)
|
||||
p.w.put(buckets.Waits(), key, raw)
|
||||
if got := p.actingOf("nats", p.actingReleased())("ace"); len(got) != 0 {
|
||||
t.Errorf("a released wait still holds: %+v", got)
|
||||
}
|
||||
@@ -71,13 +71,13 @@ func TestAnEntryWaitsForItsActingPartAndTheWaitIsKeptSaidAndEnded(t *testing.T)
|
||||
// the version runs: the record goes, said nothing more
|
||||
n.Running["ace"]["mesh-host"] = nodeengine.Running{Fingerprint: "src2:new"}
|
||||
p.actingWaits(ctx, n, wanted)
|
||||
if len(p.w.read(buckets.Waits)) != 0 || len(events) != 2 {
|
||||
t.Errorf("kept %v events %v", p.w.read(buckets.Waits), events)
|
||||
if len(p.w.read(buckets.Waits())) != 0 || len(events) != 2 {
|
||||
t.Errorf("kept %v events %v", p.w.read(buckets.Waits()), events)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnActingWaitOfANodeAwayStartsNoClockAndAnEntryWantedAlreadyWaitsNot(t *testing.T) {
|
||||
p := newPasser(kvSet{buckets.Waits: &memKV{values: map[string][]byte{}}}, time.Now())
|
||||
p := newPasser(kvSet{buckets.Waits(): &memKV{values: map[string][]byte{}}}, time.Now())
|
||||
p.publishWants = func(string, any, string) error { t.Fatal("said"); return nil }
|
||||
p.graph = &graphKeeper{}
|
||||
p.graph.computed.Revision.Entries = []meshwants.NodeEntryRef{{Node: "ace", Module: "nats", Digest: "d1"}}
|
||||
@@ -87,8 +87,8 @@ func TestAnActingWaitOfANodeAwayStartsNoClockAndAnEntryWantedAlreadyWaitsNot(t *
|
||||
p.actingWaits(context.Background(), n, map[string]map[string]mwc.Want{})
|
||||
n.Online["ace"] = true
|
||||
p.actingWaits(context.Background(), n, map[string]map[string]mwc.Want{"nats": {"ace": {Fp: "src2:a"}}})
|
||||
if len(p.w.read(buckets.Waits)) != 0 || strings.Contains(p.graph.computed.Entries["d1"].Fp, "x") {
|
||||
t.Errorf("kept %v", p.w.read(buckets.Waits))
|
||||
if len(p.w.read(buckets.Waits())) != 0 || strings.Contains(p.graph.computed.Entries["d1"].Fp, "x") {
|
||||
t.Errorf("kept %v", p.w.read(buckets.Waits()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ func staleOfWorld(w *world) staleOf {
|
||||
func hearAskEnded(ctx context.Context, js jetstream.JetStream, hub *process.EventHub, asksKV jetstream.KeyValue, stale staleOf, logf func(string, ...any)) error {
|
||||
return hub.Handle(askEndedSubject, func(m jetstream.Msg) {
|
||||
var putsKV jetstream.KeyValue
|
||||
if kv, err := js.KeyValue(ctx, buckets.Puts); err == nil {
|
||||
if kv, err := js.KeyValue(ctx, buckets.Puts()); err == nil {
|
||||
putsKV = kv
|
||||
}
|
||||
if err := askAgain(ctx, asksKV, putsKV, stale, m.Data(), time.Now(), logf); err != nil {
|
||||
@@ -161,7 +161,7 @@ func endedAtBind(ctx context.Context, js jetstream.JetStream, asksKV jetstream.K
|
||||
}
|
||||
var putsKV jetstream.KeyValue
|
||||
if js != nil {
|
||||
if kv, err := js.KeyValue(ctx, buckets.Puts); err == nil {
|
||||
if kv, err := js.KeyValue(ctx, buckets.Puts()); err == nil {
|
||||
putsKV = kv
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ var buildsStreamName = catalogue.StreamBusName(builds.BuildsStream)
|
||||
// it returns, so the first pass knows every build; a stream it cannot hear is said, and no want names a build until the assigner
|
||||
// starts again.
|
||||
func hearBuilds(ctx context.Context, js jetstream.JetStream, ends *builtEnds, wake func(), findings assign.Findings, logf func(string, ...any)) {
|
||||
acct := process.Account{Node: thisNode, Module: assignment.Module}
|
||||
acct := process.Account{Node: thisNode, Module: assignment.Module()}
|
||||
nh := notHeardWriter(findings, logf)
|
||||
cc, err := acct.HearStream(ctx, js, buildsStreamName, func(m jetstream.Msg) {
|
||||
if err := ends.put(m.Data()); err != nil {
|
||||
|
||||
+1
-1
@@ -177,7 +177,7 @@ func TestTheCompositionStepComposesEachMachineThroughTheBuildSeat(t *testing.T)
|
||||
t.Error("the machine away was asked for its input")
|
||||
}
|
||||
}
|
||||
keys, _ := b.buckets[buckets.Inputs].Keys(b.ctx)
|
||||
keys, _ := b.buckets[buckets.Inputs()].Keys(b.ctx)
|
||||
if len(keys) != 0 {
|
||||
t.Errorf("the inputs stay kept past their step-ended: %v", keys)
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ func presenceOf(data []byte) (string, time.Time, error) {
|
||||
// machine-away and machine-back. A stream not heard is said as the assigner's finding, and what it carries is not known until
|
||||
// the assigner starts again.
|
||||
func hearDelivery(ctx context.Context, js jetstream.JetStream, w *world, wake func(), findings assign.Findings, logf func(string, ...any)) {
|
||||
acct := process.Account{Node: thisNode, Module: assignment.Module}
|
||||
acct := process.Account{Node: thisNode, Module: assignment.Module()}
|
||||
nh := notHeardWriter(findings, logf)
|
||||
for _, s := range []struct {
|
||||
stream, words string
|
||||
@@ -321,10 +321,10 @@ type deliveryWaiting struct {
|
||||
// saidDeliveryWaits is every delivery wait the state delivery-waits holds, by module; a value that does not decode is said and left.
|
||||
func (p *passer) saidDeliveryWaits() map[string]deliveryWaiting {
|
||||
out := map[string]deliveryWaiting{}
|
||||
for key, value := range p.w.read(buckets.DeliveryWaits) {
|
||||
for key, value := range p.w.read(buckets.DeliveryWaits()) {
|
||||
var w deliveryWaiting
|
||||
if err := json.Unmarshal(value, &w); err != nil || w.Module == "" {
|
||||
p.logf("%s %s holds no delivery wait, left: %v", buckets.DeliveryWaits, key, err)
|
||||
p.logf("%s %s holds no delivery wait, left: %v", buckets.DeliveryWaits(), key, err)
|
||||
continue
|
||||
}
|
||||
out[w.Module] = w
|
||||
@@ -335,7 +335,7 @@ func (p *passer) saidDeliveryWaits() map[string]deliveryWaiting {
|
||||
// holdForDelivery says whether module's want, changing to fp on branch, is held for a dependency's delivery this pass: it says
|
||||
// wait-held once and keeps it; a wait released by hand holds nothing; a wait that no longer holds is ended ran.
|
||||
func (p *passer) holdForDelivery(ctx context.Context, module, fp, branch string, nodes []string) bool {
|
||||
if p.kvs[buckets.DeliveryWaits] == nil {
|
||||
if p.kvs[buckets.DeliveryWaits()] == nil {
|
||||
return false
|
||||
}
|
||||
key := keys.EscapeName(module)
|
||||
@@ -364,7 +364,7 @@ func (p *passer) holdForDelivery(ctx context.Context, module, fp, branch string,
|
||||
return true
|
||||
}
|
||||
kept := deliveryWaiting{Wait: body}
|
||||
p.put(ctx, buckets.DeliveryWaits, key, func() ([]byte, error) { return json.Marshal(kept) })
|
||||
p.put(ctx, buckets.DeliveryWaits(), key, func() ([]byte, error) { return json.Marshal(kept) })
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -376,7 +376,7 @@ func (p *passer) endDeliveryWait(ctx context.Context, key string, w deliveryWait
|
||||
p.logf("the end of the delivery wait of %s is not said, and is said at the next pass: %v", w.Module, err)
|
||||
return
|
||||
}
|
||||
p.remove(ctx, buckets.DeliveryWaits, key)
|
||||
p.remove(ctx, buckets.DeliveryWaits(), key)
|
||||
}
|
||||
|
||||
// againAsk is one ask of assigner_delivered_again, kept in the state delivered-again under its module (A-250): the version to say
|
||||
@@ -408,13 +408,13 @@ func (r *releasedVersions) releaseOf(module, version string) (releaseHeard, bool
|
||||
// version-delivered again when every present machine placing the module runs that version healthy and removes the ask, else keeps
|
||||
// it with why it does not hold, which assigner_waits reads.
|
||||
func (p *passer) deliveredAgain(ctx context.Context) {
|
||||
if p.kvs[buckets.DeliveredAgain] == nil {
|
||||
if p.kvs[buckets.DeliveredAgain()] == nil {
|
||||
return
|
||||
}
|
||||
for key, value := range p.w.read(buckets.DeliveredAgain) {
|
||||
for key, value := range p.w.read(buckets.DeliveredAgain()) {
|
||||
var a againAsk
|
||||
if err := json.Unmarshal(value, &a); err != nil || a.Version == "" {
|
||||
p.logf("%s %s holds no ask, left: %v", buckets.DeliveredAgain, key, err)
|
||||
p.logf("%s %s holds no ask, left: %v", buckets.DeliveredAgain(), key, err)
|
||||
continue
|
||||
}
|
||||
module := a.Module
|
||||
@@ -440,12 +440,12 @@ func (p *passer) deliveredAgain(ctx context.Context) {
|
||||
continue
|
||||
}
|
||||
p.logf("version-delivered of %s %s said again, asked by %s: %s", module, a.Version, a.By, a.Why)
|
||||
p.remove(ctx, buckets.DeliveredAgain, key)
|
||||
p.remove(ctx, buckets.DeliveredAgain(), key)
|
||||
continue
|
||||
}
|
||||
if a.Waits != waits {
|
||||
a.Waits = waits
|
||||
p.put(ctx, buckets.DeliveredAgain, key, func() ([]byte, error) { return json.Marshal(a) })
|
||||
p.put(ctx, buckets.DeliveredAgain(), key, func() ([]byte, error) { return json.Marshal(a) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ func TestADependentWaitsForItsDependencysDelivery(t *testing.T) {
|
||||
at := time.Date(2026, 11, 12, 9, 0, 0, 0, time.UTC)
|
||||
waits := &memKV{values: map[string][]byte{}}
|
||||
again := &memKV{values: map[string][]byte{}}
|
||||
p := newPasser(kvSet{buckets.Wants: &memKV{values: map[string][]byte{}}, buckets.DeliveryWaits: waits, buckets.DeliveredAgain: again}, at)
|
||||
p := newPasser(kvSet{buckets.Wants(): &memKV{values: map[string][]byte{}}, buckets.DeliveryWaits(): waits, buckets.DeliveredAgain(): again}, at)
|
||||
p.logf = t.Logf
|
||||
var events []said
|
||||
p.publishWants = func(event string, body any, id string) error {
|
||||
@@ -44,7 +44,7 @@ func TestADependentWaitsForItsDependencysDelivery(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
p.w.put(buckets.Wants, key, raw)
|
||||
p.w.put(buckets.Wants(), key, raw)
|
||||
}
|
||||
runs := func(node, module, fp string) {
|
||||
raw, _ := json.Marshal(map[string]any{"v": 1, "node": node, "module": module, "fingerprint": fp, "at": at})
|
||||
@@ -152,7 +152,7 @@ func TestADependentWaitsForItsDependencysDelivery(t *testing.T) {
|
||||
_ = json.Unmarshal(waits.values["app"], &kept)
|
||||
kept.Released = true
|
||||
raw, _ := json.Marshal(kept)
|
||||
p.w.put(buckets.DeliveryWaits, "app", raw)
|
||||
p.w.put(buckets.DeliveryWaits(), "app", raw)
|
||||
if p.holdForDelivery(ctx, "app", fp("3"), trunkBranch, []string{"ace"}) {
|
||||
t.Fatal("a released wait holds app")
|
||||
}
|
||||
@@ -170,18 +170,18 @@ func TestADependentWaitsForItsDependencysDelivery(t *testing.T) {
|
||||
// away; one of 1.2.0, which no machine runs, is kept with why.
|
||||
ask := func(version string) {
|
||||
raw, _ := json.Marshal(againAsk{Module: "db", Version: version, By: "op", Why: "the catalog missed it"})
|
||||
p.w.put(buckets.DeliveredAgain, "db", raw)
|
||||
p.w.put(buckets.DeliveredAgain(), "db", raw)
|
||||
}
|
||||
before := len(saidOf(meshwants.EventVersionDelivered))
|
||||
ask("1.1.0")
|
||||
p.deliveredAgain(ctx)
|
||||
if len(saidOf(meshwants.EventVersionDelivered)) != before+1 || p.w.has(buckets.DeliveredAgain, "db") {
|
||||
t.Errorf("the ask of 1.1.0 said %d version-delivered and is kept %v", len(saidOf(meshwants.EventVersionDelivered))-before, p.w.has(buckets.DeliveredAgain, "db"))
|
||||
if len(saidOf(meshwants.EventVersionDelivered)) != before+1 || p.w.has(buckets.DeliveredAgain(), "db") {
|
||||
t.Errorf("the ask of 1.1.0 said %d version-delivered and is kept %v", len(saidOf(meshwants.EventVersionDelivered))-before, p.w.has(buckets.DeliveredAgain(), "db"))
|
||||
}
|
||||
ask("1.2.0")
|
||||
p.deliveredAgain(ctx)
|
||||
var a againAsk
|
||||
if json.Unmarshal(p.w.value(buckets.DeliveredAgain, "db"), &a) != nil || a.Waits != "db 1.2.0 does not run healthy on ace" ||
|
||||
if json.Unmarshal(p.w.value(buckets.DeliveredAgain(), "db"), &a) != nil || a.Waits != "db 1.2.0 does not run healthy on ace" ||
|
||||
len(saidOf(meshwants.EventVersionDelivered)) != before+1 {
|
||||
t.Errorf("the ask of 1.2.0 = %+v", a)
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ func TestTheWantsAreSaidWantedFromTheKeptAssignmentGraphRevision(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
at := time.Date(2026, 11, 12, 9, 0, 0, 0, time.UTC)
|
||||
kv := &memKV{values: map[string][]byte{}}
|
||||
p := newPasser(kvSet{buckets.Wants: kv}, at)
|
||||
p := newPasser(kvSet{buckets.Wants(): kv}, at)
|
||||
p.logf = t.Logf
|
||||
var events []said
|
||||
p.publishWants = func(event string, body any, id string) error {
|
||||
@@ -60,9 +60,9 @@ func TestTheWantsAreSaidWantedFromTheKeptAssignmentGraphRevision(t *testing.T) {
|
||||
t.Fatalf("with no revision kept: %d events, %d puts", len(events), kv.puts)
|
||||
}
|
||||
key, _ := mwc.WantKey("ace", "web")
|
||||
delete(p.w.read(buckets.Wants), key) // the world forgets the want, as before the first pass
|
||||
delete(p.w.read(buckets.Wants()), key) // the world forgets the want, as before the first pass
|
||||
kv.puts = 0
|
||||
p.w.put(buckets.Wants, key, nil)
|
||||
p.w.put(buckets.Wants(), key, nil)
|
||||
|
||||
p.keepGraph(ctx)
|
||||
if len(events) != 1 || events[0].event != meshwants.EventAssignmentRevision || len(objects) == 0 {
|
||||
|
||||
@@ -164,12 +164,12 @@ func startBed(t *testing.T, leaveOut string) *bed {
|
||||
t.Cleanup(cancel)
|
||||
b := &bed{srv: srv, js: js, ctx: ctx, dir: dir, buckets: map[string]jetstream.KeyValue{}, journal: &journal{grew: make(chan struct{})},
|
||||
cat: &catalogBed{entries: map[string]map[string]meshcatalog.CatalogEntry{}, rev: map[string]int64{}, bodies: map[string]string{}}}
|
||||
for _, name := range append(append([]string{}, readBuckets...), ownBuckets...) {
|
||||
for _, name := range append(append([]string{}, readBuckets...), ownBuckets()...) {
|
||||
if name == leaveOut {
|
||||
continue
|
||||
}
|
||||
cfg := jetstream.KeyValueConfig{Bucket: name, History: 5}
|
||||
if name == plc.PulsesBucket {
|
||||
if name == plc.PulsesBucket() {
|
||||
cfg.History, cfg.LimitMarkerTTL = 1, time.Minute // a time to live per key, as the controller makes it (key-ttl)
|
||||
}
|
||||
kv, err := js.CreateKeyValue(ctx, cfg)
|
||||
@@ -240,10 +240,10 @@ func startBed(t *testing.T, leaveOut string) *bed {
|
||||
// The seat list (ADR 0636), which the assigner's seat server checks its verbs' callers against (A-278).
|
||||
b.seatListRead = serveSeatList(t, nc)
|
||||
// The assigner's views family is its ordinary events on EVENTS (ADR 0635 §5); its marks are its state.
|
||||
if kv, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: marksBucket, History: 1}); err != nil {
|
||||
if kv, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: marksBucket(), History: 1}); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
b.buckets[marksBucket] = kv
|
||||
b.buckets[marksBucket()] = kv
|
||||
}
|
||||
// The condition keeper's verb conditions (design 47 G §706): no condition is open, so a failing check is the delivery's after the
|
||||
// settle, which the bed sets to a second.
|
||||
@@ -378,14 +378,14 @@ func TestTheAssignerDoesNotStartWithoutABucketItReads(t *testing.T) {
|
||||
was := PutsReopenEvery
|
||||
PutsReopenEvery = 0
|
||||
defer func() { PutsReopenEvery = was }()
|
||||
b := startBed(t, buckets.Puts)
|
||||
b := startBed(t, buckets.Puts())
|
||||
done := b.start(t)
|
||||
select {
|
||||
case err := <-done:
|
||||
t.Fatalf("without %s the assigner stopped: %v", buckets.Puts, err)
|
||||
t.Fatalf("without %s the assigner stopped: %v", buckets.Puts(), err)
|
||||
case <-time.After(2 * time.Second):
|
||||
}
|
||||
putsKV, err := b.js.CreateKeyValue(b.ctx, jetstream.KeyValueConfig{Bucket: buckets.Puts, History: 5})
|
||||
putsKV, err := b.js.CreateKeyValue(b.ctx, jetstream.KeyValueConfig{Bucket: buckets.Puts(), History: 5})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -412,37 +412,37 @@ func TestTheAssignerDoesNotStartWithoutABucketItReads(t *testing.T) {
|
||||
|
||||
// Without the seat's assignments state it starts all the same, and once the bucket is made (the controller makes
|
||||
// it from the manifest) it is read with no restart: a node it names gets its own want, the node composing its own.
|
||||
pb := startBed(t, plc.AssignmentsBucket)
|
||||
pb := startBed(t, plc.AssignmentsBucket())
|
||||
pdone := pb.start(t)
|
||||
select {
|
||||
case err := <-pdone:
|
||||
t.Fatalf("without %s the assigner stopped: %v", plc.AssignmentsBucket, err)
|
||||
t.Fatalf("without %s the assigner stopped: %v", plc.AssignmentsBucket(), err)
|
||||
case <-time.After(2 * time.Second):
|
||||
}
|
||||
// ADR 0641: the state the seat's assignments were kept in before holds n1's keys, and the form of one key per node; the
|
||||
// assigner copies the keys into the state made after its start, not the second form, and leaves the old state as it is.
|
||||
values, _ := mmac.AssignmentsValues(mma.Assignments{Node: "n1", Modules: []mma.PlacedModule{{Module: "web"}}})
|
||||
for _, k := range slices.Sorted(maps.Keys(values)) { // a key per module, then the node key
|
||||
pb.putJSON(t, retiredBucket, k, values[k])
|
||||
pb.putJSON(t, retiredBucket(), k, values[k])
|
||||
}
|
||||
pb.putJSON(t, retiredBucket, "placement.n1", values["n1.node"])
|
||||
assignments, err := pb.js.CreateKeyValue(pb.ctx, jetstream.KeyValueConfig{Bucket: plc.AssignmentsBucket, History: 5})
|
||||
pb.putJSON(t, retiredBucket(), "placement.n1", values["n1.node"])
|
||||
assignments, err := pb.js.CreateKeyValue(pb.ctx, jetstream.KeyValueConfig{Bucket: plc.AssignmentsBucket(), History: 5})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if nw, err := mwc.DecodeNodeWant(pb.get(t, buckets.Wants, "want.n1.node")); err != nil || !nw.Self {
|
||||
if nw, err := mwc.DecodeNodeWant(pb.get(t, buckets.Wants(), "want.n1.node")); err != nil || !nw.Self {
|
||||
t.Fatalf("the assignments state made after the start was not read: n1's own want = %+v, %v", nw, err)
|
||||
}
|
||||
for _, k := range slices.Sorted(maps.Keys(values)) {
|
||||
if e, err := assignments.Get(pb.ctx, k); err != nil || !slices.Equal(e.Value(), values[k]) {
|
||||
t.Errorf("%s was not copied into %s: %v", k, plc.AssignmentsBucket, err)
|
||||
t.Errorf("%s was not copied into %s: %v", k, plc.AssignmentsBucket(), err)
|
||||
}
|
||||
}
|
||||
// The readers delivered before this one still read the retired state: nothing in it is deleted or written, and the end of the copy is marked in the state copies.
|
||||
if _, err := pb.buckets[copiesBucket].Get(pb.ctx, copiedMarker); err != nil {
|
||||
t.Errorf("the end of the copy is not marked in %s: %v", copiesBucket, err)
|
||||
if _, err := pb.buckets[copiesBucket()].Get(pb.ctx, copiedMarker); err != nil {
|
||||
t.Errorf("the end of the copy is not marked in %s: %v", copiesBucket(), err)
|
||||
}
|
||||
if left, err := pb.buckets[retiredBucket].Keys(pb.ctx); err != nil || len(left) != len(values)+1 {
|
||||
if left, err := pb.buckets[retiredBucket()].Keys(pb.ctx); err != nil || len(left) != len(values)+1 {
|
||||
t.Errorf("the retired state holds %v (%v), want its %d keys as they were", left, err, len(values)+1)
|
||||
}
|
||||
}
|
||||
@@ -501,7 +501,7 @@ func (b *bed) place(t *testing.T, node, module string) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for key, raw := range values {
|
||||
b.putJSON(t, plc.AssignmentsBucket, key, raw)
|
||||
b.putJSON(t, plc.AssignmentsBucket(), key, raw)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -563,7 +563,7 @@ func TestWantsAreWrittenAndNoDifferenceIs(t *testing.T) {
|
||||
// the indexer and graph, and reads nothing of the indexer's state; the answer clears it.
|
||||
firstViewBound, firstViewWait = 300*time.Millisecond, 50*time.Millisecond
|
||||
t.Cleanup(func() { catalogLate, firstViewBound, firstViewWait = nil, 0, 0 })
|
||||
for _, name := range append(append([]string{}, readBuckets...), watched...) {
|
||||
for _, name := range append(append([]string{}, readBuckets...), watched()...) {
|
||||
if strings.HasPrefix(name, buckets.IndexerModule+"_") {
|
||||
t.Errorf("the assigner reads the indexer's state %s itself", name)
|
||||
}
|
||||
@@ -603,9 +603,9 @@ func TestWantsAreWrittenAndNoDifferenceIs(t *testing.T) {
|
||||
"wants": {"n1.mesh-grants"},
|
||||
})
|
||||
// The reader raises the finding of the first view through the module's finding writer (A-26), its id escaped as a key's name.
|
||||
graphKey := views.FindingKey(views.FindingScope, keys.EscapeName(assignment.Module+"."+meshcatalog.Seat+"."+views.BranchState(meshcatalog.StateCatalogRevisions, trunkBranch)), views.UnansweredKind)
|
||||
graphKey := views.FindingKey(views.FindingScope, keys.EscapeName(assignment.Module()+"."+meshcatalog.Seat+"."+views.BranchState(meshcatalog.StateCatalogRevisions, trunkBranch)), views.UnansweredKind)
|
||||
graphRaised := func() bool {
|
||||
e, err := b.buckets[assign.FindingsBucket].Get(b.ctx, graphKey)
|
||||
e, err := b.buckets[assign.FindingsBucket()].Get(b.ctx, graphKey)
|
||||
return err == nil && strings.Contains(string(e.Value()), "mesh-catalog.revision")
|
||||
}
|
||||
for deadline := time.Now().Add(10 * time.Second); !graphRaised(); time.Sleep(50 * time.Millisecond) {
|
||||
@@ -621,7 +621,7 @@ func TestWantsAreWrittenAndNoDifferenceIs(t *testing.T) {
|
||||
}
|
||||
|
||||
wkey, _ := mwc.WantKey("ace", "web")
|
||||
want, err := mwc.DecodeWant(b.get(t, buckets.Wants, wkey))
|
||||
want, err := mwc.DecodeWant(b.get(t, buckets.Wants(), wkey))
|
||||
if err != nil || want.Fp != fNew {
|
||||
t.Fatalf("the canary ace's want = %+v, %v", want, err)
|
||||
}
|
||||
@@ -642,10 +642,10 @@ func TestWantsAreWrittenAndNoDifferenceIs(t *testing.T) {
|
||||
if neither.Subject.Machine != "g14" || !strings.HasPrefix(neither.Subject.ID, "g14.web") || neither.Severity != occ.SeverityUrgent || !strings.Contains(neither.Explanation, "canary") {
|
||||
t.Errorf("the condition of web on neither list on g14 = %+v", neither)
|
||||
}
|
||||
if keys, err := b.buckets[buckets.Differences].Keys(b.ctx); err == nil && len(keys) != 0 {
|
||||
if keys, err := b.buckets[buckets.Differences()].Keys(b.ctx); err == nil && len(keys) != 0 {
|
||||
t.Errorf("differences were written: %v", keys)
|
||||
}
|
||||
if e, err := b.buckets[buckets.Wants].Get(b.ctx, "want.g14.web"); err == nil {
|
||||
if e, err := b.buckets[buckets.Wants()].Get(b.ctx, "want.g14.web"); err == nil {
|
||||
t.Errorf("g14, which runs an old-form build and is held back by the canary, was given a want: %s", e.Value())
|
||||
}
|
||||
|
||||
@@ -674,7 +674,7 @@ func TestWantsAreWrittenAndNoDifferenceIs(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, k := range slices.Sorted(maps.Keys(values)) {
|
||||
b.putJSON(t, plc.AssignmentsBucket, k, values[k])
|
||||
b.putJSON(t, plc.AssignmentsBucket(), k, values[k])
|
||||
}
|
||||
}
|
||||
if w := b.wantNow(t, "ace", "web", "ace's want once placed: the build alone, composed by the node", func(w mwc.Want) bool { return w.Self }); w.Composed != "" || w.Fp != fNew {
|
||||
@@ -683,7 +683,7 @@ func TestWantsAreWrittenAndNoDifferenceIs(t *testing.T) {
|
||||
nkey, _ := mwc.WantNodeKey("ace")
|
||||
var self bool
|
||||
for deadline := time.Now().Add(10 * time.Second); time.Now().Before(deadline) && !self; time.Sleep(100 * time.Millisecond) {
|
||||
if e, err := b.buckets[buckets.Wants].Get(b.ctx, nkey); err == nil {
|
||||
if e, err := b.buckets[buckets.Wants()].Get(b.ctx, nkey); err == nil {
|
||||
nw, err := mwc.DecodeNodeWant(e.Value())
|
||||
self = err == nil && nw.Self
|
||||
}
|
||||
@@ -739,7 +739,7 @@ func TestWantsAreWrittenAndNoDifferenceIs(t *testing.T) {
|
||||
t.Errorf("tool's want = %+v", w)
|
||||
}
|
||||
b.wantNow(t, "ace", "db", "the provider's want at its fingerprint", func(w mwc.Want) bool { return w.Fp == fpOf('2') })
|
||||
if e, err := b.buckets[buckets.Wants].Get(b.ctx, "want.ace.app"); err == nil {
|
||||
if e, err := b.buckets[buckets.Wants()].Get(b.ctx, "want.ace.app"); err == nil {
|
||||
t.Errorf("app was wanted while its provision was asked: %s", e.Value())
|
||||
}
|
||||
asked(meshprovisionerclient.Provided, "")
|
||||
@@ -816,7 +816,7 @@ func TestWantsAreWrittenAndNoDifferenceIs(t *testing.T) {
|
||||
statement := func(node string, ok func(enginereads.Statement) bool) enginereads.Statement {
|
||||
var last enginereads.Statement
|
||||
for deadline := time.Now().Add(15 * time.Second); time.Now().Before(deadline); time.Sleep(50 * time.Millisecond) {
|
||||
if e, err := b.buckets[engineReadsBucket].Get(b.ctx, node); err == nil {
|
||||
if e, err := b.buckets[engineReadsBucket()].Get(b.ctx, node); err == nil {
|
||||
if last, err = enginereads.Decode(e.Value()); err == nil && ok(last) {
|
||||
return last
|
||||
}
|
||||
@@ -861,7 +861,7 @@ func TestWantsAreWrittenAndNoDifferenceIs(t *testing.T) {
|
||||
}
|
||||
b.wantNow(t, "ace", "forms", "the want moved once ace's engine said it reads the form", func(w mwc.Want) bool { return w.Fp == formsNew })
|
||||
// assigner_engine_reads asks ace's engine again through the state: the assigner answers there with what it states now.
|
||||
again, err := enginereads.AskAgainOf(bedState{b, engineReadsBucket}, "ace", "n1.ada", "a person asks", time.Now(), 15*time.Second)
|
||||
again, err := enginereads.AskAgainOf(bedState{b, engineReadsBucket()}, "ace", "n1.ada", "a person asks", time.Now(), 15*time.Second)
|
||||
if err != nil || again.From != enginereads.FromVerb || !slices.Contains(again.ManifestReads, "form:new") || again.AskAgain != nil {
|
||||
t.Errorf("the ask again of ace answered %+v, %v", again, err)
|
||||
}
|
||||
@@ -919,12 +919,12 @@ func TestAHeldFingerprintIsAskedAbout(t *testing.T) {
|
||||
b.start(t)
|
||||
hkey, _ := mwc.HeldKey("web", fNew)
|
||||
// 1906/H3: the held record names the machine and keeps the health, when and the node's words.
|
||||
if h, err := mwc.DecodeHeld(b.get(t, buckets.Wants, hkey)); err != nil || h.Node != "ace" ||
|
||||
if h, err := mwc.DecodeHeld(b.get(t, buckets.Wants(), hkey)); err != nil || h.Node != "ace" ||
|
||||
!strings.HasPrefix(h.Words, "health failed, phase running, seen at ") || !strings.HasSuffix(h.Words, ": connection refused") {
|
||||
t.Fatalf("held = %+v, %v", h, err)
|
||||
}
|
||||
akey, _ := asks.HoldAskKey("web", fNew)
|
||||
a, err := decodeAsk(akey, b.get(t, buckets.Asks, akey))
|
||||
a, err := decodeAsk(akey, b.get(t, buckets.Asks(), akey))
|
||||
if err != nil || a.Asking <= 0 || a.Ask.ID != meshdeliveryclient.AskingID(akey, a.Asking) || len(a.Ask.Options) != 2 {
|
||||
t.Fatalf("the ask = %+v, %v", a, err)
|
||||
}
|
||||
@@ -935,7 +935,7 @@ func TestAHeldFingerprintIsAskedAbout(t *testing.T) {
|
||||
// with the words its last finding said and the subject the finding names (`<module>[.<fingerprint>]`).
|
||||
func (b *bed) conditionsSaid(t *testing.T) map[string]oc.Condition {
|
||||
t.Helper()
|
||||
w, err := b.buckets[assign.FindingsBucket].WatchAll(b.ctx, jetstream.IncludeHistory())
|
||||
w, err := b.buckets[assign.FindingsBucket()].WatchAll(b.ctx, jetstream.IncludeHistory())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -973,16 +973,16 @@ func TestAFailedDataModuleIsAskedToRestore(t *testing.T) {
|
||||
b.putJSON(t, buckets.Have, "ace.mail", failed)
|
||||
wantKey, _ := mwc.WantKey("ace", "mail")
|
||||
want, _ := mwc.EncodeWant(mwc.Want{Fp: fNew, Composed: "sha256:" + strings.Repeat("c", 64)})
|
||||
b.putJSON(t, buckets.Wants, wantKey, want)
|
||||
b.putJSON(t, buckets.Wants(), wantKey, want)
|
||||
b.place(t, "ace", "mail")
|
||||
b.start(t)
|
||||
|
||||
akey, _ := asks.RestoreAskKey("ace", "mail", fNew)
|
||||
a, err := decodeAsk(akey, b.get(t, buckets.Asks, akey))
|
||||
a, err := decodeAsk(akey, b.get(t, buckets.Asks(), akey))
|
||||
if err != nil || len(a.Ask.Options) != 2 || a.Ask.Options[0].ID != "restore" || a.Ask.Options[1].ID != "leave-stopped" {
|
||||
t.Fatalf("the restore ask = %+v, %v", a, err)
|
||||
}
|
||||
if w, err := mwc.DecodeWant(b.get(t, buckets.Wants, wantKey)); err != nil || w.Fp != fNew {
|
||||
if w, err := mwc.DecodeWant(b.get(t, buckets.Wants(), wantKey)); err != nil || w.Fp != fNew {
|
||||
t.Errorf("the data module was set back: want = %+v, %v", w, err)
|
||||
}
|
||||
key := ""
|
||||
@@ -1028,7 +1028,7 @@ func TestTheAssignerReplacedResumesWithNothingLostOrRepeated(t *testing.T) {
|
||||
b.place(t, "ace", name)
|
||||
}
|
||||
count := func() int {
|
||||
keys, err := b.buckets[buckets.Wants].Keys(b.ctx)
|
||||
keys, err := b.buckets[buckets.Wants()].Keys(b.ctx)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
@@ -1044,7 +1044,7 @@ func TestTheAssignerReplacedResumesWithNothingLostOrRepeated(t *testing.T) {
|
||||
time.Sleep(time.Second) // and no pass of the second writes any again
|
||||
for _, name := range names {
|
||||
key, _ := mwc.WantKey("ace", name)
|
||||
history, err := b.buckets[buckets.Wants].History(b.ctx, key)
|
||||
history, err := b.buckets[buckets.Wants()].History(b.ctx, key)
|
||||
if err != nil {
|
||||
t.Errorf("the want of %s was never written: %v", name, err)
|
||||
continue
|
||||
@@ -1077,7 +1077,7 @@ func (b *bed) putWant(t *testing.T, node, module string, w mwc.Want) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b.putJSON(t, buckets.Wants, key, v)
|
||||
b.putJSON(t, buckets.Wants(), key, v)
|
||||
}
|
||||
|
||||
// wantNow waits until the want of module on node satisfies ok and answers it.
|
||||
@@ -1086,7 +1086,7 @@ func (b *bed) wantNow(t *testing.T, node, module, what string, ok func(mwc.Want)
|
||||
key, _ := mwc.WantKey(node, module)
|
||||
var last mwc.Want
|
||||
for deadline := time.Now().Add(15 * time.Second); time.Now().Before(deadline); time.Sleep(50 * time.Millisecond) {
|
||||
if e, err := b.buckets[buckets.Wants].Get(b.ctx, key); err == nil {
|
||||
if e, err := b.buckets[buckets.Wants()].Get(b.ctx, key); err == nil {
|
||||
if w, err := mwc.DecodeWant(e.Value()); err == nil {
|
||||
last = w
|
||||
if ok(w) {
|
||||
@@ -1102,7 +1102,7 @@ func (b *bed) wantNow(t *testing.T, node, module, what string, ok func(mwc.Want)
|
||||
// answer says the teller wrote the operator's answer to the ask that stands at key (its warrant names that asking's id).
|
||||
func (b *bed) answer(t *testing.T, key, option string) {
|
||||
t.Helper()
|
||||
a, err := decodeAsk(key, b.get(t, buckets.Asks, key))
|
||||
a, err := decodeAsk(key, b.get(t, buckets.Asks(), key))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -1143,7 +1143,7 @@ func TestAReleaseAnswerWritesTheWantAgain(t *testing.T) {
|
||||
t.Fatalf("the set-back want = %+v", w)
|
||||
}
|
||||
hold, _ := asks.HoldAskKey("web", fNew)
|
||||
b.get(t, buckets.Asks, hold)
|
||||
b.get(t, buckets.Asks(), hold)
|
||||
b.answer(t, hold, "release")
|
||||
w = b.wantNow(t, "ace", "web", "the held fingerprint's want written again", func(w mwc.Want) bool { return w.Fp == fNew })
|
||||
if w.Composed != "" || !w.Self || w.Rule != 0 {
|
||||
@@ -1165,7 +1165,7 @@ func TestAReleaseAnswerWritesTheWantAgain(t *testing.T) {
|
||||
func (b *bed) askNow(t *testing.T, key string, asking int) decodedAsk {
|
||||
t.Helper()
|
||||
for deadline := time.Now().Add(15 * time.Second); time.Now().Before(deadline); time.Sleep(50 * time.Millisecond) {
|
||||
if e, err := b.buckets[buckets.Asks].Get(b.ctx, key); err == nil {
|
||||
if e, err := b.buckets[buckets.Asks()].Get(b.ctx, key); err == nil {
|
||||
if a, err := decodeAsk(key, e.Value()); err == nil && a.Asking == asking {
|
||||
return a
|
||||
}
|
||||
@@ -1224,11 +1224,11 @@ func TestABackupAnswerIsWrittenIntoTheNodesWant(t *testing.T) {
|
||||
for k, c := range b.conditionsSaid(t) {
|
||||
cleared = cleared || (strings.Contains(k, "backup") && c.State == occ.StateCleared)
|
||||
}
|
||||
if !b.hasKey(buckets.Asks, akey) && cleared {
|
||||
if !b.hasKey(buckets.Asks(), akey) && cleared {
|
||||
break
|
||||
}
|
||||
if time.Now().After(deadline) {
|
||||
t.Fatalf("the backup of a move the want left behind: ask there %v, condition cleared %v", b.hasKey(buckets.Asks, akey), cleared)
|
||||
t.Fatalf("the backup of a move the want left behind: ask there %v, condition cleared %v", b.hasKey(buckets.Asks(), akey), cleared)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1249,12 +1249,12 @@ func TestAHoldIsLiftedOnceEveryNodeRunsItHealthy(t *testing.T) {
|
||||
b.start(t)
|
||||
hkey, _ := mwc.HeldKey("web", fNew)
|
||||
akey, _ := asks.HoldAskKey("web", fNew)
|
||||
b.get(t, buckets.Wants, hkey)
|
||||
b.get(t, buckets.Asks, akey)
|
||||
b.get(t, buckets.Wants(), hkey)
|
||||
b.get(t, buckets.Asks(), akey)
|
||||
healthy, _ := nodeengineclient.EncodeHave(nodeengine.HaveByNodeModuleValue{Fp: fNew, Phase: nodeengineclient.PhaseRunning, Health: nodeengineclient.HealthOK})
|
||||
b.putJSON(t, buckets.Have, "ace.web", healthy)
|
||||
for deadline := time.Now().Add(15 * time.Second); time.Now().Before(deadline); time.Sleep(100 * time.Millisecond) {
|
||||
if b.hasKey(buckets.Wants, hkey) || b.hasKey(buckets.Asks, akey) {
|
||||
if b.hasKey(buckets.Wants(), hkey) || b.hasKey(buckets.Asks(), akey) {
|
||||
continue
|
||||
}
|
||||
for k, c := range b.conditionsSaid(t) {
|
||||
@@ -1266,7 +1266,7 @@ func TestAHoldIsLiftedOnceEveryNodeRunsItHealthy(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
t.Fatalf("the hold was not lifted: held %v, ask %v", b.hasKey(buckets.Wants, hkey), b.hasKey(buckets.Asks, akey))
|
||||
t.Fatalf("the hold was not lifted: held %v, ask %v", b.hasKey(buckets.Wants(), hkey), b.hasKey(buckets.Asks(), akey))
|
||||
}
|
||||
|
||||
// 1906/H2, whole on the bus: a hold lifted by itself and written again after the assigner was started again is asked under a
|
||||
@@ -1283,12 +1283,12 @@ func TestAHoldWrittenAgainAfterALiftAndARestartIsAskedAgain(t *testing.T) {
|
||||
_, stop := b.startStoppable(t)
|
||||
hkey, _ := mwc.HeldKey("web", fNew)
|
||||
akey, _ := asks.HoldAskKey("web", fNew)
|
||||
first, err := decodeAsk(akey, b.get(t, buckets.Asks, akey))
|
||||
first, err := decodeAsk(akey, b.get(t, buckets.Asks(), akey))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b.putJSON(t, buckets.Have, "ace.web", healthy)
|
||||
for deadline := time.Now().Add(15 * time.Second); b.hasKey(buckets.Wants, hkey); time.Sleep(100 * time.Millisecond) {
|
||||
for deadline := time.Now().Add(15 * time.Second); b.hasKey(buckets.Wants(), hkey); time.Sleep(100 * time.Millisecond) {
|
||||
if time.Now().After(deadline) {
|
||||
t.Fatal("the hold was not lifted")
|
||||
}
|
||||
@@ -1298,7 +1298,7 @@ func TestAHoldWrittenAgainAfterALiftAndARestartIsAskedAgain(t *testing.T) {
|
||||
b.answerTo(t, akey, "keep", first.Ask.ID) // an answer to the first hold, which the later hold must not take
|
||||
b.putJSON(t, buckets.Have, "ace.web", failed)
|
||||
_, stop = b.startStoppable(t)
|
||||
again, err := decodeAsk(akey, b.get(t, buckets.Asks, akey))
|
||||
again, err := decodeAsk(akey, b.get(t, buckets.Asks(), akey))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -1326,7 +1326,7 @@ func TestAHoldWrittenAgainAfterALiftAndARestartIsAskedAgain(t *testing.T) {
|
||||
subscribed(t, nc)
|
||||
b.start(t)
|
||||
for deadline := time.Now().Add(15 * time.Second); ; time.Sleep(50 * time.Millisecond) {
|
||||
if third, err := decodeAsk(akey, b.get(t, buckets.Asks, akey)); err == nil && third.Ask.ID != again.Ask.ID {
|
||||
if third, err := decodeAsk(akey, b.get(t, buckets.Asks(), akey)); err == nil && third.Ask.ID != again.Ask.ID {
|
||||
break
|
||||
}
|
||||
if time.Now().After(deadline) {
|
||||
@@ -1334,7 +1334,7 @@ func TestAHoldWrittenAgainAfterALiftAndARestartIsAskedAgain(t *testing.T) {
|
||||
}
|
||||
}
|
||||
b.putJSON(t, buckets.Have, "ace.web", healthy)
|
||||
for deadline := time.Now().Add(15 * time.Second); b.hasKey(buckets.Wants, hkey); time.Sleep(100 * time.Millisecond) {
|
||||
for deadline := time.Now().Add(15 * time.Second); b.hasKey(buckets.Wants(), hkey); time.Sleep(100 * time.Millisecond) {
|
||||
if time.Now().After(deadline) {
|
||||
t.Fatal("the stale \"keep\" of the first hold kept the second one held")
|
||||
}
|
||||
@@ -1349,7 +1349,7 @@ func (s bedStore) kv(state string) jetstream.KeyValue {
|
||||
if strings.Contains(state, ".") {
|
||||
return s.b.buckets[strings.Replace(state, ".", "_", 1)]
|
||||
}
|
||||
return s.b.buckets[assignment.Module+"_"+state]
|
||||
return s.b.buckets[assignment.Module()+"_"+state]
|
||||
}
|
||||
|
||||
func (s bedStore) get(state, key string) ([]byte, uint64, bool, error) {
|
||||
@@ -1391,7 +1391,7 @@ func TestAProposalIsAnsweredApprovedAndWritten(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if _, err := b.js.CreateStream(b.ctx, jetstream.StreamConfig{Name: "MOD_" + assignment.Module + "_assignments",
|
||||
if _, err := b.js.CreateStream(b.ctx, jetstream.StreamConfig{Name: "MOD_" + assignment.Module() + "_assignments",
|
||||
Subjects: assignmentsSubjects()}); err != nil { // the seat's four events: the views family has a stream of its own
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -1433,11 +1433,11 @@ func TestAProposalIsAnsweredApprovedAndWritten(t *testing.T) {
|
||||
if k == key {
|
||||
continue
|
||||
}
|
||||
if _, err := b.buckets[plc.AssignmentsBucket].Put(b.ctx, k, values[k]); err != nil {
|
||||
if _, err := b.buckets[plc.AssignmentsBucket()].Put(b.ctx, k, values[k]); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
rev, err := b.buckets[plc.AssignmentsBucket].Put(b.ctx, key, values[key])
|
||||
rev, err := b.buckets[plc.AssignmentsBucket()].Put(b.ctx, key, values[key])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -1452,9 +1452,9 @@ func TestAProposalIsAnsweredApprovedAndWritten(t *testing.T) {
|
||||
// The holder converts anchor, held in the form before alone, to its node and module keys once at its start.
|
||||
var anchorRev uint64
|
||||
for deadline := time.Now().Add(15 * time.Second); anchorRev == 0; time.Sleep(50 * time.Millisecond) {
|
||||
if e, err := b.buckets[plc.AssignmentsBucket].Get(b.ctx, "anchor.node"); err == nil {
|
||||
if e, err := b.buckets[plc.AssignmentsBucket()].Get(b.ctx, "anchor.node"); err == nil {
|
||||
anchorRev = e.Revision()
|
||||
if _, err := b.buckets[plc.AssignmentsBucket].Get(b.ctx, "anchor.web"); err != nil {
|
||||
if _, err := b.buckets[plc.AssignmentsBucket()].Get(b.ctx, "anchor.web"); err != nil {
|
||||
t.Fatalf("anchor converted without its module key: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -1487,7 +1487,7 @@ func TestAProposalIsAnsweredApprovedAndWritten(t *testing.T) {
|
||||
t.Helper()
|
||||
var last mma.Proposal
|
||||
for deadline := time.Now().Add(15 * time.Second); time.Now().Before(deadline); time.Sleep(50 * time.Millisecond) {
|
||||
if e, err := b.buckets[plc.ProposalsBucket].Get(b.ctx, id); err == nil {
|
||||
if e, err := b.buckets[plc.ProposalsBucket()].Get(b.ctx, id); err == nil {
|
||||
if p, err := plc.DecodeProposal(e.Value()); err == nil {
|
||||
if last = p; ok(p) {
|
||||
return p
|
||||
@@ -1502,7 +1502,7 @@ func TestAProposalIsAnsweredApprovedAndWritten(t *testing.T) {
|
||||
t.Helper()
|
||||
for _, node := range p.Must {
|
||||
key, _ := plc.CandidateKey(p.ID, node)
|
||||
e, err := b.buckets[plc.CandidatesBucket].Get(b.ctx, key)
|
||||
e, err := b.buckets[plc.CandidatesBucket()].Get(b.ctx, key)
|
||||
if err != nil {
|
||||
t.Fatalf("no candidate %s: %v", key, err)
|
||||
}
|
||||
@@ -1534,7 +1534,7 @@ func TestAProposalIsAnsweredApprovedAndWritten(t *testing.T) {
|
||||
// The node key names the modules, each module key carries its own.
|
||||
values := map[string][]byte{}
|
||||
for _, key := range []string{node + ".node", node + ".web", node + ".shell"} {
|
||||
if e, err := b.buckets[plc.AssignmentsBucket].Get(b.ctx, key); err == nil {
|
||||
if e, err := b.buckets[plc.AssignmentsBucket()].Get(b.ctx, key); err == nil {
|
||||
values[key] = e.Value()
|
||||
}
|
||||
}
|
||||
@@ -1543,7 +1543,7 @@ func TestAProposalIsAnsweredApprovedAndWritten(t *testing.T) {
|
||||
t.Errorf("%s = %v, %v, want %v", node, mmac.Assigned(got[node]), err, want)
|
||||
}
|
||||
}
|
||||
if b.hasKey(plc.ProposalsBucket, p.ID) {
|
||||
if b.hasKey(plc.ProposalsBucket(), p.ID) {
|
||||
t.Error("an applied proposal stays in the state")
|
||||
}
|
||||
|
||||
@@ -1561,13 +1561,13 @@ func TestAProposalIsAnsweredApprovedAndWritten(t *testing.T) {
|
||||
proposalNow(moved.ID, "asked", func(p mma.Proposal) bool { return p.State == mmac.ProposalAsked })
|
||||
akey, _ = asks.AssignmentsAskKey(moved.ID)
|
||||
b.answer(t, akey, assign.OptionNo)
|
||||
for deadline := time.Now().Add(15 * time.Second); b.hasKey(plc.ProposalsBucket, moved.ID); time.Sleep(50 * time.Millisecond) {
|
||||
for deadline := time.Now().Add(15 * time.Second); b.hasKey(plc.ProposalsBucket(), moved.ID); time.Sleep(50 * time.Millisecond) {
|
||||
if time.Now().After(deadline) {
|
||||
t.Fatal("a declined proposal was not ended")
|
||||
}
|
||||
}
|
||||
key, _ := mmac.NodeKey("laptop")
|
||||
if e, _ := b.buckets[plc.AssignmentsBucket].Get(b.ctx, key); e == nil || !strings.Contains(string(e.Value()), "web") {
|
||||
if e, _ := b.buckets[plc.AssignmentsBucket()].Get(b.ctx, key); e == nil || !strings.Contains(string(e.Value()), "web") {
|
||||
t.Error("a declined proposal wrote its key")
|
||||
}
|
||||
|
||||
@@ -1608,10 +1608,10 @@ func TestAProposalIsAnsweredApprovedAndWritten(t *testing.T) {
|
||||
case <-time.After(15 * time.Second):
|
||||
t.Fatal("no assignments-changed heard for the joining node")
|
||||
}
|
||||
if ace, err := b.buckets[plc.AssignmentsBucket].Get(b.ctx, "ace.node"); err != nil || !strings.Contains(string(ace.Value()), "10.9.0.2") {
|
||||
if ace, err := b.buckets[plc.AssignmentsBucket()].Get(b.ctx, "ace.node"); err != nil || !strings.Contains(string(ace.Value()), "10.9.0.2") {
|
||||
t.Errorf("the joining node's key = %v, %v", ace, err)
|
||||
}
|
||||
if akey, _ := asks.AssignmentsAskKey(joined.(proposed).Proposal.ID); b.hasKey(buckets.Asks, akey) || b.hasKey(plc.ProposalsBucket, joined.(proposed).Proposal.ID) {
|
||||
if akey, _ := asks.AssignmentsAskKey(joined.(proposed).Proposal.ID); b.hasKey(buckets.Asks(), akey) || b.hasKey(plc.ProposalsBucket(), joined.(proposed).Proposal.ID) {
|
||||
t.Error("an ask was raised for the joining node, or its proposal stays open")
|
||||
}
|
||||
// Its enrolment ends before it confirms: a remove-node on the same warrant deletes its key at once, with no ask, and
|
||||
@@ -1631,7 +1631,7 @@ func TestAProposalIsAnsweredApprovedAndWritten(t *testing.T) {
|
||||
case <-time.After(15 * time.Second):
|
||||
t.Fatal("no assignments-changed heard for the removed node")
|
||||
}
|
||||
if _, err := b.buckets[plc.AssignmentsBucket].Get(b.ctx, "ace.node"); err == nil {
|
||||
if _, err := b.buckets[plc.AssignmentsBucket()].Get(b.ctx, "ace.node"); err == nil {
|
||||
t.Error("the removed node's key stays")
|
||||
}
|
||||
}
|
||||
@@ -1640,15 +1640,15 @@ func TestAProposalIsAnsweredApprovedAndWritten(t *testing.T) {
|
||||
// the bucket does, and checks that no views event landed where a waiter on the assigner's put waits.
|
||||
func (b *bed) viewsHold(t *testing.T, state, key string) {
|
||||
t.Helper()
|
||||
kv, marks := b.buckets[viewedStates[state]], b.buckets[marksBucket]
|
||||
kv, marks := b.buckets[viewedStates[state]], b.buckets[marksBucket()]
|
||||
src := func(kv jetstream.KeyValue) views.Source { return bedSource{b.ctx, kv} }
|
||||
st, err := b.js.Stream(b.ctx, "EVENTS")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for deadline := time.Now().Add(20 * time.Second); ; time.Sleep(50 * time.Millisecond) {
|
||||
f := &views.Follower{Owner: assignment.Module, Tool: state, State: state, Ask: func(context.Context, string) (views.Answer, error) {
|
||||
return views.FirstView(assignment.Module, state, src(kv), src(marks), "", time.Now())
|
||||
f := &views.Follower{Owner: assignment.Module(), Tool: state, State: state, Ask: func(context.Context, string) (views.Answer, error) {
|
||||
return views.FirstView(assignment.Module(), state, src(kv), src(marks), "", time.Now())
|
||||
}}
|
||||
if err := f.Start(b.ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -1657,7 +1657,7 @@ func (b *bed) viewsHold(t *testing.T, state, key string) {
|
||||
gap := false
|
||||
for seq := info.State.FirstSeq; seq > 0 && seq <= info.State.LastSeq; seq++ {
|
||||
m, err := st.GetMsg(b.ctx, seq)
|
||||
if owner, _ := views.OwnerOfEvent(m.Subject); err != nil || owner != assignment.Module || views.StateOfEvent(m.Subject) != state {
|
||||
if owner, _ := views.OwnerOfEvent(m.Subject); err != nil || owner != assignment.Module() || views.StateOfEvent(m.Subject) != state {
|
||||
continue
|
||||
}
|
||||
e, err := views.DecodeEvent(m.Data)
|
||||
@@ -1715,7 +1715,7 @@ func (s bedSource) Get(key string) (*views.Entry, bool, error) {
|
||||
func assignmentsSubjects() []string {
|
||||
var out []string
|
||||
for _, e := range mma.Events() {
|
||||
out = append(out, process.ModuleStreamSubject(assignment.Module, e))
|
||||
out = append(out, process.ModuleStreamSubject(assignment.Module(), e))
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -1827,7 +1827,7 @@ func firstViewsBeforeAnyNewFormState(t *testing.T, b *bed, granted map[string][]
|
||||
}
|
||||
state := func(name string) views.Source {
|
||||
if name == views.MarksState {
|
||||
return bedSource{b.ctx, b.buckets[marksBucket]}
|
||||
return bedSource{b.ctx, b.buckets[marksBucket()]}
|
||||
}
|
||||
return bedSource{b.ctx, b.buckets[viewedStates[name]]}
|
||||
}
|
||||
@@ -1837,7 +1837,7 @@ func firstViewsBeforeAnyNewFormState(t *testing.T, b *bed, granted map[string][]
|
||||
outR, outW := io.Pipe()
|
||||
t.Cleanup(func() { inW.Close(); outW.Close() })
|
||||
go func() {
|
||||
_ = stdio.ServeOn(assignment.Module, viewTools(func(k string) string { return env[k] }, state, time.Now), inR, outW)
|
||||
_ = stdio.ServeOn(assignment.Module(), viewTools(func(k string) string { return env[k] }, state, time.Now), inR, outW)
|
||||
}()
|
||||
answers := make(chan map[string]any, 16)
|
||||
go func() {
|
||||
@@ -1858,7 +1858,7 @@ func firstViewsBeforeAnyNewFormState(t *testing.T, b *bed, granted map[string][]
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(nc.Close)
|
||||
sub, err := nc.Subscribe("mesh.mod."+assignment.Module+".call.*.*", func(msg *nats.Msg) {
|
||||
sub, err := nc.Subscribe("mesh.mod."+assignment.Module()+".call.*.*", func(msg *nats.Msg) {
|
||||
one <- struct{}{}
|
||||
defer func() { <-one }()
|
||||
tokens := strings.Split(msg.Subject, ".")
|
||||
@@ -1891,7 +1891,7 @@ func firstViewsBeforeAnyNewFormState(t *testing.T, b *bed, granted map[string][]
|
||||
ask := func(user, tool string) (views.Answer, error) {
|
||||
return views.AskVia(func(key string, body any) (json.RawMessage, error) {
|
||||
data, _ := json.Marshal(body)
|
||||
msg, err := nc.Request("mesh.mod."+assignment.Module+".call."+tool+"."+strings.ReplaceAll(user, ".", "~"), data, 5*time.Second)
|
||||
msg, err := nc.Request("mesh.mod."+assignment.Module()+".call."+tool+"."+strings.ReplaceAll(user, ".", "~"), data, 5*time.Second)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1907,7 +1907,7 @@ func firstViewsBeforeAnyNewFormState(t *testing.T, b *bed, granted map[string][]
|
||||
t.Errorf("%s's first view of %s before any new-form state: %v", user, tool, err)
|
||||
case slices.Contains(allowed, user):
|
||||
keys, _ := b.buckets[viewedStates[tool]].Keys(b.ctx)
|
||||
if a.Owner != assignment.Module || a.State != tool || len(a.Values) != len(keys) {
|
||||
if a.Owner != assignment.Module() || a.State != tool || len(a.Values) != len(keys) {
|
||||
t.Errorf("%s's first view of %s = %+v, the state holds %v", user, tool, a, keys)
|
||||
}
|
||||
case err == nil || !strings.Contains(err.Error(), "refused"):
|
||||
|
||||
@@ -78,25 +78,29 @@ var readFamilies = []string{buckets.ComposedRead}
|
||||
|
||||
// ownBuckets are the assigner's own states, as its manifest declares them: the seat mesh-module-assigner's three among them
|
||||
// (ADR 0567 §1), named after the assigner as every module state is.
|
||||
var ownBuckets = []string{buckets.Asks, buckets.Differences,
|
||||
buckets.Puts, buckets.Wants, plc.AssignmentsBucket, plc.ProposalsBucket,
|
||||
plc.CandidatesBucket, plc.PulsesBucket, assign.FindingsBucket, assign.PoliciesBucket, plc.SeatHoldersBucket,
|
||||
engineReadsBucket, retiredBucket, copiesBucket, writtenBucket, buckets.Waits, buckets.PutWaits, buckets.DeliveryWaits, buckets.DeliveredAgain, buckets.Inputs, pinsBucket}
|
||||
func ownBuckets() []string {
|
||||
return []string{buckets.Asks(), buckets.Differences(),
|
||||
buckets.Puts(), buckets.Wants(), plc.AssignmentsBucket(), plc.ProposalsBucket(),
|
||||
plc.CandidatesBucket(), plc.PulsesBucket(), assign.FindingsBucket(), assign.PoliciesBucket(), plc.SeatHoldersBucket(),
|
||||
engineReadsBucket(), retiredBucket(), copiesBucket(), writtenBucket(), buckets.Waits(), buckets.PutWaits(), buckets.DeliveryWaits(), buckets.DeliveredAgain(), buckets.Inputs(), pinsBucket()}
|
||||
}
|
||||
|
||||
// engineReadsState is the assigner's state of what each node's engine states it reads (ADR 0618 §3), one key `<node>`, as
|
||||
// the verb manifest-reads last answered it or the fallback read it: what assigner_engine_reads lists and asks again.
|
||||
const engineReadsState = "engine-reads"
|
||||
|
||||
// engineReadsBucket is that state's bucket.
|
||||
const engineReadsBucket = assignment.Module + "_" + engineReadsState
|
||||
func engineReadsBucket() string { return assignment.Module() + "_" + engineReadsState }
|
||||
|
||||
// writtenBucket is the bucket of the state written (pipelines.go): the pipeline ends the wants follow.
|
||||
const writtenBucket = assignment.Module + "_" + writtenState
|
||||
func writtenBucket() string { return assignment.Module() + "_" + writtenState }
|
||||
|
||||
// seatBuckets are the seat's states, new with it: until the controller has made them from the manifest and the bus grants
|
||||
// them, the assigner starts without them and binds each once the bus says it was created (issue 1790's kind).
|
||||
var seatBuckets = []string{plc.AssignmentsBucket, plc.ProposalsBucket, plc.CandidatesBucket,
|
||||
plc.PulsesBucket, copiesBucket}
|
||||
func seatBuckets() []string {
|
||||
return []string{plc.AssignmentsBucket(), plc.ProposalsBucket(), plc.CandidatesBucket(),
|
||||
plc.PulsesBucket(), copiesBucket()}
|
||||
}
|
||||
|
||||
// keptStates is what the assigner keeps and who may read it (ADR 0495 §2-§3): its wants by the watchman and the view
|
||||
// (and the node agent from release 6), its differences by the view, its asks by the view
|
||||
@@ -138,18 +142,18 @@ type kvSet map[string]jetstream.KeyValue
|
||||
// without it and takes no put further until it starts again (ADR 0546).
|
||||
func open(ctx context.Context, js process.BucketOpener, logf func(string, ...any)) (kvSet, error) {
|
||||
out := kvSet{}
|
||||
for _, name := range append(append([]string{}, readBuckets...), ownBuckets...) {
|
||||
for _, name := range append(append([]string{}, readBuckets...), ownBuckets()...) {
|
||||
kv, err := js.KeyValue(ctx, name)
|
||||
if name == buckets.Puts && err != nil {
|
||||
if name == buckets.Puts() && err != nil {
|
||||
logf("the bucket %s cannot be opened (%v): no put is taken further until the assigner starts again", name, err)
|
||||
continue
|
||||
}
|
||||
if slices.Contains(seatBuckets, name) && err != nil {
|
||||
if slices.Contains(seatBuckets(), name) && err != nil {
|
||||
// The seat's states come in the same delivery as this assigner (ADR 0567 §1): without assignments no module is placed.
|
||||
logf("the bucket %s cannot be opened (%v): it is bound once the bus says it was made", name, err)
|
||||
continue
|
||||
}
|
||||
if name == plc.SeatHoldersBucket && err != nil {
|
||||
if name == plc.SeatHoldersBucket() && err != nil {
|
||||
// The seat's state, new with ADR 0571 §4: bound once the bus says it was made; until then nobody is told who
|
||||
// holds which seat here, and the controller's seats still answer.
|
||||
logf("the bucket %s cannot be opened (%v): it is bound once the bus says it was made", name, err)
|
||||
@@ -161,56 +165,56 @@ func open(ctx context.Context, js process.BucketOpener, logf func(string, ...any
|
||||
logf("the bucket %s cannot be opened (%v): it is bound once the bus says it was made", name, err)
|
||||
continue
|
||||
}
|
||||
if name == assign.PoliciesBucket && err != nil {
|
||||
if name == assign.PoliciesBucket() && err != nil {
|
||||
// The seat mesh-wants' state, new with it (ADR 0565 §7): bound once the bus says it was made; until then no
|
||||
// person's policy is read, so the controller's beside each composition holds, as before.
|
||||
logf("the bucket %s cannot be opened (%v): it is bound once the bus says it was made", name, err)
|
||||
continue
|
||||
}
|
||||
if name == assign.FindingsBucket && err != nil {
|
||||
if name == assign.FindingsBucket() && err != nil {
|
||||
// New with health leaving the controller (issue 1952 WM6): without it the conditions are said straight to the
|
||||
// router and kept as no finding.
|
||||
logf("the bucket %s cannot be opened (%v): the assigner's conditions are said straight to the router until it starts again", name, err)
|
||||
continue
|
||||
}
|
||||
if name == engineReadsBucket && err != nil {
|
||||
if name == engineReadsBucket() && err != nil {
|
||||
// New with ADR 0618 §3: without it the statements the verb answers are kept in memory only, and
|
||||
// assigner_engine_reads lists nothing.
|
||||
logf("the bucket %s cannot be opened (%v): what each node's engine reads is kept in memory only until it starts again", name, err)
|
||||
continue
|
||||
}
|
||||
if name == buckets.Inputs && err != nil {
|
||||
if name == buckets.Inputs() && err != nil {
|
||||
// New with the verb input (design 47 G §354): only the tool process reads it, so the program starts without it.
|
||||
logf("the bucket %s cannot be opened (%v): it is read by the tool process alone", name, err)
|
||||
continue
|
||||
}
|
||||
if name == pinsBucket && err != nil {
|
||||
if name == pinsBucket() && err != nil {
|
||||
// New with the pins (design 47 G §579): without it no pin is kept and no revision is removed.
|
||||
logf("the bucket %s cannot be opened (%v): no pin is kept and no revision removed until it starts again", name, err)
|
||||
continue
|
||||
}
|
||||
if name == buckets.Waits && err != nil {
|
||||
if name == buckets.Waits() && err != nil {
|
||||
// New with the wait on an outside cause (design 47 G §706): without it no wait is made, and a failing check is judged
|
||||
// as before.
|
||||
logf("the bucket %s cannot be opened (%v): no outside wait is kept until the assigner starts again", name, err)
|
||||
continue
|
||||
}
|
||||
if name == buckets.DeliveredAgain && err != nil {
|
||||
if name == buckets.DeliveredAgain() && err != nil {
|
||||
// New with A-250: without it assigner_delivered_again is not taken until the assigner starts again.
|
||||
logf("the bucket %s cannot be opened (%v): no version is said delivered again until the assigner starts again", name, err)
|
||||
continue
|
||||
}
|
||||
if name == buckets.DeliveryWaits && err != nil {
|
||||
if name == buckets.DeliveryWaits() && err != nil {
|
||||
// New with A-246: without it no dependent waits for its dependency's delivery until the assigner starts again.
|
||||
logf("the bucket %s cannot be opened (%v): no delivery wait is kept until the assigner starts again", name, err)
|
||||
continue
|
||||
}
|
||||
if name == buckets.PutWaits && err != nil {
|
||||
if name == buckets.PutWaits() && err != nil {
|
||||
// New with A-211: without it no put's wait is said until the assigner starts again.
|
||||
logf("the bucket %s cannot be opened (%v): no put's wait is said until the assigner starts again", name, err)
|
||||
continue
|
||||
}
|
||||
if name == retiredBucket && err != nil {
|
||||
if name == retiredBucket() && err != nil {
|
||||
// The retired state is read only for the copy into `assignments` (migrate.go): a mesh made after the switch has
|
||||
// none, and nothing is copied.
|
||||
logf("the bucket %s cannot be opened (%v): nothing is copied from it", name, err)
|
||||
@@ -233,11 +237,11 @@ func open(ctx context.Context, js process.BucketOpener, logf func(string, ...any
|
||||
// states and those new with a delivery of their own, bound once made, and the two that choose the grants' source.
|
||||
func startsWithout(name string) bool {
|
||||
switch name {
|
||||
case plc.SeatHoldersBucket, buckets.Provisions, assign.PoliciesBucket, assign.FindingsBucket,
|
||||
buckets.Puts, engineReadsBucket, retiredBucket, writtenBucket, buckets.Waits, buckets.PutWaits, buckets.DeliveryWaits, buckets.DeliveredAgain, buckets.Inputs, pinsBucket:
|
||||
case plc.SeatHoldersBucket(), buckets.Provisions, assign.PoliciesBucket(), assign.FindingsBucket(),
|
||||
buckets.Puts(), engineReadsBucket(), retiredBucket(), writtenBucket(), buckets.Waits(), buckets.PutWaits(), buckets.DeliveryWaits(), buckets.DeliveredAgain(), buckets.Inputs(), pinsBucket():
|
||||
return true
|
||||
}
|
||||
return slices.Contains(seatBuckets, name) || name == buckets.Grants
|
||||
return slices.Contains(seatBuckets(), name) || name == buckets.Grants
|
||||
}
|
||||
|
||||
// run is the program: it returns when ctx ends or the bus cannot be used.
|
||||
@@ -247,6 +251,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
plc.SetModule(account.Module) // the assigner names its own states from its credential's module (A-296, §428)
|
||||
conn, js, err := account.Dial()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -285,10 +290,10 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
||||
}
|
||||
}()
|
||||
iv := &catalogViews{js: js, account: account, call: said.Call, writer: wireOf(writer), logf: logger.Printf,
|
||||
status: process.AwaitBucket(ctx, js, conn, firstViewsBucket, "assigner_first_views reads nothing and assigner_first_view_again is not heard", logger.Printf)}
|
||||
status: process.AwaitBucket(ctx, js, conn, firstViewsBucket(), "assigner_first_views reads nothing and assigner_first_view_again is not heard", logger.Printf)}
|
||||
// The inputs of the composition steps it asks a build seat to run: the seat mesh-wants' verb input is answered here in chunks
|
||||
// of the core connection, of any size (design 47 G §354, §712, A-237).
|
||||
inputs := inputsOver(kvs[buckets.Inputs], js, account.Module, logger.Printf)
|
||||
inputs := inputsOver(kvs[buckets.Inputs()], js, account.Module, logger.Printf)
|
||||
// The seat list (seatlist.go), followed in follow over the connection and the hub: the verb input's callers are checked
|
||||
// against the seat's definition it holds (A-186).
|
||||
gate := seatlist.NewPart("assigner", seatListAgainCallers)
|
||||
@@ -306,7 +311,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
||||
// condition keeper carries (novox/hq ADR 0590 §3). Without it the conditions are said straight to the router.
|
||||
func findingsOf(ctx context.Context, account process.Account, conn *nats.Conn, js jetstream.JetStream, said *process.Conn, kvs kvSet, logf func(string, ...any)) (assign.Findings, *heldWriter) {
|
||||
var f assign.Findings
|
||||
if kvs[assign.FindingsBucket] == nil {
|
||||
if kvs[assign.FindingsBucket()] == nil {
|
||||
return f, nil
|
||||
}
|
||||
state := said.State(assign.FindingsState)
|
||||
@@ -354,30 +359,34 @@ type emitting struct{ *process.Conn }
|
||||
func (e emitting) Emit(eventType string, body any) error { return e.Conn.Emit(eventType, body) }
|
||||
|
||||
// optionalEnds is what stops when the watch of a bucket that never gates the first pass ends.
|
||||
var optionalEnds = map[string]string{buckets.Puts: "no put is taken further", buckets.Answers: "no ask is answered"}
|
||||
func optionalEnds() map[string]string {
|
||||
return map[string]string{buckets.Puts(): "no put is taken further", buckets.Answers: "no ask is answered"}
|
||||
}
|
||||
|
||||
// watched are the buckets whose every value a pass reads; the manifests are read by digest when a pass needs one, and
|
||||
// watched only for keys, so a manifest that arrives wakes a composition that waits for it.
|
||||
var watched = []string{
|
||||
buckets.Grants,
|
||||
plc.AssignmentsBucket,
|
||||
plc.ProposalsBucket,
|
||||
plc.CandidatesBucket,
|
||||
plc.PulsesBucket,
|
||||
copiesBucket,
|
||||
buckets.Have,
|
||||
buckets.Asks,
|
||||
buckets.Differences,
|
||||
buckets.Wants,
|
||||
assign.PoliciesBucket,
|
||||
buckets.Provisions,
|
||||
plc.SeatHoldersBucket,
|
||||
engineReadsBucket,
|
||||
writtenBucket,
|
||||
buckets.Waits,
|
||||
buckets.PutWaits,
|
||||
buckets.DeliveryWaits,
|
||||
buckets.DeliveredAgain, // its watch wakes a pass at once on an ask of assigner_delivered_again (A-250)
|
||||
func watched() []string {
|
||||
return []string{
|
||||
buckets.Grants,
|
||||
plc.AssignmentsBucket(),
|
||||
plc.ProposalsBucket(),
|
||||
plc.CandidatesBucket(),
|
||||
plc.PulsesBucket(),
|
||||
copiesBucket(),
|
||||
buckets.Have,
|
||||
buckets.Asks(),
|
||||
buckets.Differences(),
|
||||
buckets.Wants(),
|
||||
assign.PoliciesBucket(),
|
||||
buckets.Provisions,
|
||||
plc.SeatHoldersBucket(),
|
||||
engineReadsBucket(),
|
||||
writtenBucket(),
|
||||
buckets.Waits(),
|
||||
buckets.PutWaits(),
|
||||
buckets.DeliveryWaits(),
|
||||
buckets.DeliveredAgain(), // its watch wakes a pass at once on an ask of assigner_delivered_again (A-250)
|
||||
}
|
||||
}
|
||||
|
||||
// follow watches every bucket a pass reads and makes a pass at start, once every watch handed what stands, and after
|
||||
@@ -385,7 +394,7 @@ var watched = []string{
|
||||
func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber, kvs kvSet, say func(oc.Condition) error, findings assign.Findings, bus caller, iv *catalogViews, inputs *inputKeeper, gate *seatlist.Part, logger *log.Logger, getenv func(string) string) error {
|
||||
w := newWorld()
|
||||
// The callers' seat entries are resolved from the assigner's own seat-holders as it last read them (A-298).
|
||||
gate.Holders(seatlist.HoldersIn(func() (map[string][]byte, error) { return w.read(plc.SeatHoldersBucket), nil }))
|
||||
gate.Holders(seatlist.HoldersIn(func() (map[string][]byte, error) { return w.read(plc.SeatHoldersBucket()), nil }))
|
||||
dirty := make(chan struct{}, 1)
|
||||
wake := func() {
|
||||
select {
|
||||
@@ -393,8 +402,8 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber,
|
||||
default:
|
||||
}
|
||||
}
|
||||
errs := make(chan error, len(watched)+2)
|
||||
settled := make(chan struct{}, len(watched)+2)
|
||||
errs := make(chan error, len(watched())+2)
|
||||
settled := make(chan struct{}, len(watched())+2)
|
||||
// What each node's engine reads (ADR 0618 §3, §4): asked through the seat's verb, kept in the state engine-reads, read
|
||||
// from the node's record where the engine does not answer (the fallback, said in the journal).
|
||||
reads := &enginereads.Keeper{Fallback: w.recordReads, Changed: wake, Logf: logger.Printf}
|
||||
@@ -402,7 +411,7 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber,
|
||||
reads.Call = bus.CallKeptOn // a kept call, called again through a transport fault (ADR 0668 §1)
|
||||
reads.Once = bus.CallOn // its fault keeps the fallback at once while the kept call asks on (ADR 0668 §3)
|
||||
}
|
||||
if kv := kvs[engineReadsBucket]; kv != nil {
|
||||
if kv := kvs[engineReadsBucket()]; kv != nil {
|
||||
reads.Put = func(node string, v []byte) error { _, err := kv.Put(ctx, node, v); return err }
|
||||
reads.Delete = func(node string) error { return kv.Delete(ctx, node) }
|
||||
}
|
||||
@@ -412,7 +421,7 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber,
|
||||
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 == buckets.Puts || name == buckets.Answers
|
||||
optional := name == buckets.Puts() || name == buckets.Answers
|
||||
go func() {
|
||||
defer kw.Stop()
|
||||
for {
|
||||
@@ -423,7 +432,7 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber,
|
||||
case e, ok := <-kw.Updates():
|
||||
if !ok {
|
||||
if optional {
|
||||
logger.Printf("the watch of %s ended: %s until the assigner starts again", name, optionalEnds[name])
|
||||
logger.Printf("the watch of %s ended: %s until the assigner starts again", name, optionalEnds()[name])
|
||||
return
|
||||
}
|
||||
errs <- fmt.Errorf("the watch of %s ended", name)
|
||||
@@ -439,7 +448,7 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber,
|
||||
if e.Operation() == jetstream.KeyValuePut {
|
||||
value = e.Value()
|
||||
}
|
||||
if name == engineReadsBucket {
|
||||
if name == engineReadsBucket() {
|
||||
reads.Heard(e.Key(), value, value == nil) // it wakes a pass when a statement changed
|
||||
continue
|
||||
}
|
||||
@@ -452,10 +461,10 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber,
|
||||
}
|
||||
// The puts' watch is not waited for before the first pass: a watch of them that ends before it handed what stands only
|
||||
// stops the puts, never the wants.
|
||||
if kvs[buckets.Puts] != nil {
|
||||
if err := watch(buckets.Puts); err != nil {
|
||||
if kvs[buckets.Puts()] != nil {
|
||||
if err := watch(buckets.Puts()); err != nil {
|
||||
logger.Printf("%v: no put is taken further until it is opened again", err)
|
||||
delete(kvs, buckets.Puts)
|
||||
delete(kvs, buckets.Puts())
|
||||
}
|
||||
}
|
||||
// The puts' bucket is made by the controller from the manifest's states at its start (ADR 0201), and the assigner's
|
||||
@@ -464,27 +473,27 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber,
|
||||
// further, without a restart.
|
||||
var triedPuts time.Time
|
||||
reopenPuts := func(p *passer) {
|
||||
if kvs[buckets.Puts] != nil || time.Since(triedPuts) < PutsReopenEvery {
|
||||
if kvs[buckets.Puts()] != nil || time.Since(triedPuts) < PutsReopenEvery {
|
||||
return
|
||||
}
|
||||
triedPuts = time.Now()
|
||||
kv, err := js.KeyValue(ctx, buckets.Puts)
|
||||
kv, err := js.KeyValue(ctx, buckets.Puts())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
kvs[buckets.Puts] = kv
|
||||
if err := watch(buckets.Puts); err != nil {
|
||||
kvs[buckets.Puts()] = kv
|
||||
if err := watch(buckets.Puts()); err != nil {
|
||||
logger.Printf("%v: no put is taken further until it is opened again", err)
|
||||
delete(kvs, buckets.Puts)
|
||||
delete(kvs, buckets.Puts())
|
||||
return
|
||||
}
|
||||
if bus != nil {
|
||||
p.acts = newPutActs(bus, wake, logger.Printf)
|
||||
}
|
||||
logger.Printf("the bucket %s is open: puts are taken further", buckets.Puts)
|
||||
logger.Printf("the bucket %s is open: puts are taken further", buckets.Puts())
|
||||
}
|
||||
started := 0
|
||||
for _, name := range watched {
|
||||
for _, name := range watched() {
|
||||
if kvs[name] == nil && startsWithout(name) {
|
||||
continue // not on the bus at this start (open said so)
|
||||
}
|
||||
@@ -541,15 +550,15 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber,
|
||||
name string
|
||||
kv jetstream.KeyValue
|
||||
}
|
||||
assignmentsAppeared := make(chan appeared, len(seatBuckets)+2)
|
||||
for _, name := range seatBuckets {
|
||||
assignmentsAppeared := make(chan appeared, len(seatBuckets())+2)
|
||||
for _, name := range seatBuckets() {
|
||||
if kvs[name] == nil && js != nil {
|
||||
process.AwaitBucket(ctx, js, nc, name, "the seat mesh-module-assigner waits for it",
|
||||
logger.Printf).Ready(func(kv jetstream.KeyValue) { assignmentsAppeared <- appeared{name, kv} })
|
||||
}
|
||||
}
|
||||
// And the two that choose where grants are read from, with the same delivery as the grants part that writes one.
|
||||
for _, name := range []string{buckets.Grants, assign.PoliciesBucket, buckets.Provisions, plc.SeatHoldersBucket} {
|
||||
for _, name := range []string{buckets.Grants, assign.PoliciesBucket(), buckets.Provisions, plc.SeatHoldersBucket()} {
|
||||
if kvs[name] == nil && js != nil {
|
||||
name := name
|
||||
process.AwaitBucket(ctx, js, nc, name, "until then the bucket is not read",
|
||||
@@ -581,7 +590,7 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber,
|
||||
reads: reads, seats: gate}
|
||||
// The wait on an outside cause (outside.go, design 47 G §706): the keeper's three events say when to judge again, and the
|
||||
// seat mesh-wants' wait-held and wait-ended are said; registered before the hub starts.
|
||||
if bus != nil && kvs[buckets.Waits] != nil {
|
||||
if bus != nil && kvs[buckets.Waits()] != nil {
|
||||
bound, settle, err := outsideSettings(getenv)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -603,7 +612,7 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber,
|
||||
if js != nil {
|
||||
p.publishWants = wantsPublisher(ctx, js, thisNode)
|
||||
if iv != nil && iv.h != nil {
|
||||
objects := newBusObjects(js, process.Account{Node: thisNode, Module: assignment.Module}, assigngraph.RevisionsObjects)
|
||||
objects := newBusObjects(js, process.Account{Node: thisNode, Module: assignment.Module()}, assigngraph.RevisionsObjects)
|
||||
p.graph = &graphKeeper{objects: objects, halves: iv.h.views}
|
||||
keep := func(ctx context.Context, ev meshcatalog.CatalogRevisionEvent) error {
|
||||
return p.keepCandidate(ctx, iv.call, ev)
|
||||
@@ -614,7 +623,7 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber,
|
||||
p.entries.Changed = func(meshcatalogclient.EntryRow) { wake() }
|
||||
// The pins of the revisions (pins.go, design 47 G §579): the gate's pinned, freed, heads-replaced and pins-stated
|
||||
// on the hub, registered before it starts.
|
||||
if kv := kvs[pinsBucket]; kv != nil {
|
||||
if kv := kvs[pinsBucket()]; kv != nil {
|
||||
k := &pinKeeper{ctx: ctx, st: kvEnds{ctx, kv}, objects: objects, inUse: inUseOf(w), findings: findings, now: time.Now, logf: logger.Printf}
|
||||
if err := k.made(); err != nil {
|
||||
logger.Printf("the state %s is not marked made, so no revision is removed until it is: %v", pinsState, err)
|
||||
@@ -634,11 +643,11 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber,
|
||||
if js != nil {
|
||||
// The teller's ask-ended (issue 1966): an ask ended at the channel without an answer is written again; registered
|
||||
// before hearReadsChanged starts the hub.
|
||||
if err := hearAskEnded(ctx, js, hub, kvs[buckets.Asks], staleOfWorld(w), logger.Printf); err != nil {
|
||||
if err := hearAskEnded(ctx, js, hub, kvs[buckets.Asks()], staleOfWorld(w), logger.Printf); err != nil {
|
||||
logger.Printf("%s is not heard, so an ask that ends at the operator channel without an answer is not asked again until the assigner starts again: %v", askEndedSubject, err)
|
||||
}
|
||||
readEndedAtBind(ctx, js, hub, kvs[buckets.Asks], staleOfWorld(w), bus, callWait{heard: func() bool { return w.seatHeard(oc.Seat) }, findings: findings}, logger.Printf)
|
||||
if kv := kvs[writtenBucket]; kv != nil {
|
||||
readEndedAtBind(ctx, js, hub, kvs[buckets.Asks()], staleOfWorld(w), bus, callWait{heard: func() bool { return w.seatHeard(oc.Seat) }, findings: findings}, logger.Printf)
|
||||
if kv := kvs[writtenBucket()]; kv != nil {
|
||||
// The runner's pipeline-ended and group-ended (design 47 G §29): kept in the state written, which the wants follow
|
||||
// once the first registrations are written (§95, §353).
|
||||
p.ends = newPipelineEnds(kvEnds{ctx, kv}, wake, logger.Printf)
|
||||
@@ -660,7 +669,7 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber,
|
||||
hearReadsChanged(ctx, js, hub, reads, findings, gate.EventsLive, logger.Printf)
|
||||
hearRunning(ctx, js, w, wake, findings, logger.Printf)
|
||||
}
|
||||
if bus != nil && kvs[buckets.Puts] != nil {
|
||||
if bus != nil && kvs[buckets.Puts()] != nil {
|
||||
p.acts = newPutActs(bus, wake, logger.Printf)
|
||||
}
|
||||
// The composition step (design 47 G §22, §354): the seat mesh-wants' accepted kind step, run by one run ask per machine.
|
||||
@@ -699,7 +708,7 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber,
|
||||
// be said before this credential may hear it: every pass, itself started by a watched change, binds each one the
|
||||
// bus now holds, and says the seat waits while one is missing.
|
||||
var unbound []string
|
||||
for _, name := range seatBuckets {
|
||||
for _, name := range seatBuckets() {
|
||||
if kvs[name] != nil || js == nil {
|
||||
continue
|
||||
}
|
||||
@@ -724,7 +733,7 @@ var PutsReopenEvery = time.Minute
|
||||
// (ADR 0437 clause 9).
|
||||
func sayVia(ctx context.Context, js jetstream.JetStream) func(oc.Condition) error {
|
||||
return func(c oc.Condition) error {
|
||||
subject, err := occ.ConditionSubject(assignment.Module)
|
||||
subject, err := occ.ConditionSubject(assignment.Module())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -764,7 +773,7 @@ func readsChangedHandler(reads *enginereads.Keeper, logf func(string, ...any)) f
|
||||
// consumer on EVENTS, and asks the node it names again; the hub also hands the indexer's views to the followers of
|
||||
// indexerViews, and starts once both are registered.
|
||||
func hearReadsChanged(ctx context.Context, js jetstream.JetStream, hub *process.EventHub, reads *enginereads.Keeper, findings assign.Findings, live func(), logf func(string, ...any)) {
|
||||
account := process.Account{Node: thisNode, Module: assignment.Module}
|
||||
account := process.Account{Node: thisNode, Module: assignment.Module()}
|
||||
w := notHeardWriter(findings, logf)
|
||||
handle := readsChangedHandler(reads, logf)
|
||||
err := hub.Handle(enginereads.SubjectPrefix+"*", handle)
|
||||
|
||||
@@ -69,7 +69,7 @@ func TestOpensWhatItsManifestNames(t *testing.T) {
|
||||
for _, pair := range []struct {
|
||||
what string
|
||||
code, want []string
|
||||
}{{"reads", readBuckets, reads}, {"states", append(slices.Clone(ownBuckets), marksBucket, firstViewsBucket), states}} { // the marks are bound by the views family (views.go)
|
||||
}{{"reads", readBuckets, reads}, {"states", append(slices.Clone(ownBuckets()), marksBucket(), firstViewsBucket()), states}} { // the marks are bound by the views family (views.go)
|
||||
got, want := slices.Sorted(slices.Values(pair.code)), slices.Sorted(slices.Values(pair.want))
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("the program's %s are %v, the manifest's %v", pair.what, got, want)
|
||||
@@ -79,7 +79,7 @@ func TestOpensWhatItsManifestNames(t *testing.T) {
|
||||
for _, s := range keptStates {
|
||||
kept = append(kept, m.Module+"_"+s.Name)
|
||||
}
|
||||
if got, want := slices.Sorted(slices.Values(kept)), slices.Sorted(slices.Values(append(slices.Clone(ownBuckets), marksBucket, firstViewsBucket))); !reflect.DeepEqual(got, want) {
|
||||
if got, want := slices.Sorted(slices.Values(kept)), slices.Sorted(slices.Values(append(slices.Clone(ownBuckets()), marksBucket(), firstViewsBucket()))); !reflect.DeepEqual(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
|
||||
@@ -90,17 +90,17 @@ func TestOpensWhatItsManifestNames(t *testing.T) {
|
||||
starts bool
|
||||
said string
|
||||
}{
|
||||
{buckets.Puts, jetstream.ErrBucketNotFound, true, "no put is taken further"},
|
||||
{buckets.Puts, errors.New("nats: permissions violation for subscription to \"$JS.API.STREAM.INFO.KV_mesh-delivery-assigner_puts\""), true, "no put is taken further"},
|
||||
{buckets.Puts, context.DeadlineExceeded, true, "no put is taken further"},
|
||||
{buckets.Wants, errors.New("nats: permissions violation"), false, ""},
|
||||
{buckets.Puts(), jetstream.ErrBucketNotFound, true, "no put is taken further"},
|
||||
{buckets.Puts(), errors.New("nats: permissions violation for subscription to \"$JS.API.STREAM.INFO.KV_mesh-delivery-assigner_puts\""), true, "no put is taken further"},
|
||||
{buckets.Puts(), context.DeadlineExceeded, true, "no put is taken further"},
|
||||
{buckets.Wants(), errors.New("nats: permissions violation"), false, ""},
|
||||
// Issue 774: the grants part's grants not there do not stop the start.
|
||||
{buckets.Grants, jetstream.ErrBucketNotFound, true, "no grant is read"},
|
||||
} {
|
||||
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, c.said)):
|
||||
case c.starts && (err != nil || kvs[c.missing] != nil || len(kvs) != len(readBuckets)+len(ownBuckets())-1 || !strings.Contains(logged, c.said)):
|
||||
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(), "grant of "+c.missing)): // 1945: named with its grant
|
||||
t.Errorf("%s refused with %v: the start answered %v", c.missing, c.err, err)
|
||||
@@ -112,7 +112,7 @@ func TestOpensWhatItsManifestNames(t *testing.T) {
|
||||
defer cancel()
|
||||
wrote := make(chan string, 8)
|
||||
kvs := kvSet{}
|
||||
for _, name := range append(append([]string{}, readBuckets...), ownBuckets...) {
|
||||
for _, name := range append(append([]string{}, readBuckets...), ownBuckets()...) {
|
||||
kvs[name] = &watchKV{memKV: &memKV{values: map[string][]byte{}}}
|
||||
}
|
||||
placed, err := mmac.AssignmentsValues(mma.Assignments{Node: "ace"})
|
||||
@@ -120,11 +120,11 @@ func TestOpensWhatItsManifestNames(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for key, raw := range placed {
|
||||
kv := kvs[plc.AssignmentsBucket].(*watchKV)
|
||||
kv := kvs[plc.AssignmentsBucket()].(*watchKV)
|
||||
kv.entries = append(kv.entries, kvEntry{key: key, value: raw})
|
||||
}
|
||||
kvs[buckets.Puts].(*watchKV).closed = true
|
||||
kvs[buckets.Wants].(*watchKV).wrote = wrote
|
||||
kvs[buckets.Puts()].(*watchKV).closed = true
|
||||
kvs[buckets.Wants()].(*watchKV).wrote = wrote
|
||||
go follow(ctx, nil, nil, kvs, func(oc.Condition) error { return nil }, assign.Findings{}, nil, nil, nil, seatlist.NewPart("assigner", seatListAgainCallers), log.New(io.Discard, "", 0), func(string) string { return "" })
|
||||
select {
|
||||
case key := <-wrote:
|
||||
|
||||
@@ -18,10 +18,9 @@ import (
|
||||
// The state this seat's assignments were kept in before ADR 0641 gave it its name. The assigner reads it here and nowhere
|
||||
// else: at its start it copies each key into `assignments` (copyRetired) and keeps no reader of it afterwards. The
|
||||
// manifest still declares the state, so the assigner's account may read it for the copy; a later release drops it.
|
||||
const (
|
||||
retiredState = "placement"
|
||||
retiredBucket = plc.Module + "_" + retiredState
|
||||
)
|
||||
const retiredState = "placement"
|
||||
|
||||
func retiredBucket() string { return plc.Module() + "_" + retiredState }
|
||||
|
||||
// CopiedEvent is the module's event saying the copy was made, once: mesh.mod.mesh-delivery-assigner.event.assignments-copied.
|
||||
const CopiedEvent = "assignments-copied"
|
||||
@@ -37,10 +36,9 @@ type copiedSaid struct {
|
||||
|
||||
// copiesState is the assigner's own state of its copies, read by its tool assigner_read_copies, and copiesBucket its bucket.
|
||||
// The retired state gets nothing written: readers of other modules still parse every key in it.
|
||||
const (
|
||||
copiesState = "copies"
|
||||
copiesBucket = plc.Module + "_" + copiesState
|
||||
)
|
||||
const copiesState = "copies"
|
||||
|
||||
func copiesBucket() string { return plc.Module() + "_" + copiesState }
|
||||
|
||||
// copiedMarker is the key, in the state copies, that says the copy ended: its time and the keys it copied. It is written
|
||||
// after the last key, and the copy is made at every start until it is there and never after, so a copy cut short by a
|
||||
@@ -63,12 +61,12 @@ func (p *passer) copyRetired(ctx context.Context) bool {
|
||||
if p.copied {
|
||||
return true
|
||||
}
|
||||
old, next, copies := p.kvs[retiredBucket], p.kvs[plc.AssignmentsBucket], p.kvs[copiesBucket]
|
||||
old, next, copies := p.kvs[retiredBucket()], p.kvs[plc.AssignmentsBucket()], p.kvs[copiesBucket()]
|
||||
if next == nil || copies == nil {
|
||||
return false
|
||||
}
|
||||
if old == nil {
|
||||
p.logf("assignments: the retired state %s cannot be read, so nothing is copied from it", retiredBucket)
|
||||
p.logf("assignments: the retired state %s cannot be read, so nothing is copied from it", retiredBucket())
|
||||
p.copied = true
|
||||
return true
|
||||
}
|
||||
@@ -77,12 +75,12 @@ func (p *passer) copyRetired(ctx context.Context) bool {
|
||||
p.copied = true // the copy ended at an earlier start: nothing is copied again
|
||||
return true
|
||||
case !errors.Is(err, jetstream.ErrKeyNotFound):
|
||||
p.logf("assignments: the marker %s of %s cannot be read, and the copy is tried again at the next pass: %v", copiedMarker, copiesBucket, err)
|
||||
p.logf("assignments: the marker %s of %s cannot be read, and the copy is tried again at the next pass: %v", copiedMarker, copiesBucket(), err)
|
||||
return false
|
||||
}
|
||||
names, err := old.Keys(ctx)
|
||||
if err != nil && !errors.Is(err, jetstream.ErrNoKeysFound) {
|
||||
p.logf("assignments: the keys of %s cannot be listed, and are tried again at the next pass: %v", retiredBucket, err)
|
||||
p.logf("assignments: the keys of %s cannot be listed, and are tried again at the next pass: %v", retiredBucket(), err)
|
||||
return false
|
||||
}
|
||||
// A node's module keys before its node key: the node key is the commit the readers wait for.
|
||||
@@ -120,13 +118,13 @@ func (p *passer) copyRetired(ctx context.Context) bool {
|
||||
return false
|
||||
default:
|
||||
said.Copied++
|
||||
p.w.putEntry(plc.AssignmentsBucket, key, entry.Value(), rev, p.clock())
|
||||
p.w.putEntry(plc.AssignmentsBucket(), key, entry.Value(), rev, p.clock())
|
||||
}
|
||||
}
|
||||
}
|
||||
mark, _ := json.Marshal(copiedMark{At: p.clock(), Copied: said.Copied + said.Present})
|
||||
if _, err := copies.Put(ctx, copiedMarker, mark); err != nil {
|
||||
p.logf("assignments: the end of the copy is not written as %s in %s, and the copy is tried again at the next pass: %v", copiedMarker, copiesBucket, err)
|
||||
p.logf("assignments: the end of the copy is not written as %s in %s, and the copy is tried again at the next pass: %v", copiedMarker, copiesBucket(), err)
|
||||
return false
|
||||
}
|
||||
p.copied = true
|
||||
@@ -134,7 +132,7 @@ func (p *passer) copyRetired(ctx context.Context) bool {
|
||||
return true // the retired state holds nothing
|
||||
}
|
||||
p.logf("assignments: copied %d keys of %s into %s (%d already there kept as they are, %d of the form placement.<node> not copied, %d fitting no form left)",
|
||||
said.Copied, retiredBucket, plc.AssignmentsBucket, said.Present, said.Dropped, said.Skipped)
|
||||
said.Copied, retiredBucket(), plc.AssignmentsBucket(), said.Present, said.Dropped, said.Skipped)
|
||||
if p.seat != nil {
|
||||
if err := p.seat.bus.Emit(CopiedEvent, said); err != nil {
|
||||
p.logf("assignments: the copy is not said as an event: %v", err)
|
||||
|
||||
@@ -220,11 +220,11 @@ func (p *passer) orderActing(module string, released map[string]bool, unread map
|
||||
// orderWaits keeps the order waits after the pass's wants, as actingWaits keeps the acting waits: it starts the wait of each edge not met,
|
||||
// ends the wait of one that ran, was replaced or was released, and says each.
|
||||
func (p *passer) orderWaits(ctx context.Context, n assign.Nodes, wanted map[string]map[string]mwc.Want) {
|
||||
if p.kvs[buckets.Waits] == nil {
|
||||
if p.kvs[buckets.Waits()] == nil {
|
||||
return
|
||||
}
|
||||
now := p.clock()
|
||||
kept := p.w.read(buckets.Waits)
|
||||
kept := p.w.read(buckets.Waits())
|
||||
trunk := p.w.trunk()
|
||||
if p.orderSaid == nil {
|
||||
p.orderSaid = map[string]bool{}
|
||||
@@ -242,7 +242,7 @@ func (p *passer) orderWaits(ctx context.Context, n assign.Nodes, wanted map[stri
|
||||
if w, err := acting.Decode(raw); err == nil && w.Entry == e.entry && w.Fp == e.fp {
|
||||
if !w.Released && !p.orderSaid[key] {
|
||||
// A wait still held after a restart is said again, as wait-held (A-260).
|
||||
if err := p.wantsSay(seat.EventWaitHeld, acting.Body(w, assignment.Module), key+"."+e.fp+"."+e.entry+".again"); err != nil {
|
||||
if err := p.wantsSay(seat.EventWaitHeld, acting.Body(w, assignment.Module()), key+"."+e.fp+"."+e.entry+".again"); err != nil {
|
||||
p.logf("the wait of %s on %s for %s is not said again, and is said at the next pass: %v", e.module, e.node, e.part, err)
|
||||
} else {
|
||||
p.orderSaid[key] = true
|
||||
@@ -257,11 +257,11 @@ func (p *passer) orderWaits(ctx context.Context, n assign.Nodes, wanted map[stri
|
||||
}
|
||||
w := acting.Wait{Step: seatdef.StepPlace, Node: e.node, Module: e.module, Part: e.part, PartNode: e.partNode, Fp: e.fp, Entry: e.entry, Order: e.order,
|
||||
Since: now, ClockFrom: now, Bound: acting.BoundOf(seat.Edge{Step: seatdef.StepPlace}).String()}
|
||||
if err := p.wantsSay(seat.EventWaitHeld, acting.Body(w, assignment.Module), key+"."+e.fp+"."+e.entry); err != nil {
|
||||
if err := p.wantsSay(seat.EventWaitHeld, acting.Body(w, assignment.Module()), key+"."+e.fp+"."+e.entry); err != nil {
|
||||
p.logf("the wait of %s on %s for %s is not said, and is said at the next pass: %v", e.module, e.node, e.part, err)
|
||||
continue
|
||||
}
|
||||
p.put(ctx, buckets.Waits, key, func() ([]byte, error) { return acting.Encode(w) })
|
||||
p.put(ctx, buckets.Waits(), key, func() ([]byte, error) { return acting.Encode(w) })
|
||||
p.orderSaid[key] = true
|
||||
}
|
||||
for key, raw := range kept {
|
||||
@@ -283,14 +283,14 @@ func (p *passer) orderWaits(ctx context.Context, n assign.Nodes, wanted map[stri
|
||||
how = acting.HowReplaced
|
||||
}
|
||||
if !w.EndSaid {
|
||||
body := acting.Body(w, assignment.Module)
|
||||
body := acting.Body(w, assignment.Module())
|
||||
body.How = how
|
||||
if err := p.wantsSay(seat.EventWaitEnded, body, key+"."+w.Fp+"."+w.Entry+"."+how); err != nil {
|
||||
p.logf("the end of the wait of %s on %s for %s is not said, and is said at the next pass: %v", w.Module, w.Node, w.Part, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
p.remove(ctx, buckets.Waits, key)
|
||||
p.remove(ctx, buckets.Waits(), key)
|
||||
delete(p.orderSaid, key)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ func newOrderFixture(t *testing.T, mods []orderMod, history ...map[string]string
|
||||
t.Helper()
|
||||
ctx := context.Background()
|
||||
f := &orderFixture{t: t, wants: &memKV{values: map[string][]byte{}}, answers: make(chan struct{}, 64)}
|
||||
f.p = newPasser(kvSet{buckets.Wants: f.wants, buckets.Asks: &memKV{values: map[string][]byte{}}, buckets.Waits: &memKV{values: map[string][]byte{}}}, time.Now())
|
||||
f.p = newPasser(kvSet{buckets.Wants(): f.wants, buckets.Asks(): &memKV{values: map[string][]byte{}}, buckets.Waits(): &memKV{values: map[string][]byte{}}}, time.Now())
|
||||
p := f.p
|
||||
p.logf = t.Logf
|
||||
p.say = func(oc.Condition) error { return nil }
|
||||
@@ -97,7 +97,7 @@ func newOrderFixture(t *testing.T, mods []orderMod, history ...map[string]string
|
||||
f.runs(m.name, m.run)
|
||||
wkey, _ := mwc.WantKey("ace", m.name)
|
||||
old, _ := mwc.EncodeWant(mwc.Want{Fp: m.run, Self: true})
|
||||
p.w.put(buckets.Wants, wkey, old)
|
||||
p.w.put(buckets.Wants(), wkey, old)
|
||||
f.wants.values[wkey] = old
|
||||
}
|
||||
composes(t, p.w, "ace", digest)
|
||||
@@ -106,7 +106,7 @@ func newOrderFixture(t *testing.T, mods []orderMod, history ...map[string]string
|
||||
t.Fatal(err)
|
||||
}
|
||||
for key, raw := range values {
|
||||
p.w.put(plc.AssignmentsBucket, key, raw)
|
||||
p.w.put(plc.AssignmentsBucket(), key, raw)
|
||||
}
|
||||
p.w.heardPresence("ace", false, time.Now()) // ace is online: its last presence event heard is machine-back
|
||||
objects := memObjects{}
|
||||
@@ -180,7 +180,7 @@ func (f *orderFixture) wantAt(module string) string {
|
||||
|
||||
func (f *orderFixture) waits() []string {
|
||||
var out []string
|
||||
for key := range f.p.w.read(buckets.Waits) {
|
||||
for key := range f.p.w.read(buckets.Waits()) {
|
||||
if acting.IsOrder(key) {
|
||||
out = append(out, key)
|
||||
}
|
||||
@@ -212,7 +212,7 @@ func TestAModuleWhoseKeptDataChangesFormWaitsForTheNewBackupHolderAndTheBusModul
|
||||
if len(f.waits()) != 2 || len(f.events) != 2 || f.events[0] != meshwants.EventWaitHeld || f.bodies[0].Step != seatdef.StepPlace || f.bodies[0].Module != "web" {
|
||||
t.Fatalf("waits %v events %v bodies %+v: want two wait-held in the step place, `order.ace.web.backup` and `order.ace.web.bus`", f.waits(), f.events, f.bodies)
|
||||
}
|
||||
if _, err := acting.Decode(f.p.w.read(buckets.Waits)[acting.OrderKey("ace", "web", "backup")]); err != nil {
|
||||
if _, err := acting.Decode(f.p.w.read(buckets.Waits())[acting.OrderKey("ace", "web", "backup")]); err != nil {
|
||||
t.Fatalf("the wait is not kept under its key: %v", err)
|
||||
}
|
||||
f.pass()
|
||||
@@ -241,7 +241,7 @@ func TestAnOrderWaitIsReadAndReleasedByTheExistingTools(t *testing.T) {
|
||||
f.pass()
|
||||
f.pass()
|
||||
var acts []string
|
||||
s := newPutTools(memStore{waitsState: f.p.kvs[buckets.Waits].(*memKV), buckets.DeliveryWaitsState: &memKV{values: map[string][]byte{}}, buckets.DeliveredAgainState: &memKV{values: map[string][]byte{}}}, func(key string, body any) (json.RawMessage, error) {
|
||||
s := newPutTools(memStore{waitsState: f.p.kvs[buckets.Waits()].(*memKV), buckets.DeliveryWaitsState: &memKV{values: map[string][]byte{}}, buckets.DeliveredAgainState: &memKV{values: map[string][]byte{}}}, func(key string, body any) (json.RawMessage, error) {
|
||||
acts = append(acts, key)
|
||||
return json.RawMessage(`{"ok":true,"log":"logged"}`), nil
|
||||
})
|
||||
@@ -252,7 +252,7 @@ func TestAnOrderWaitIsReadAndReleasedByTheExistingTools(t *testing.T) {
|
||||
if _, err := s.doRelease(map[string]any{"node": "ace", "module": "web", "how": "now", "why": "the restore point is taken by hand"}, "shanks.node-tools"); err != nil || len(acts) == 0 {
|
||||
t.Fatalf("%v %v", err, acts)
|
||||
}
|
||||
f.p.w.put(buckets.Waits, "order.ace.web.backup", f.p.kvs[buckets.Waits].(*memKV).values["order.ace.web.backup"])
|
||||
f.p.w.put(buckets.Waits(), "order.ace.web.backup", f.p.kvs[buckets.Waits()].(*memKV).values["order.ace.web.backup"])
|
||||
f.pass()
|
||||
f.pass()
|
||||
if got := f.wantAt("web"); got != orderFP("b") || f.bodies[len(f.bodies)-1].How != acting.HowReleased {
|
||||
|
||||
@@ -48,7 +48,7 @@ func wantsPublisher(ctx context.Context, js jetstream.JetStream, node string) pu
|
||||
}
|
||||
msg := nats.NewMsg(seatdef.EventSubject(meshwants.Seat, event))
|
||||
msg.Header.Set("x-event-id", id)
|
||||
msg.Header.Set("x-source", assignment.Module)
|
||||
msg.Header.Set("x-source", assignment.Module())
|
||||
msg.Header.Set("x-node", node)
|
||||
msg.Header.Set("x-time", time.Now().UTC().Format(time.RFC3339Nano))
|
||||
msg.Header.Set("content-type", "application/json")
|
||||
@@ -236,7 +236,7 @@ func (o *outsideActs) take(machine string) ([]outside.Cond, bool) {
|
||||
// events, finding and ask. moved says the delivery moved owner to node.
|
||||
func (p *passer) outsideReview(ctx context.Context, module, fp string, nodes []string, n assign.Nodes, keeperSelf bool, moved func(owner, node string) bool) assign.Nodes {
|
||||
o := p.outside
|
||||
if o == nil || fp == "" || p.kvs[buckets.Waits] == nil {
|
||||
if o == nil || fp == "" || p.kvs[buckets.Waits()] == nil {
|
||||
return n
|
||||
}
|
||||
hex, err := catalogkey.KeyPart(fp)
|
||||
@@ -244,7 +244,7 @@ func (p *passer) outsideReview(ctx context.Context, module, fp string, nodes []s
|
||||
return n
|
||||
}
|
||||
now := p.clock()
|
||||
kept := p.w.read(buckets.Waits)
|
||||
kept := p.w.read(buckets.Waits())
|
||||
override := map[string]string{}
|
||||
// A wait of another fingerprint of this module is replaced by the newer want.
|
||||
for key, raw := range kept {
|
||||
@@ -409,12 +409,12 @@ func containsString(l []string, s string) bool {
|
||||
|
||||
// keepWait writes the wait's record.
|
||||
func (p *passer) keepWait(ctx context.Context, key string, w outside.Wait) {
|
||||
p.put(ctx, buckets.Waits, key, func() ([]byte, error) { return outside.Encode(w) })
|
||||
p.put(ctx, buckets.Waits(), key, func() ([]byte, error) { return outside.Encode(w) })
|
||||
}
|
||||
|
||||
func (p *passer) outsideBody(w outside.Wait) meshwants.Wait {
|
||||
since, from := w.Since, w.ClockFrom
|
||||
return meshwants.Wait{Step: meshwants.WaitStepOutside, Owner: assignment.Module, Node: w.Node, Module: w.Module, Fingerprint: w.FP, On: w.On,
|
||||
return meshwants.Wait{Step: meshwants.WaitStepOutside, Owner: assignment.Module(), Node: w.Node, Module: w.Module, Fingerprint: w.FP, On: w.On,
|
||||
Since: since, ClockFrom: &from, Bound: w.Bound,
|
||||
Words: fmt.Sprintf("%s on %s waits on %s", w.Module, w.Node, w.On)}
|
||||
}
|
||||
@@ -432,14 +432,14 @@ func (p *passer) outsideEnd(ctx context.Context, key string, w outside.Wait, how
|
||||
}
|
||||
p.outsideFinding(w, false)
|
||||
if ask, err := asks.OutsideAskKey(w.Node, w.Module, w.FP); err == nil {
|
||||
p.remove(ctx, buckets.Asks, ask)
|
||||
p.remove(ctx, buckets.Asks(), ask)
|
||||
}
|
||||
if p.outside != nil && w.Cause != "" {
|
||||
p.outside.mu.Lock()
|
||||
p.outside.causes[w.Cause] = removeString(p.outside.causes[w.Cause], w.Node)
|
||||
p.outside.mu.Unlock()
|
||||
}
|
||||
p.remove(ctx, buckets.Waits, key)
|
||||
p.remove(ctx, buckets.Waits(), key)
|
||||
}
|
||||
|
||||
func removeString(l []string, s string) []string {
|
||||
@@ -483,7 +483,7 @@ func (p *passer) outsideAsk(ctx context.Context, node, module, fp string, w outs
|
||||
}
|
||||
asking := assign.AskingSeconds(now)
|
||||
a := assign.OutsideAsk(key, asks.OutsideAbout(key), node, module, fp, w.On, w.ClockFrom, asking, now)
|
||||
p.put(ctx, buckets.Asks, key, func() ([]byte, error) { return assign.EncodeAsk(key, a) })
|
||||
p.put(ctx, buckets.Asks(), key, func() ([]byte, error) { return assign.EncodeAsk(key, a) })
|
||||
if ask, err := assign.AskOf(a); err == nil {
|
||||
return ask.ID
|
||||
}
|
||||
|
||||
@@ -38,9 +38,9 @@ type outsideRig struct {
|
||||
|
||||
func newOutsideRig(t *testing.T) *outsideRig {
|
||||
r := &outsideRig{t: t, now: time.Date(2026, 11, 11, 10, 0, 0, 0, time.UTC), found: map[string]bool{}, woke: make(chan struct{}, 16), health: nodeengineclient.HealthFailed}
|
||||
r.p = newPasser(kvSet{buckets.Waits: &memKV{values: map[string][]byte{}}, buckets.Asks: &memKV{values: map[string][]byte{}}}, r.now)
|
||||
r.p = newPasser(kvSet{buckets.Waits(): &memKV{values: map[string][]byte{}}, buckets.Asks(): &memKV{values: map[string][]byte{}}}, r.now)
|
||||
r.p.now = func() time.Time { return r.now }
|
||||
r.p.w.put(buckets.Asks, "x", nil)
|
||||
r.p.w.put(buckets.Asks(), "x", nil)
|
||||
r.p.outside = newOutsideActs(func(key string, body any) (json.RawMessage, error) {
|
||||
if r.unread {
|
||||
return nil, context.DeadlineExceeded
|
||||
@@ -82,7 +82,7 @@ func (r *outsideRig) review(keeperSelf bool, moved func(string, string) bool) as
|
||||
|
||||
func (r *outsideRig) wait() outside.Wait {
|
||||
r.t.Helper()
|
||||
for _, v := range r.p.w.read(buckets.Waits) {
|
||||
for _, v := range r.p.w.read(buckets.Waits()) {
|
||||
w, err := outside.Decode(v)
|
||||
if err != nil {
|
||||
r.t.Fatal(err)
|
||||
@@ -113,7 +113,7 @@ func TestTheWaitOnAnOutsideCause(t *testing.T) {
|
||||
r.p.outside.Heard("mesh.seat.mesh-conditions.event.condition-cleared", []byte(`{"key":"machine.hub.unreachable","event":"cleared","change":"cleared","at":"2026-11-11T10:01:00Z","subject":{"id":"hub","scope":"machine","machine":"hub","also":["ace"]}}`))
|
||||
<-r.woke
|
||||
r.review(false, nil)
|
||||
if len(r.events) != 2 || r.events[1] != meshwants.EventWaitEnded || r.bodies[1].How != outside.HowCleared || len(r.found) != 0 || len(r.p.w.read(buckets.Waits)) != 0 {
|
||||
if len(r.events) != 2 || r.events[1] != meshwants.EventWaitEnded || r.bodies[1].How != outside.HowCleared || len(r.found) != 0 || len(r.p.w.read(buckets.Waits())) != 0 {
|
||||
t.Errorf("events %v bodies %+v findings %v", r.events, r.bodies, r.found)
|
||||
}
|
||||
})
|
||||
@@ -175,7 +175,7 @@ func TestTheWaitOnAnOutsideCause(t *testing.T) {
|
||||
<-r.woke
|
||||
r.review(false, nil)
|
||||
key, _ := asks.OutsideAskKey("ace", "nats", fpTest)
|
||||
raw := r.p.w.value(buckets.Asks, key)
|
||||
raw := r.p.w.value(buckets.Asks(), key)
|
||||
if raw == nil {
|
||||
t.Fatalf("%s: no ask at %s", how, key)
|
||||
}
|
||||
@@ -189,7 +189,7 @@ func TestTheWaitOnAnOutsideCause(t *testing.T) {
|
||||
if r.events[len(r.events)-1] != meshwants.EventWaitEnded || r.bodies[len(r.bodies)-1].How != how || healthOf(n) != want {
|
||||
t.Errorf("%s: events %v last %+v health %s", how, r.events, r.bodies[len(r.bodies)-1], healthOf(n))
|
||||
}
|
||||
if r.p.w.value(buckets.Asks, key) != nil || len(r.found) != 0 {
|
||||
if r.p.w.value(buckets.Asks(), key) != nil || len(r.found) != 0 {
|
||||
t.Errorf("%s: the ask or the finding stands after the end", how)
|
||||
}
|
||||
// a check that goes on failing is not waited on again
|
||||
@@ -263,7 +263,7 @@ func TestTheToolsReadAndReleaseAnOutsideWait(t *testing.T) {
|
||||
acts = append(acts, key)
|
||||
return json.RawMessage(`{"ok":true,"log":"logged"}`), nil
|
||||
})
|
||||
for k, v := range r.p.w.read(buckets.Waits) {
|
||||
for k, v := range r.p.w.read(buckets.Waits()) {
|
||||
s.st.put(waitsState, k, v)
|
||||
}
|
||||
got, err := s.doWaits(map[string]any{"module": "nats"})
|
||||
@@ -291,7 +291,7 @@ func TestTheToolsReadAndReleaseAnOutsideWait(t *testing.T) {
|
||||
t.Errorf("wait = %+v", w)
|
||||
}
|
||||
// the process takes the release at its next pass: ended go-on, counted healthy
|
||||
r.p.w.put(buckets.Waits, keys[0], raw)
|
||||
r.p.w.put(buckets.Waits(), keys[0], raw)
|
||||
n := r.review(false, nil)
|
||||
if r.bodies[len(r.bodies)-1].How != "go-on" || n.Haves["ace"]["nats"].Health != nodeengineclient.HealthOK {
|
||||
t.Errorf("last %+v health %s", r.bodies[len(r.bodies)-1], n.Haves["ace"]["nats"].Health)
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package main
|
||||
|
||||
import plc "mesh-delivery-assigner/internal/assignment"
|
||||
|
||||
// The assigner's tests run as the module its manifest names, as a credential's module would say it at its start (design 47 G A-296).
|
||||
func init() { plc.SetModule("mesh-delivery-assigner") }
|
||||
@@ -226,7 +226,7 @@ func (p *passer) pass(ctx context.Context) {
|
||||
|
||||
// nodeWants writes each placed node's own want, `want.<node>.node`: the node composes its own (ADR 0547 §2, ADR 0571 §6).
|
||||
func (p *passer) nodeWants(ctx context.Context) {
|
||||
written := p.w.read(buckets.Wants)
|
||||
written := p.w.read(buckets.Wants())
|
||||
placed := p.w.placedNodes()
|
||||
// A node the catalogue places composes its own composition (ADR 0547 §2): its own want names none.
|
||||
for node := range placed {
|
||||
@@ -238,7 +238,7 @@ func (p *passer) nodeWants(ctx context.Context) {
|
||||
continue
|
||||
}
|
||||
want := mwc.NodeWant{Self: true, Words: "the node composes its own composition (ADR 0547 §2)"}
|
||||
p.put(ctx, buckets.Wants, key, func() ([]byte, error) { return mwc.EncodeNodeWant(want) })
|
||||
p.put(ctx, buckets.Wants(), key, func() ([]byte, error) { return mwc.EncodeNodeWant(want) })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,7 +269,7 @@ func (p *passer) wants(ctx context.Context) {
|
||||
}
|
||||
}
|
||||
wanted := p.w.wantsOf()
|
||||
chosen, _ := assign.DecodePolicies(p.w.read(assign.PoliciesBucket))
|
||||
chosen, _ := assign.DecodePolicies(p.w.read(assign.PoliciesBucket()))
|
||||
provisionsOf := p.provisionsOf()
|
||||
missing := map[string]bool{}
|
||||
defer func() { p.provisionsMissing(ctx, missing) }()
|
||||
@@ -348,7 +348,7 @@ func (p *passer) wants(ctx context.Context) {
|
||||
m.KeptBuilds = p.namedBuild(name, m.FP)
|
||||
m.Released = p.answeredWith(hold, "release")
|
||||
m.Kept = p.answeredWith(hold, "keep")
|
||||
m.Built, m.Held, m.Granted = p.w.built(name, fp), p.w.has(buckets.Wants, held) && !m.Released, p.w.has(grants, grant)
|
||||
m.Built, m.Held, m.Granted = p.w.built(name, fp), p.w.has(buckets.Wants(), held) && !m.Released, p.w.has(grants, grant)
|
||||
m.BuildFailed, m.BuildError = p.w.buildStanding(name, fp)
|
||||
if raw := p.w.value(grants, grant); raw != nil {
|
||||
if g, err := grantsstate.DecodeGrant(raw); err == nil {
|
||||
@@ -396,7 +396,7 @@ func (p *passer) wants(ctx context.Context) {
|
||||
key, err := mwc.HeldKey(name, fp)
|
||||
if err == nil {
|
||||
held := *out.Held
|
||||
p.put(ctx, buckets.Wants, key, func() ([]byte, error) { return mwc.EncodeHeld(held) })
|
||||
p.put(ctx, buckets.Wants(), key, func() ([]byte, error) { return mwc.EncodeHeld(held) })
|
||||
}
|
||||
}
|
||||
if out.Lifted {
|
||||
@@ -698,11 +698,11 @@ func (p *passer) putWant(ctx context.Context, node, module string, want mwc.Want
|
||||
}
|
||||
fp := want.Fp
|
||||
if want.Removed {
|
||||
if before, err := mwc.DecodeWant(p.w.value(buckets.Wants, key)); err == nil {
|
||||
if before, err := mwc.DecodeWant(p.w.value(buckets.Wants(), key)); err == nil {
|
||||
fp = before.Fp
|
||||
}
|
||||
}
|
||||
if p.written(ctx, buckets.Wants, key, func() ([]byte, error) { return mwc.EncodeWant(want) }) {
|
||||
if p.written(ctx, buckets.Wants(), key, func() ([]byte, error) { return mwc.EncodeWant(want) }) {
|
||||
p.sayWanted(node, module, fp, want.Removed)
|
||||
}
|
||||
}
|
||||
@@ -712,7 +712,7 @@ func (p *passer) putWant(ctx context.Context, node, module string, want mwc.Want
|
||||
// node (wants are only compared, ADR 0429 §7), and the options say so.
|
||||
func (p *passer) holdAsk(ctx context.Context, module, fp string, fresh *mwc.Held) {
|
||||
key, err := asks.HoldAskKey(module, fp)
|
||||
if err != nil || p.w.has(buckets.Asks, key) {
|
||||
if err != nil || p.w.has(buckets.Asks(), key) {
|
||||
return
|
||||
}
|
||||
// The asking is the hold's time in seconds: a hold of the same fingerprint written again after a lift is another asking, under
|
||||
@@ -721,7 +721,7 @@ func (p *passer) holdAsk(ctx context.Context, module, fp string, fresh *mwc.Held
|
||||
asking := int(now.Unix())
|
||||
h := p.heldHold(module, fp, fresh)
|
||||
a := assign.HoldAsk(key, module, fp, h.Node, h.Words, asking, now)
|
||||
p.put(ctx, buckets.Asks, key, func() ([]byte, error) { return assign.EncodeAsk(key, a) })
|
||||
p.put(ctx, buckets.Asks(), key, func() ([]byte, error) { return assign.EncodeAsk(key, a) })
|
||||
}
|
||||
|
||||
// lift takes away a hold the assigner lifted by itself, since every node that runs the module runs the held fingerprint healthy
|
||||
@@ -749,13 +749,13 @@ func (p *passer) lift(ctx context.Context, module, fp string) {
|
||||
p.said[c.Key+".lifted"] = true
|
||||
delete(p.said, c.Key+".raised") // a hold written again is said again
|
||||
}
|
||||
p.remove(ctx, buckets.Wants, held)
|
||||
p.remove(ctx, buckets.Asks, ask)
|
||||
p.remove(ctx, buckets.Wants(), held)
|
||||
p.remove(ctx, buckets.Asks(), ask)
|
||||
// The restore asks of that fingerprint, on every node, go with the hold: every node runs it healthy, so no node waits to be
|
||||
// set back from it (issue 1906, note 28).
|
||||
for key := range p.w.read(buckets.Asks) {
|
||||
for key := range p.w.read(buckets.Asks()) {
|
||||
if k, err := asks.ParseAnyAskKey(key); err == nil && k.Kind == asks.AskKindRestore && k.Module == module && k.FP == fp {
|
||||
p.remove(ctx, buckets.Asks, key)
|
||||
p.remove(ctx, buckets.Asks(), key)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -778,7 +778,7 @@ func (p *passer) staleAsk(key string, trunk map[string]meshcatalog.ModulesByModu
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
raw := p.w.value(buckets.Wants, wkey)
|
||||
raw := p.w.value(buckets.Wants(), wkey)
|
||||
if raw == nil {
|
||||
return ""
|
||||
}
|
||||
@@ -843,7 +843,7 @@ func (p *passer) yesWritten(k asks.AskKey) bool {
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
w, err := mwc.DecodeWant(p.w.value(buckets.Wants, key))
|
||||
w, err := mwc.DecodeWant(p.w.value(buckets.Wants(), key))
|
||||
return err == nil && w.Yes == k.Composed
|
||||
}
|
||||
|
||||
@@ -858,7 +858,7 @@ func (p *passer) fingerprintsOf(node, module, trunkFP string) []string {
|
||||
}
|
||||
add(trunkFP)
|
||||
if key, err := mwc.WantKey(node, module); err == nil {
|
||||
if w, err := mwc.DecodeWant(p.w.value(buckets.Wants, key)); err == nil {
|
||||
if w, err := mwc.DecodeWant(p.w.value(buckets.Wants(), key)); err == nil {
|
||||
add(w.Fp)
|
||||
}
|
||||
}
|
||||
@@ -877,7 +877,7 @@ func (p *passer) staleAsks(ctx context.Context, trunk map[string]meshcatalog.Mod
|
||||
if len(trunk) == 0 {
|
||||
return
|
||||
}
|
||||
for key := range p.w.read(buckets.Asks) {
|
||||
for key := range p.w.read(buckets.Asks()) {
|
||||
if why := p.staleAsk(key, trunk); why != "" {
|
||||
// The condition is cleared first and the ask goes only once that was said: the ask is what brings this key back at the
|
||||
// next pass, so a clear that failed once is made again, with no state of its own (issue 1906, note 29).
|
||||
@@ -899,7 +899,7 @@ func (p *passer) staleAsks(ctx context.Context, trunk map[string]meshcatalog.Mod
|
||||
p.revertFinding(assign.RevertHeldFinding(k.Node, k.Module, "", "", ""), false)
|
||||
}
|
||||
p.logf("the ask %s is about a fingerprint or composition the trunk has left behind and is taken away: %s", key, why)
|
||||
p.remove(ctx, buckets.Asks, key)
|
||||
p.remove(ctx, buckets.Asks(), key)
|
||||
// The held record stays: it is the memory that this fingerprint failed (ADR 0408 §4), and a revert returns the trunk to
|
||||
// exactly it. Only the condition, which would else ask for an answer that can no longer be given, is cleared, and it is
|
||||
// raised again if the trunk returns to the fingerprint.
|
||||
@@ -932,7 +932,7 @@ func (p *passer) heldHold(module, fp string, fresh *mwc.Held) mwc.Held {
|
||||
if err != nil {
|
||||
return mwc.Held{}
|
||||
}
|
||||
h, _ := mwc.DecodeHeld(p.w.read(buckets.Wants)[key])
|
||||
h, _ := mwc.DecodeHeld(p.w.read(buckets.Wants())[key])
|
||||
return h
|
||||
}
|
||||
|
||||
@@ -956,13 +956,13 @@ func (p *passer) heldCondition(ctx context.Context, module, fp string, fresh *mw
|
||||
// restoreAsk writes, once, the ask about a module holding data whose held fingerprint failed its health check on node.
|
||||
func (p *passer) restoreAsk(ctx context.Context, module, node, fp string) {
|
||||
key, err := asks.RestoreAskKey(node, module, fp)
|
||||
if err != nil || p.w.has(buckets.Asks, key) {
|
||||
if err != nil || p.w.has(buckets.Asks(), key) {
|
||||
return
|
||||
}
|
||||
words := p.heldHold(module, fp, nil).Words
|
||||
now := p.clock()
|
||||
a := assign.RestoreAsk(key, node, module, fp, words, assign.AskingSeconds(now), now)
|
||||
p.put(ctx, buckets.Asks, key, func() ([]byte, error) { return assign.EncodeAsk(key, a) })
|
||||
p.put(ctx, buckets.Asks(), key, func() ([]byte, error) { return assign.EncodeAsk(key, a) })
|
||||
}
|
||||
|
||||
// answeredWith says whether the teller wrote an answer to the ask at key as it stands now and the option chosen is option. The
|
||||
@@ -974,7 +974,7 @@ func (p *passer) answeredWith(key, option string) bool {
|
||||
if !ok || a.Option != option {
|
||||
return false
|
||||
}
|
||||
_, ask, err := assign.DecodeAsk(key, p.w.value(buckets.Asks, key))
|
||||
_, ask, err := assign.DecodeAsk(key, p.w.value(buckets.Asks(), key))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
@@ -1000,12 +1000,12 @@ func (p *passer) clearHeld(module, fp string) bool {
|
||||
// recordAsk writes, once, the ask about a move of a module of policy record that waits for the operator's yes.
|
||||
func (p *passer) recordAsk(ctx context.Context, module, fp string, need assign.RecordNeed) {
|
||||
key, err := asks.RecordAskKey(need.Node, module, need.Digest)
|
||||
if err != nil || p.w.has(buckets.Asks, key) {
|
||||
if err != nil || p.w.has(buckets.Asks(), key) {
|
||||
return
|
||||
}
|
||||
now := p.clock()
|
||||
a := assign.RecordAsk(key, need.Node, module, fp, need, assign.AskingSeconds(now), now)
|
||||
p.put(ctx, buckets.Asks, key, func() ([]byte, error) { return assign.EncodeAsk(key, a) })
|
||||
p.put(ctx, buckets.Asks(), key, func() ([]byte, error) { return assign.EncodeAsk(key, a) })
|
||||
}
|
||||
|
||||
// backups sees to the modules held on a node because the backup before an apply failed (the have's phase held, ADR 0539 §16):
|
||||
@@ -1038,7 +1038,7 @@ func (p *passer) backups(ctx context.Context, reverting map[string]bool) {
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
current, currentAsk, there := assign.DecodeAsk(akey, p.w.read(buckets.Asks)[akey])
|
||||
current, currentAsk, there := assign.DecodeAsk(akey, p.w.read(buckets.Asks())[akey])
|
||||
if there != nil || assign.BackupAskDigest(current) != want.Composed {
|
||||
after := -1
|
||||
if there == nil {
|
||||
@@ -1075,7 +1075,7 @@ func (p *passer) backups(ctx context.Context, reverting map[string]bool) {
|
||||
// backupAsk writes the backup ask at its asking and raises the condition of the failed backup, once.
|
||||
func (p *passer) backupAsk(ctx context.Context, key, node, module string, want mwc.Want, words string, asking int) {
|
||||
a := assign.BackupAsk(key, node, module, want.Fp, want.Composed, words, asking, time.Now())
|
||||
p.put(ctx, buckets.Asks, key, func() ([]byte, error) { return assign.EncodeAsk(key, a) })
|
||||
p.put(ctx, buckets.Asks(), key, func() ([]byte, error) { return assign.EncodeAsk(key, a) })
|
||||
c := assign.BackupCondition(node, module, want.Fp, words, true)
|
||||
if p.say == nil || p.said[c.Key] {
|
||||
return
|
||||
@@ -1227,7 +1227,7 @@ func missingPulse(key string) string { return "provision-missing." + key }
|
||||
// `provider.<node>.<module>.<provision>.provision-missing` is written; one keyed again ends both. The pulse's expiry, heard on
|
||||
// the watch, is the pass that writes it: no timer.
|
||||
func (p *passer) provisionsMissing(ctx context.Context, missing map[string]bool) {
|
||||
pulses := p.kvs[plc.PulsesBucket]
|
||||
pulses := p.kvs[plc.PulsesBucket()]
|
||||
if p.missing == nil {
|
||||
p.missing = map[string]bool{}
|
||||
}
|
||||
@@ -1263,7 +1263,7 @@ func (p *passer) provisionsMissing(ctx context.Context, missing map[string]bool)
|
||||
delete(p.missing, key)
|
||||
if pulses != nil && p.pulsing(missingPulse(key)) {
|
||||
if err := pulses.Delete(ctx, missingPulse(key)); err == nil {
|
||||
p.w.put(plc.PulsesBucket, missingPulse(key), nil)
|
||||
p.w.put(plc.PulsesBucket(), missingPulse(key), nil)
|
||||
}
|
||||
}
|
||||
if p.missingSaid[key] && p.findings.Delete != nil {
|
||||
@@ -1318,7 +1318,7 @@ func (p *passer) unplacedFindings() {
|
||||
// assignments holds any more deleted. A pass follows each assignments change and each manifest the indexer takes in, so no
|
||||
// timer writes it.
|
||||
func (p *passer) seatHolders(ctx context.Context) {
|
||||
kv := p.kvs[plc.SeatHoldersBucket]
|
||||
kv := p.kvs[plc.SeatHoldersBucket()]
|
||||
if kv == nil {
|
||||
return
|
||||
}
|
||||
@@ -1335,13 +1335,13 @@ func (p *passer) seatHolders(ctx context.Context) {
|
||||
return
|
||||
}
|
||||
want := plc.SeatHoldersOf(assignments)
|
||||
have := p.w.read(plc.SeatHoldersBucket)
|
||||
have := p.w.read(plc.SeatHoldersBucket())
|
||||
for key, sh := range want {
|
||||
value, err := json.Marshal(sh)
|
||||
if err != nil || string(have[key]) == string(value) {
|
||||
continue
|
||||
}
|
||||
p.put(ctx, plc.SeatHoldersBucket, key, func() ([]byte, error) { return value, nil })
|
||||
p.put(ctx, plc.SeatHoldersBucket(), key, func() ([]byte, error) { return value, nil })
|
||||
}
|
||||
for key := range have {
|
||||
if _, ok := want[key]; ok {
|
||||
@@ -1351,7 +1351,7 @@ func (p *passer) seatHolders(ctx context.Context) {
|
||||
p.logf("the seat holders %s are not deleted: %v", key, err)
|
||||
continue
|
||||
}
|
||||
p.w.put(plc.SeatHoldersBucket, key, nil)
|
||||
p.w.put(plc.SeatHoldersBucket(), key, nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ func placeOn(t *testing.T, w *world, node string, modules ...string) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for key, raw := range values {
|
||||
w.put(plc.AssignmentsBucket, key, raw)
|
||||
w.put(plc.AssignmentsBucket(), key, raw)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ var fpTest = meshcatalogclient.SourcePrefix2 + strings.Repeat("b", 64)
|
||||
// an id it keeps).
|
||||
func TestAHoldAskedAgainAfterALiftAndARestartHasANewID(t *testing.T) {
|
||||
asksKV, wantsKV := &memKV{values: map[string][]byte{}}, &memKV{values: map[string][]byte{}}
|
||||
kvs := kvSet{buckets.Asks: asksKV, buckets.Wants: wantsKV}
|
||||
kvs := kvSet{buckets.Asks(): asksKV, buckets.Wants(): wantsKV}
|
||||
key, _ := asks.HoldAskKey("web", fpTest)
|
||||
held := &mwc.Held{Node: "ace", Words: "health failed"}
|
||||
first := newPasser(kvs, time.Date(2026, 10, 25, 1, 0, 0, 0, time.UTC))
|
||||
@@ -167,7 +167,7 @@ func TestAHoldAskedAgainAfterALiftAndARestartHasANewID(t *testing.T) {
|
||||
} {
|
||||
t.Run("ended backup ask: "+c.name, func(t *testing.T) {
|
||||
bkv := &memKV{values: map[string][]byte{}}
|
||||
pb := newPasser(kvSet{buckets.Asks: bkv, buckets.Wants: &memKV{values: map[string][]byte{}}}, now)
|
||||
pb := newPasser(kvSet{buckets.Asks(): bkv, buckets.Wants(): &memKV{values: map[string][]byte{}}}, now)
|
||||
ba := assign.BackupAsk(backKey, "ace", "web", fpTest, "sha256:"+strings.Repeat("d", 64), "w", 2, now)
|
||||
bv, err := assign.EncodeAsk(backKey, ba)
|
||||
if err != nil {
|
||||
@@ -177,7 +177,7 @@ func TestAHoldAskedAgainAfterALiftAndARestartHasANewID(t *testing.T) {
|
||||
if c.want != nil {
|
||||
wkey, _ := mwc.WantKey("ace", "web")
|
||||
raw, _ := mwc.EncodeWant(*c.want)
|
||||
pb.w.put(buckets.Wants, wkey, raw)
|
||||
pb.w.put(buckets.Wants(), wkey, raw)
|
||||
}
|
||||
_, bask, _ := assign.DecodeAsk(backKey, bv)
|
||||
body, err := tellerevents.EncodeEnded(tellerevents.Ended{Key: backKey, Ask: bask.ID, Outcome: occ.OutcomeExpired})
|
||||
@@ -250,7 +250,7 @@ func TestAnAnswerCountsOnlyForTheAskingItAnswered(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
p.w.put(buckets.Asks, key, v)
|
||||
p.w.put(buckets.Asks(), key, v)
|
||||
return mustAskID(a)
|
||||
}
|
||||
first := put(100)
|
||||
@@ -266,7 +266,7 @@ func TestAnAnswerCountsOnlyForTheAskingItAnswered(t *testing.T) {
|
||||
if p.answeredWith(key, "release") {
|
||||
t.Error("the answer to an earlier hold carried over to a later one")
|
||||
}
|
||||
p.w.put(buckets.Asks, key, nil) // the ask taken away
|
||||
p.w.put(buckets.Asks(), key, nil) // the ask taken away
|
||||
if p.answeredWith(key, "release") {
|
||||
t.Error("an answer to an ask taken away was taken")
|
||||
}
|
||||
@@ -492,7 +492,7 @@ func (b *fakeBus) Emit(eventType string, body any) error {
|
||||
func aPlacementsBuildIsPut(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
putsKV := &memKV{values: map[string][]byte{}}
|
||||
p := newPasser(kvSet{buckets.Puts: putsKV}, time.Date(2026, 11, 10, 18, 0, 0, 0, time.UTC))
|
||||
p := newPasser(kvSet{buckets.Puts(): putsKV}, time.Date(2026, 11, 10, 18, 0, 0, 0, time.UTC))
|
||||
earlier := meshcatalogclient.SourcePrefix2 + strings.Repeat("e", 64)
|
||||
place := func(build string) {
|
||||
values, err := mmac.AssignmentsValues(mma.Assignments{Node: "g14", Modules: []mma.PlacedModule{{Module: "web", Build: build}, {Module: "db"}}})
|
||||
@@ -500,7 +500,7 @@ func aPlacementsBuildIsPut(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for key, raw := range values {
|
||||
p.w.putEntry(plc.AssignmentsBucket, key, raw, uint64(len(raw)), time.Now())
|
||||
p.w.putEntry(plc.AssignmentsBucket(), key, raw, uint64(len(raw)), time.Now())
|
||||
}
|
||||
}
|
||||
read := func() (puts.Put, bool) {
|
||||
@@ -536,7 +536,7 @@ func aPlacementsBuildIsPut(t *testing.T) {
|
||||
person := puts.Put{FP: earlier, Hold: true, Why: "a try", By: "lead@shanks", At: time.Now()}.Said(time.Now(), puts.Holding, "held")
|
||||
raw, _ := puts.Encode(person)
|
||||
putsKV.values["g14.web"] = raw
|
||||
p.w.put(buckets.Puts, "g14.web", raw)
|
||||
p.w.put(buckets.Puts(), "g14.web", raw)
|
||||
p.placementPuts(ctx)
|
||||
if got, _ := read(); got.Step != puts.Holding || got.By != "lead@shanks" {
|
||||
t.Errorf("a person's put was released by the placement: %+v", got)
|
||||
@@ -554,7 +554,7 @@ func putOfTheBus(t *testing.T) {
|
||||
digest := "sha256:" + strings.Repeat("d", 64)
|
||||
mem := func() *memKV { return &memKV{values: map[string][]byte{}} }
|
||||
putsKV, asksKV, wantsKV := mem(), mem(), mem()
|
||||
kvs := kvSet{buckets.Puts: putsKV, buckets.Asks: asksKV, buckets.Wants: wantsKV}
|
||||
kvs := kvSet{buckets.Puts(): putsKV, buckets.Asks(): asksKV, buckets.Wants(): wantsKV}
|
||||
p := newPasser(kvs, now)
|
||||
bus := &fakeBus{calls: make(chan string, 8)}
|
||||
woke := make(chan struct{}, 8) // each act's goroutine wakes a pass once it is done
|
||||
@@ -617,7 +617,7 @@ func putOfTheBus(t *testing.T) {
|
||||
pkey, _ := puts.Key("novox", "nats")
|
||||
step := func() puts.Put {
|
||||
t.Helper()
|
||||
world(buckets.Puts, pkey, putsKV.values[pkey])
|
||||
world(buckets.Puts(), pkey, putsKV.values[pkey])
|
||||
p.pass(ctx)
|
||||
got, err := puts.Decode(putsKV.values[pkey])
|
||||
if err != nil {
|
||||
@@ -649,7 +649,7 @@ func putOfTheBus(t *testing.T) {
|
||||
if _, there := wantsKV.values["want.novox.nats"]; there {
|
||||
t.Fatal("the bus's want was written before the operator's warrant")
|
||||
}
|
||||
world(buckets.Asks, akey, asksKV.values[akey])
|
||||
world(buckets.Asks(), akey, asksKV.values[akey])
|
||||
yes, _ := mdc.EncodeAnswer(mdc.Answer{Option: assign.OptionYes, Warrant: a.Ask.ID + "@2026-10-25T22:01:00Z", Words: "the operator, verified"})
|
||||
world(buckets.Answers, akey, yes)
|
||||
if got := step(); got.Step != puts.Snapshot {
|
||||
@@ -664,7 +664,7 @@ func putOfTheBus(t *testing.T) {
|
||||
if call := <-bus.calls; !strings.HasPrefix(call, "seat:mesh-hand-acts.warranted ") || !strings.Contains(call, a.Ask.ID) { // ADR 0565 §11: into the seat
|
||||
t.Errorf("the warrant was recorded as %q", call)
|
||||
}
|
||||
world(buckets.Wants, "want.novox.nats", wantsKV.values["want.novox.nats"])
|
||||
world(buckets.Wants(), "want.novox.nats", wantsKV.values["want.novox.nats"])
|
||||
// Design 47 G §309: the put ends on the node-engine seat's running naming its fingerprint, never on the have.
|
||||
haveRunning, _ := nodeengineclient.EncodeHave(nodeengine.HaveByNodeModuleValue{Fp: fp, Composed: digest, Phase: nodeengineclient.PhaseRunning, Health: nodeengineclient.HealthOK})
|
||||
world(buckets.Have, hkey, haveRunning)
|
||||
@@ -715,7 +715,7 @@ func putOfTheBus(t *testing.T) {
|
||||
if again.Step != puts.Person || err != nil || a2.Ask.ID == a.Ask.ID {
|
||||
t.Fatalf("put again, it stands at %s (%s); its ask %q, %v; the first ask was %q", again.Step, again.Words, a2.Ask.ID, err, a.Ask.ID)
|
||||
}
|
||||
world(buckets.Asks, akey, asksKV.values[akey])
|
||||
world(buckets.Asks(), akey, asksKV.values[akey])
|
||||
yes2, _ := mdc.EncodeAnswer(mdc.Answer{Option: assign.OptionYes, Warrant: a2.Ask.ID + "@2026-10-25T22:03:00Z", Words: "the operator, verified"})
|
||||
world(buckets.Answers, akey, yes2)
|
||||
if got := step(); got.Step != puts.Snapshot {
|
||||
@@ -725,7 +725,7 @@ func putOfTheBus(t *testing.T) {
|
||||
// Released between the pass's read and its write: the pass writes no want for the put that no longer stands.
|
||||
oldWant, _ := mwc.EncodeWant(mwc.Want{Fp: meshcatalogclient.SourcePrefix2 + strings.Repeat("a", 64)})
|
||||
wantsKV.values["want.novox.nats"] = oldWant
|
||||
world(buckets.Wants, "want.novox.nats", oldWant)
|
||||
world(buckets.Wants(), "want.novox.nats", oldWant)
|
||||
if _, err := s.doRelease(map[string]any{"node": "novox", "module": "nats", "why": "changed my mind"}, "shanks.node-tools"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -911,7 +911,7 @@ func testThePassGathersTheModulesOnNeitherList(t *testing.T) {
|
||||
func TestTheFirstPassAfterAStartClearsWhatAPreviousProcessRaised(t *testing.T) {
|
||||
t.Run("a held module", testAHeldModuleRaisesNoNeitherAndClearsAStandingOne)
|
||||
wantsKV := &memKV{values: map[string][]byte{}}
|
||||
p := newPasser(kvSet{buckets.Wants: wantsKV, buckets.Asks: &memKV{values: map[string][]byte{}}}, time.Now())
|
||||
p := newPasser(kvSet{buckets.Wants(): wantsKV, buckets.Asks(): &memKV{values: map[string][]byte{}}}, time.Now())
|
||||
var said []oc.Condition
|
||||
p.say = func(c oc.Condition) error { said = append(said, c); return nil }
|
||||
mod, _ := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Fp2: fpTest, Repository: "novox/web", Commit: strings.Repeat("1", 40), Path: meshcatalogclient.RootPath})
|
||||
@@ -923,7 +923,7 @@ func TestTheFirstPassAfterAStartClearsWhatAPreviousProcessRaised(t *testing.T) {
|
||||
p.w.put(buckets.Have, "g14.web", have)
|
||||
key, _ := mwc.WantKey("g14", "web")
|
||||
want, _ := mwc.EncodeWant(mwc.Want{Fp: fpTest, Composed: "sha256:" + strings.Repeat("c", 64)})
|
||||
p.w.put(buckets.Wants, key, want)
|
||||
p.w.put(buckets.Wants(), key, want)
|
||||
p.wants(context.Background())
|
||||
cleared := map[string]bool{}
|
||||
for _, c := range said {
|
||||
@@ -949,7 +949,7 @@ func TestTheFirstPassAfterAStartClearsWhatAPreviousProcessRaised(t *testing.T) {
|
||||
// raised (the first pass after a start), each once.
|
||||
func testAHeldModuleRaisesNoNeitherAndClearsAStandingOne(t *testing.T) {
|
||||
setup := func() (*passer, *[]oc.Condition) {
|
||||
p := newPasser(kvSet{buckets.Wants: &memKV{values: map[string][]byte{}}, buckets.Asks: &memKV{values: map[string][]byte{}}}, time.Now())
|
||||
p := newPasser(kvSet{buckets.Wants(): &memKV{values: map[string][]byte{}}, buckets.Asks(): &memKV{values: map[string][]byte{}}}, time.Now())
|
||||
said := &[]oc.Condition{}
|
||||
p.say = func(c oc.Condition) error { *said = append(*said, c); return nil }
|
||||
mod, _ := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Fp2: fpTest, Repository: "novox/resolved", Commit: strings.Repeat("1", 40), Path: meshcatalogclient.RootPath})
|
||||
@@ -961,7 +961,7 @@ func testAHeldModuleRaisesNoNeitherAndClearsAStandingOne(t *testing.T) {
|
||||
p.w.put(buckets.Have, "shanks.systemd-resolved", have)
|
||||
hkey, _ := mwc.HeldKey("systemd-resolved", fpTest)
|
||||
held, _ := mwc.EncodeHeld(mwc.Held{Node: "shanks", Words: "health failed"})
|
||||
p.w.put(buckets.Wants, hkey, held)
|
||||
p.w.put(buckets.Wants(), hkey, held)
|
||||
return p, said
|
||||
}
|
||||
key := assign.NeitherCondition("shanks", "systemd-resolved", assign.Holding{}, false).Key
|
||||
@@ -1034,7 +1034,7 @@ func TestAsksOfAFingerprintTheTrunkLeftBehindAreTakenAway(t *testing.T) {
|
||||
} {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
asksKV, wantsKV := &memKV{values: map[string][]byte{}}, &memKV{values: map[string][]byte{}}
|
||||
p := newPasser(kvSet{buckets.Asks: asksKV, buckets.Wants: wantsKV}, time.Now())
|
||||
p := newPasser(kvSet{buckets.Asks(): asksKV, buckets.Wants(): wantsKV}, time.Now())
|
||||
var said []oc.Condition
|
||||
p.say = func(c oc.Condition) error { said = append(said, c); return nil }
|
||||
for _, key := range all {
|
||||
@@ -1055,7 +1055,7 @@ func TestAsksOfAFingerprintTheTrunkLeftBehindAreTakenAway(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
asksKV.values[key] = v
|
||||
p.w.put(buckets.Asks, key, v)
|
||||
p.w.put(buckets.Asks(), key, v)
|
||||
}
|
||||
placeOn(t, p.w, "novox", "web") // a placed node composes its own: its module's have waits on a composition
|
||||
if !c.noDigit {
|
||||
@@ -1073,11 +1073,11 @@ func TestAsksOfAFingerprintTheTrunkLeftBehindAreTakenAway(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
p.w.put(buckets.Wants, wkey, raw)
|
||||
p.w.put(buckets.Wants(), wkey, raw)
|
||||
}
|
||||
heldOld, _ := mwc.HeldKey("web", fpOld)
|
||||
wantsKV.values[heldOld] = []byte("x")
|
||||
p.w.put(buckets.Wants, heldOld, []byte("x"))
|
||||
p.w.put(buckets.Wants(), heldOld, []byte("x"))
|
||||
firstAsk, _ := decodeAsk(holdOld, asksKV.values[holdOld])
|
||||
p.staleAsks(context.Background(), c.trunk)
|
||||
// the held record and condition of a fingerprint whose hold ask went are taken away with it
|
||||
@@ -1119,7 +1119,7 @@ func TestAsksOfAFingerprintTheTrunkLeftBehindAreTakenAway(t *testing.T) {
|
||||
|
||||
t.Run("a clear that failed to be said is said at the next pass, and the ask goes after it", func(t *testing.T) {
|
||||
asksKV, wantsKV := &memKV{values: map[string][]byte{}}, &memKV{values: map[string][]byte{}}
|
||||
p := newPasser(kvSet{buckets.Asks: asksKV, buckets.Wants: wantsKV}, time.Now())
|
||||
p := newPasser(kvSet{buckets.Asks(): asksKV, buckets.Wants(): wantsKV}, time.Now())
|
||||
var said []oc.Condition
|
||||
fail := true
|
||||
p.say = func(c oc.Condition) error {
|
||||
@@ -1136,7 +1136,7 @@ func TestAsksOfAFingerprintTheTrunkLeftBehindAreTakenAway(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
asksKV.values[holdOld] = v
|
||||
p.w.put(buckets.Asks, holdOld, v)
|
||||
p.w.put(buckets.Asks(), holdOld, v)
|
||||
p.staleAsks(context.Background(), trunkOf(fpNew))
|
||||
if _, there := asksKV.values[holdOld]; !there || len(said) != 0 {
|
||||
t.Fatalf("after a failed clear: ask left = %v, conditions said = %d; want the ask kept and nothing said", there, len(said))
|
||||
@@ -1149,12 +1149,12 @@ func TestAsksOfAFingerprintTheTrunkLeftBehindAreTakenAway(t *testing.T) {
|
||||
|
||||
t.Run("a lift takes the restore asks of its fingerprint on every node", func(t *testing.T) {
|
||||
asksKV, wantsKV := &memKV{values: map[string][]byte{}}, &memKV{values: map[string][]byte{}}
|
||||
p := newPasser(kvSet{buckets.Asks: asksKV, buckets.Wants: wantsKV}, time.Now())
|
||||
p := newPasser(kvSet{buckets.Asks(): asksKV, buckets.Wants(): wantsKV}, time.Now())
|
||||
keep, _ := asks.RestoreAskKey("ace", "web", fpNew)
|
||||
keepOther, _ := asks.RestoreAskKey("ace", "other", fpOld)
|
||||
for _, key := range []string{restOld, mustKey(asks.RestoreAskKey("ace", "web", fpOld)), keep, keepOther} {
|
||||
asksKV.values[key] = []byte("x")
|
||||
p.w.put(buckets.Asks, key, []byte("x"))
|
||||
p.w.put(buckets.Asks(), key, []byte("x"))
|
||||
}
|
||||
p.lift(context.Background(), "web", fpOld)
|
||||
for key, gone := range map[string]bool{restOld: true, mustKey(asks.RestoreAskKey("ace", "web", fpOld)): true, keep: false, keepOther: false} {
|
||||
@@ -1189,7 +1189,7 @@ func aReleasesApproveAnswersTheMoveOfItsBuildAlone(t *testing.T) {
|
||||
waiting, was := "sha256:"+strings.Repeat("e", 64), "sha256:"+strings.Repeat("d", 64)
|
||||
commit := strings.Repeat("1", 40)
|
||||
asksKV := &memKV{values: map[string][]byte{}}
|
||||
p := newPasser(kvSet{buckets.Asks: asksKV}, time.Now())
|
||||
p := newPasser(kvSet{buckets.Asks(): asksKV}, time.Now())
|
||||
p.logf = t.Logf
|
||||
mod, _ := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Fp2: fpTest, Repository: "novox/web", Commit: commit, Src: 7, FpSrc: 7, Path: meshcatalogclient.RootPath})
|
||||
putModuleRaw(p.w, trunkBranch, "web", mod)
|
||||
@@ -1230,14 +1230,14 @@ func aPlacedNodesWantsNameTheBuildAloneAndItsYesComesFromItsHave(t *testing.T) {
|
||||
fpOld := meshcatalogclient.SourcePrefix2 + strings.Repeat("a", 64)
|
||||
waiting, was := "sha256:"+strings.Repeat("e", 64), "sha256:"+strings.Repeat("d", 64)
|
||||
asksKV, wantsKV := &memKV{values: map[string][]byte{}}, &memKV{values: map[string][]byte{}}
|
||||
p := newPasser(kvSet{buckets.Asks: asksKV, buckets.Wants: wantsKV}, time.Now())
|
||||
p := newPasser(kvSet{buckets.Asks(): asksKV, buckets.Wants(): wantsKV}, time.Now())
|
||||
p.logf = t.Logf
|
||||
put := func(bucket, key string, v []byte) { p.w.put(bucket, key, v) }
|
||||
mod, _ := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Fp2: fpTest, Repository: "novox/web", Commit: strings.Repeat("1", 40), Path: meshcatalogclient.RootPath})
|
||||
putModuleRaw(p.w, trunkBranch, "web", mod) // the trunk's build is not done: rule 1 holds g14 at fpOld
|
||||
placeOn(t, p.w, "g14", "web")
|
||||
pl, _ := mmac.EncodeAssignments(mma.Assignments{Node: "g14", Modules: []mma.PlacedModule{{Module: "web"}}})
|
||||
put(plc.AssignmentsBucket, "assignments.g14", pl)
|
||||
put(plc.AssignmentsBucket(), "assignments.g14", pl)
|
||||
on := true
|
||||
p.w.heardPresence("g14", !on, time.Now())
|
||||
have, _ := nodeengineclient.EncodeHave(nodeengine.HaveByNodeModuleValue{Fp: fpOld, Composed: was, Phase: nodeengineclient.PhaseRunning, Health: nodeengineclient.HealthOK})
|
||||
@@ -1245,10 +1245,10 @@ func aPlacedNodesWantsNameTheBuildAloneAndItsYesComesFromItsHave(t *testing.T) {
|
||||
wkey, _ := mwc.WantKey("g14", "web")
|
||||
want, _ := mwc.EncodeWant(mwc.Want{Fp: fpOld, Composed: was})
|
||||
wantsKV.values[wkey] = want
|
||||
put(buckets.Wants, wkey, want)
|
||||
put(buckets.Wants(), wkey, want)
|
||||
sync := func() {
|
||||
for k, v := range wantsKV.values {
|
||||
put(buckets.Wants, k, v)
|
||||
put(buckets.Wants(), k, v)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1287,7 +1287,7 @@ func aPlacedNodesWantsNameTheBuildAloneAndItsYesComesFromItsHave(t *testing.T) {
|
||||
t.Fatalf("a pass that changed nothing put %d wants and %d asks", wantsKV.puts-wantPuts, asksKV.puts-askPuts)
|
||||
}
|
||||
// Whichever rule asks it, a want put with the value the bucket holds reaches no bucket: each put is a change its node hears.
|
||||
p.put(context.Background(), buckets.Wants, wkey, func() ([]byte, error) { return wantsKV.values[wkey], nil })
|
||||
p.put(context.Background(), buckets.Wants(), wkey, func() ([]byte, error) { return wantsKV.values[wkey], nil })
|
||||
if wantsKV.puts != wantPuts {
|
||||
t.Fatalf("a want put unchanged reached the bucket")
|
||||
}
|
||||
@@ -1295,7 +1295,7 @@ func aPlacedNodesWantsNameTheBuildAloneAndItsYesComesFromItsHave(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
put(buckets.Asks, akey, asksKV.values[akey])
|
||||
put(buckets.Asks(), akey, asksKV.values[akey])
|
||||
yes, _ := mdc.EncodeAnswer(mdc.Answer{Option: assign.OptionYes, Warrant: a.Ask.ID + "@2026-10-26T19:00:00Z", Words: "the operator"})
|
||||
put(buckets.Answers, akey, yes)
|
||||
p.wants(context.Background())
|
||||
@@ -1454,7 +1454,7 @@ func aRecordAskIsNotTakenAwayBeforeItsYesIsWrittenAndIsAskedAgainWhenWrittenAgai
|
||||
} {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
asksKV, wantsKV := &memKV{values: map[string][]byte{}}, &memKV{values: map[string][]byte{}}
|
||||
p := newPasser(kvSet{buckets.Asks: asksKV, buckets.Wants: wantsKV}, time.Now())
|
||||
p := newPasser(kvSet{buckets.Asks(): asksKV, buckets.Wants(): wantsKV}, time.Now())
|
||||
placeOn(t, p.w, "novox", "web")
|
||||
composes(t, p.w, "novox", "sha256:"+strings.Repeat("9", 64)) // the whole node's composition is never a module's
|
||||
a := assign.RecordAsk(rkey, "novox", "web", fp, assign.RecordNeed{Node: "novox", Digest: dig}, 2, time.Now())
|
||||
@@ -1463,7 +1463,7 @@ func aRecordAskIsNotTakenAwayBeforeItsYesIsWrittenAndIsAskedAgainWhenWrittenAgai
|
||||
t.Fatal(err)
|
||||
}
|
||||
asksKV.values[rkey] = v
|
||||
p.w.put(buckets.Asks, rkey, v)
|
||||
p.w.put(buckets.Asks(), rkey, v)
|
||||
hkey, _ := nodeengineclient.HaveKey("novox", "web")
|
||||
raw, err := nodeengineclient.EncodeHave(c.have)
|
||||
if err != nil {
|
||||
@@ -1477,7 +1477,7 @@ func aRecordAskIsNotTakenAwayBeforeItsYesIsWrittenAndIsAskedAgainWhenWrittenAgai
|
||||
if c.wanted != "" {
|
||||
wkey, _ := mwc.WantKey("novox", "web")
|
||||
w, _ := mwc.EncodeWant(mwc.Want{Fp: fp, Self: true, Yes: c.wanted})
|
||||
p.w.put(buckets.Wants, wkey, w)
|
||||
p.w.put(buckets.Wants(), wkey, w)
|
||||
}
|
||||
p.staleAsks(context.Background(), trunk)
|
||||
if _, there := asksKV.values[rkey]; there == c.gone {
|
||||
@@ -1507,9 +1507,9 @@ func aRecordAskIsNotTakenAwayBeforeItsYesIsWrittenAndIsAskedAgainWhenWrittenAgai
|
||||
}{"record": {rec, rkey}, "restore": {restore, rest}, "hold": {holdIt, hold}} {
|
||||
at := time.Now()
|
||||
asksKV := &memKV{values: map[string][]byte{}}
|
||||
p := newPasser(kvSet{buckets.Asks: asksKV, buckets.Wants: &memKV{values: map[string][]byte{}}}, at)
|
||||
p := newPasser(kvSet{buckets.Asks(): asksKV, buckets.Wants(): &memKV{values: map[string][]byte{}}}, at)
|
||||
first := c.write(p)
|
||||
p.remove(context.Background(), buckets.Asks, c.key)
|
||||
p.remove(context.Background(), buckets.Asks(), c.key)
|
||||
p.now = func() time.Time { return at.Add(time.Minute) }
|
||||
if again := c.write(p); again == first {
|
||||
t.Errorf("%s: the ask written again carries the id %s the router kept, so it is never asked again", name, first)
|
||||
@@ -1521,7 +1521,7 @@ func aRecordAskIsNotTakenAwayBeforeItsYesIsWrittenAndIsAskedAgainWhenWrittenAgai
|
||||
// readAskID is the router id of the ask at key in the pass's asks.
|
||||
func readAskID(t *testing.T, p *passer, key string) string {
|
||||
t.Helper()
|
||||
d, err := decodeAsk(key, p.w.value(buckets.Asks, key))
|
||||
d, err := decodeAsk(key, p.w.value(buckets.Asks(), key))
|
||||
if err != nil {
|
||||
t.Fatalf("%s: %v", key, err)
|
||||
}
|
||||
|
||||
@@ -39,7 +39,6 @@ import (
|
||||
|
||||
const (
|
||||
pinsState = "pins"
|
||||
pinsBucket = assignment.Module + "_" + pinsState
|
||||
pinsTool = "assigner_pins"
|
||||
unpinTool = "assigner_unpin"
|
||||
pinsMadeKey = "made"
|
||||
@@ -177,11 +176,11 @@ func (k *pinKeeper) missing(revision int64, record string) {
|
||||
k.logf("the revision %d the record %s pins is not held, and no finding writer is wired to say so", revision, record)
|
||||
return
|
||||
}
|
||||
o := assign.Observed{Scope: assignment.Module, ID: strconv.FormatInt(revision, 10), Kind: pinMissingKind,
|
||||
o := assign.Observed{Scope: assignment.Module(), ID: strconv.FormatInt(revision, 10), Kind: pinMissingKind,
|
||||
Summary: fmt.Sprintf("the delivery record %s pins the assignment graph revision %d, which the assigner no longer holds", record, revision),
|
||||
Headline: "A delivery record pins a revision the assigner no longer holds",
|
||||
Explanation: fmt.Sprintf("The record %s pinned revision %d after it was removed; %s clears it.", record, revision, unpinTool),
|
||||
Source: assignment.Module}
|
||||
Source: assignment.Module()}
|
||||
if err := k.findings.Put(o.Finding(k.now())); err != nil {
|
||||
k.logf("the finding %s of revision %d is not written: %v", pinMissingKind, revision, err)
|
||||
}
|
||||
@@ -536,3 +535,5 @@ func inUseOf(w *world) func(module, fp string) bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func pinsBucket() string { return assignment.Module() + "_" + pinsState }
|
||||
|
||||
@@ -190,7 +190,7 @@ func TestAPassWritesNoWantWithoutAPassedPipeline(t *testing.T) {
|
||||
want bool
|
||||
}{{"the trunk-only want", false, false, true}, {"no pipeline passed", true, false, false}, {"a pipeline passed", true, true, true}} {
|
||||
wantsKV := &memKV{values: map[string][]byte{}}
|
||||
p := newPasser(kvSet{buckets.Wants: wantsKV, buckets.Asks: &memKV{values: map[string][]byte{}}}, time.Now())
|
||||
p := newPasser(kvSet{buckets.Wants(): wantsKV, buckets.Asks(): &memKV{values: map[string][]byte{}}}, time.Now())
|
||||
p.say = func(oc.Condition) error { return nil }
|
||||
mdigest := strings.Repeat("e", 64)
|
||||
mod, _ := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Fp2: fp, Manifest: mdigest, Repository: "novox/web", Commit: strings.Repeat("1", 40), Path: meshcatalogclient.RootPath})
|
||||
@@ -264,7 +264,7 @@ func TestTheWantsSwitchOnTheFirstRegistration(t *testing.T) {
|
||||
func TestACommitOnBetaMovesOnlyTheNodeThatChoseBeta(t *testing.T) {
|
||||
fp := func(c string) string { return meshcatalogclient.SourcePrefix2 + strings.Repeat(c, 64) }
|
||||
wantsKV := &memKV{values: map[string][]byte{}}
|
||||
p := newPasser(kvSet{buckets.Wants: wantsKV, buckets.Asks: &memKV{values: map[string][]byte{}}}, time.Now())
|
||||
p := newPasser(kvSet{buckets.Wants(): wantsKV, buckets.Asks(): &memKV{values: map[string][]byte{}}}, time.Now())
|
||||
p.say = func(oc.Condition) error { return nil }
|
||||
p.ends = newPipelineEnds(newMemEnds(), func() {}, t.Logf)
|
||||
p.ends.trunk = p.w.trunkName
|
||||
@@ -282,7 +282,7 @@ func TestACommitOnBetaMovesOnlyTheNodeThatChoseBeta(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for key, raw := range values {
|
||||
p.w.put(plc.AssignmentsBucket, key, raw)
|
||||
p.w.put(plc.AssignmentsBucket(), key, raw)
|
||||
}
|
||||
}
|
||||
// publish makes the half of branch name web at fingerprint f: built, granted and passed by its pipeline.
|
||||
|
||||
@@ -20,7 +20,7 @@ const placementBy = "the placement"
|
||||
// `assigner_puts` and released with `assigner_release` like any other; a placement's build the builder never built is refused
|
||||
// by its build step, said in the put's words. A put asked by a person stands until the placement names another build.
|
||||
func (p *passer) placementPuts(ctx context.Context) {
|
||||
kv := p.kvs[buckets.Puts]
|
||||
kv := p.kvs[buckets.Puts()]
|
||||
if kv == nil {
|
||||
return
|
||||
}
|
||||
@@ -36,7 +36,7 @@ func (p *passer) placementPuts(ctx context.Context) {
|
||||
}
|
||||
}
|
||||
}
|
||||
held := p.w.read(buckets.Puts)
|
||||
held := p.w.read(buckets.Puts())
|
||||
keys := make([]string, 0, len(named)+len(held))
|
||||
for k := range named {
|
||||
keys = append(keys, k)
|
||||
@@ -86,7 +86,7 @@ func (p *passer) placementPuts(ctx context.Context) {
|
||||
p.logf("the placement's put %s could not be written, and is written at the next pass: %v", key, err)
|
||||
continue
|
||||
}
|
||||
p.w.put(buckets.Puts, key, data)
|
||||
p.w.put(buckets.Puts(), key, data)
|
||||
p.logf("put %s: %s: %s", key, next.Step, next.Words)
|
||||
if p.acts != nil {
|
||||
p.acts.say(key, next)
|
||||
|
||||
@@ -83,7 +83,7 @@ func identity(key string, p puts.Put) string {
|
||||
// putsByModule is every put the assigner holds, by module then node.
|
||||
func (p *passer) putsByModule() map[string]map[string]puts.Put {
|
||||
out := map[string]map[string]puts.Put{}
|
||||
for key, value := range p.w.read(buckets.Puts) {
|
||||
for key, value := range p.w.read(buckets.Puts()) {
|
||||
node, module, err := puts.ParseKey(key)
|
||||
if err != nil {
|
||||
continue
|
||||
@@ -192,7 +192,7 @@ func (p *passer) putWorld(node, module string, put puts.Put, manifest catalogue.
|
||||
w.Composed, w.Granted = p.composedFor(node, module, put.FP)
|
||||
if d := w.Composed; d != nil {
|
||||
policy := d.Policy
|
||||
chosen, _ := assign.DecodePolicies(p.w.read(assign.PoliciesBucket))
|
||||
chosen, _ := assign.DecodePolicies(p.w.read(assign.PoliciesBucket()))
|
||||
if c, ok := chosen[module]; ok {
|
||||
policy, _, _, _ = catalogue.Upgrade(manifest, &c) // a person's choice at the assigner (ADR 0565 §7)
|
||||
} else if policy == "" {
|
||||
@@ -201,7 +201,7 @@ func (p *passer) putWorld(node, module string, put puts.Put, manifest catalogue.
|
||||
w.Recorded = policy == catalogue.PolicyRecord
|
||||
if akey, err := asks.RecordAskKey(node, module, d.Digest); err == nil && put.Ask == akey {
|
||||
// Only this put's own ask counts: an ask (and its answer) of an earlier put under the same key is another asking.
|
||||
if a, ask, err := assign.DecodeAsk(akey, p.w.value(buckets.Asks, akey)); err == nil && a.Asking == assign.PutAsking(put) {
|
||||
if a, ask, err := assign.DecodeAsk(akey, p.w.value(buckets.Asks(), akey)); err == nil && a.Asking == assign.PutAsking(put) {
|
||||
switch {
|
||||
case p.answeredWith(akey, assign.OptionYes):
|
||||
w.Answer = assign.OptionYes
|
||||
@@ -252,13 +252,13 @@ func (p *passer) actOn(ctx context.Context, node, module, id string, w assign.Pu
|
||||
// The put's own ask, written again over one an earlier put left under the key (its asking differs), so the teller
|
||||
// raises it anew under its own id.
|
||||
akey := next.Put.Ask
|
||||
current, _, err := assign.DecodeAsk(akey, p.w.value(buckets.Asks, akey))
|
||||
current, _, err := assign.DecodeAsk(akey, p.w.value(buckets.Asks(), akey))
|
||||
if err != nil || current.Asking != assign.PutAsking(next.Put) {
|
||||
a, err := assign.PutAsk(akey, node, module, next.Put, *w.Composed, w.Bus, w.Now)
|
||||
if err != nil {
|
||||
p.logf("the ask about putting %s on %s is not written: %v", module, node, err)
|
||||
} else {
|
||||
p.put(ctx, buckets.Asks, akey, func() ([]byte, error) { return assign.EncodeAsk(akey, a) })
|
||||
p.put(ctx, buckets.Asks(), akey, func() ([]byte, error) { return assign.EncodeAsk(akey, a) })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -269,7 +269,7 @@ func (p *passer) actOn(ctx context.Context, node, module, id string, w assign.Pu
|
||||
|
||||
// performs checks that the act about to be performed is the one the yes of the ask at key bound (occ.Performs).
|
||||
func (p *passer) performs(key string, act occ.Act) error {
|
||||
_, ask, err := assign.DecodeAsk(key, p.w.value(buckets.Asks, key))
|
||||
_, ask, err := assign.DecodeAsk(key, p.w.value(buckets.Asks(), key))
|
||||
if err != nil {
|
||||
return fmt.Errorf("the ask %s cannot be read: %w", key, err)
|
||||
}
|
||||
@@ -289,7 +289,7 @@ func samePut(a, b puts.Put) bool {
|
||||
// writePut writes a put's next value over the one the pass read, by compare-and-set, unless the tool has put again or
|
||||
// released it since; and says it as the event put. It says whether it wrote.
|
||||
func (p *passer) writePut(ctx context.Context, key string, read, next puts.Put) bool {
|
||||
kv := p.kvs[buckets.Puts]
|
||||
kv := p.kvs[buckets.Puts()]
|
||||
if kv == nil {
|
||||
return false
|
||||
}
|
||||
@@ -313,7 +313,7 @@ func (p *passer) writePut(ctx context.Context, key string, read, next puts.Put)
|
||||
}
|
||||
return false
|
||||
}
|
||||
p.w.put(buckets.Puts, key, data)
|
||||
p.w.put(buckets.Puts(), key, data)
|
||||
p.logf("put %s: %s: %s", key, next.Step, next.Words)
|
||||
p.acts.say(key, next)
|
||||
return true
|
||||
|
||||
@@ -36,7 +36,7 @@ func putWaits(p puts.Put) bool {
|
||||
// putWaitBody is the wait of the put p of module on node.
|
||||
func putWaitBody(node, module string, p puts.Put) meshwants.Wait {
|
||||
since := p.StandsSince().UTC()
|
||||
return meshwants.Wait{Step: meshwants.WaitStepPut, Owner: assignment.Module, Node: node, Module: module, Fingerprint: p.FP,
|
||||
return meshwants.Wait{Step: meshwants.WaitStepPut, Owner: assignment.Module(), Node: node, Module: module, Fingerprint: p.FP,
|
||||
Since: since, ClockFrom: &since, Bound: putWaitBound.String(),
|
||||
Words: fmt.Sprintf("the put of %s at %s on %s waits for %s to run it", module, shortFP(p.FP), node, node)}
|
||||
}
|
||||
@@ -51,10 +51,10 @@ type putWaiting struct {
|
||||
// saidPutWaits is every put wait the state put-waits holds, by key; a value that does not decode is said and left.
|
||||
func (p *passer) saidPutWaits() map[string]putWaiting {
|
||||
out := map[string]putWaiting{}
|
||||
for key, value := range p.w.read(buckets.PutWaits) {
|
||||
for key, value := range p.w.read(buckets.PutWaits()) {
|
||||
var w putWaiting
|
||||
if err := json.Unmarshal(value, &w); err != nil || w.ID == "" {
|
||||
p.logf("%s %s holds no put wait, left: %v", buckets.PutWaits, key, err)
|
||||
p.logf("%s %s holds no put wait, left: %v", buckets.PutWaits(), key, err)
|
||||
continue
|
||||
}
|
||||
out[key] = w
|
||||
@@ -66,7 +66,7 @@ func (p *passer) saidPutWaits() map[string]putWaiting {
|
||||
// while this process ran or while the assigner was stopped (A-204), and wait-held of each put that waits and was not said;
|
||||
// one not said, or not kept, is said at the next pass. Without the state no put's wait is said.
|
||||
func (p *passer) putWaits(ctx context.Context, all map[string]map[string]puts.Put) {
|
||||
if p.kvs[buckets.PutWaits] == nil {
|
||||
if p.kvs[buckets.PutWaits()] == nil {
|
||||
return
|
||||
}
|
||||
waiting := p.saidPutWaits()
|
||||
@@ -92,7 +92,7 @@ func (p *passer) putWaits(ctx context.Context, all map[string]map[string]puts.Pu
|
||||
p.logf("the end of the put %s's wait is not said, and is said at the next pass: %v", key, err)
|
||||
continue
|
||||
}
|
||||
p.remove(ctx, buckets.PutWaits, key)
|
||||
p.remove(ctx, buckets.PutWaits(), key)
|
||||
delete(waiting, key)
|
||||
}
|
||||
for module, byNode := range all {
|
||||
@@ -110,7 +110,7 @@ func (p *passer) putWaits(ctx context.Context, all map[string]map[string]puts.Pu
|
||||
continue
|
||||
}
|
||||
kept := putWaiting{ID: id, Put: put}
|
||||
p.put(ctx, buckets.PutWaits, key, func() ([]byte, error) { return json.Marshal(kept) })
|
||||
p.put(ctx, buckets.PutWaits(), key, func() ([]byte, error) { return json.Marshal(kept) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,9 +22,9 @@ func TestAPutWithoutHoldIsSaidAsAWait(t *testing.T) {
|
||||
var events []string
|
||||
var bodies []meshwants.Wait
|
||||
start := func() *passer { // a process's start: a fresh world that hands what the state holds
|
||||
p := newPasser(kvSet{buckets.PutWaits: kept}, at)
|
||||
p := newPasser(kvSet{buckets.PutWaits(): kept}, at)
|
||||
for k, v := range kept.values {
|
||||
p.w.put(buckets.PutWaits, k, v)
|
||||
p.w.put(buckets.PutWaits(), k, v)
|
||||
}
|
||||
p.acts = newPutActs(&fakeBus{calls: make(chan string, 8)}, func() {}, func(string, ...any) {})
|
||||
p.publishWants = func(event string, body any, id string) error {
|
||||
|
||||
@@ -58,7 +58,7 @@ func (p *passer) reverts(ctx context.Context) map[string]bool {
|
||||
}
|
||||
reason := revertReason(have.Detail)
|
||||
if have.Phase != nodeengineclient.PhaseHeld || reason == "" {
|
||||
if have.Fp == want.Fp && p.w.has(buckets.Asks, akey) {
|
||||
if have.Fp == want.Fp && p.w.has(buckets.Asks(), akey) {
|
||||
p.revertFinding(assign.RevertHeldFinding(node, module, "", "", ""), false) // the reverted build runs
|
||||
}
|
||||
continue
|
||||
@@ -68,11 +68,11 @@ func (p *passer) reverts(ctx context.Context) map[string]bool {
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
current, currentAsk, there := assign.DecodeAsk(akey, p.w.read(buckets.Asks)[akey])
|
||||
current, currentAsk, there := assign.DecodeAsk(akey, p.w.read(buckets.Asks())[akey])
|
||||
if there != nil {
|
||||
now := p.clock()
|
||||
a := assign.RevertAsk(akey, node, module, want.Fp, kept, form, have.Detail, 0, now)
|
||||
p.put(ctx, buckets.Asks, akey, func() ([]byte, error) { return assign.EncodeAsk(akey, a) })
|
||||
p.put(ctx, buckets.Asks(), akey, func() ([]byte, error) { return assign.EncodeAsk(akey, a) })
|
||||
p.revertFinding(assign.RevertHeldFinding(node, module, kept, form, reason), true)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ func TestAHeldRevertIsAskedAndALiveAnswerIsWrittenIntoTheWant(t *testing.T) {
|
||||
at := time.Date(2026, 11, 12, 10, 0, 0, 0, time.UTC)
|
||||
fpOld := meshcatalogclient.SourcePrefix2 + strings.Repeat("a", 64)
|
||||
asksKV, wantsKV := &memKV{values: map[string][]byte{}}, &memKV{values: map[string][]byte{}}
|
||||
p := newPasser(kvSet{buckets.Asks: asksKV, buckets.Wants: wantsKV}, at)
|
||||
p := newPasser(kvSet{buckets.Asks(): asksKV, buckets.Wants(): wantsKV}, at)
|
||||
p.logf = t.Logf
|
||||
findings := map[string][]byte{}
|
||||
p.findings = assign.Findings{Put: putInto(findings), Delete: func(k string) error { delete(findings, k); return nil }}
|
||||
@@ -63,7 +63,7 @@ func TestAHeldRevertIsAskedAndALiveAnswerIsWrittenIntoTheWant(t *testing.T) {
|
||||
wkey, _ := mwc.WantKey("ace", "web")
|
||||
want := mwc.Want{Fp: fpOld, Self: true}
|
||||
raw, _ := mwc.EncodeWant(want)
|
||||
p.w.put(buckets.Wants, wkey, raw)
|
||||
p.w.put(buckets.Wants(), wkey, raw)
|
||||
hkey, _ := nodeengineclient.HaveKey("ace", "web")
|
||||
have := func(fp, phase, detail string) {
|
||||
raw, err := nodeengineclient.EncodeHave(nodeengine.HaveByNodeModuleValue{Fp: fp, Phase: phase, Detail: detail, Health: nodeengineclient.HealthOK})
|
||||
@@ -86,10 +86,10 @@ func TestAHeldRevertIsAskedAndALiveAnswerIsWrittenIntoTheWant(t *testing.T) {
|
||||
pass()
|
||||
rkey, _ := asks.RevertAskKey("ace", "web", fpOld)
|
||||
bkey, _ := asks.BackupAskKey("ace", "web", fpOld)
|
||||
if !p.w.has(buckets.Asks, rkey) || p.w.has(buckets.Asks, bkey) {
|
||||
if !p.w.has(buckets.Asks(), rkey) || p.w.has(buckets.Asks(), bkey) {
|
||||
t.Fatalf("asks %v: want the revert ask %s and no backup ask", asksKV.values, rkey)
|
||||
}
|
||||
_, ask, err := assign.DecodeAsk(rkey, p.w.read(buckets.Asks)[rkey])
|
||||
_, ask, err := assign.DecodeAsk(rkey, p.w.read(buckets.Asks())[rkey])
|
||||
if err != nil || !strings.Contains(ask.Explanation, "sessions") || !strings.Contains(ask.Explanation, "form 1") {
|
||||
t.Fatalf("the revert ask = %+v, %v", ask, err)
|
||||
}
|
||||
@@ -110,7 +110,7 @@ func TestAHeldRevertIsAskedAndALiveAnswerIsWrittenIntoTheWant(t *testing.T) {
|
||||
}
|
||||
answer(assign.OptionRevertLive)
|
||||
pass()
|
||||
got, err := mwc.DecodeWant(p.w.read(buckets.Wants)[wkey])
|
||||
got, err := mwc.DecodeWant(p.w.read(buckets.Wants())[wkey])
|
||||
if err != nil || got.Fp != fpOld || got.Revert != mwc.RevertLive || got.Words != `the operator answered "live" to the revert ask (asking 1)` {
|
||||
t.Fatalf("the want after live = %+v, %v", got, err)
|
||||
}
|
||||
@@ -128,9 +128,9 @@ func TestAHeldRevertIsAskedAndALiveAnswerIsWrittenIntoTheWant(t *testing.T) {
|
||||
t.Fatal("the finding is not raised again for a hold again")
|
||||
}
|
||||
raw, _ = mwc.EncodeWant(mwc.Want{Fp: fpTest, Self: true})
|
||||
p.w.put(buckets.Wants, wkey, raw)
|
||||
p.w.put(buckets.Wants(), wkey, raw)
|
||||
p.staleAsks(ctx, map[string]meshcatalog.ModulesByModuleValue{"web": {Fp2: fpTest}})
|
||||
if _, raised := findings[fkey]; raised || p.w.has(buckets.Asks, rkey) {
|
||||
t.Fatalf("a newer want left the ask (%v) or the finding (%v)", p.w.has(buckets.Asks, rkey), raised)
|
||||
if _, raised := findings[fkey]; raised || p.w.has(buckets.Asks(), rkey) {
|
||||
t.Fatalf("a newer want left the ask (%v) or the finding (%v)", p.w.has(buckets.Asks(), rkey), raised)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
|
||||
// hearRunning keeps each `running` the stream holds and each it says from now, and wakes the pass on every one.
|
||||
func hearRunning(ctx context.Context, js jetstream.JetStream, w *world, wake func(), findings assign.Findings, logf func(string, ...any)) {
|
||||
acct := process.Account{Node: thisNode, Module: assignment.Module}
|
||||
acct := process.Account{Node: thisNode, Module: assignment.Module()}
|
||||
nh := notHeardWriter(findings, logf)
|
||||
cc, err := acct.HearSeatEvents(ctx, js, nodeengine.StreamRunning, func(m jetstream.Msg) {
|
||||
if err := m.Ack(); err != nil {
|
||||
|
||||
@@ -57,7 +57,7 @@ func seatPublisher(ctx context.Context, js jetstream.JetStream, node string) pub
|
||||
}
|
||||
msg := nats.NewMsg(seatdef.EventSubject(mma.Seat, event))
|
||||
msg.Header.Set("x-event-id", id)
|
||||
msg.Header.Set("x-source", assignment.Module)
|
||||
msg.Header.Set("x-source", assignment.Module())
|
||||
msg.Header.Set("x-node", node)
|
||||
msg.Header.Set("x-time", time.Now().UTC().Format(time.RFC3339Nano))
|
||||
msg.Header.Set("content-type", "application/json")
|
||||
@@ -155,7 +155,7 @@ func (p *passer) seatAssignments() (map[string]mma.Assignments, map[string]uint6
|
||||
func (p *passer) candidatesOf(id string) (map[string]mma.CandidatesByProposalNodeValue, map[string]uint64, time.Time) {
|
||||
out, revs := map[string]mma.CandidatesByProposalNodeValue{}, map[string]uint64{}
|
||||
var newest time.Time
|
||||
for key, value := range p.w.read(plc.CandidatesBucket) {
|
||||
for key, value := range p.w.read(plc.CandidatesBucket()) {
|
||||
pid, node, err := plc.ParseCandidateKey(key)
|
||||
if err != nil || pid != id {
|
||||
continue
|
||||
@@ -164,7 +164,7 @@ func (p *passer) candidatesOf(id string) (map[string]mma.CandidatesByProposalNod
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
meta := p.w.metaOf(plc.CandidatesBucket, key)
|
||||
meta := p.w.metaOf(plc.CandidatesBucket(), key)
|
||||
out[node], revs[node] = c, meta.revision
|
||||
if meta.at.After(newest) {
|
||||
newest = meta.at
|
||||
@@ -194,7 +194,7 @@ func (p *passer) answersTo(id string) map[string]*nodeengine.CandidateAnswer {
|
||||
|
||||
// assignmentsSeat takes every open proposal one step further.
|
||||
func (p *passer) assignmentsSeat(ctx context.Context) {
|
||||
for _, b := range seatBuckets {
|
||||
for _, b := range seatBuckets() {
|
||||
if p.kvs[b] == nil {
|
||||
return
|
||||
}
|
||||
@@ -207,7 +207,7 @@ func (p *passer) assignmentsSeat(ctx context.Context) {
|
||||
}
|
||||
current, revs := p.seatAssignments()
|
||||
ids := make([]string, 0)
|
||||
raws := p.w.read(plc.ProposalsBucket)
|
||||
raws := p.w.read(plc.ProposalsBucket())
|
||||
for id := range raws {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
@@ -218,32 +218,32 @@ func (p *passer) assignmentsSeat(ctx context.Context) {
|
||||
p.logf("the proposal %s cannot be read, so it is left: %v", id, err)
|
||||
continue
|
||||
}
|
||||
p.stepProposal(ctx, prop, p.w.metaOf(plc.ProposalsBucket, id).revision, current, revs)
|
||||
p.stepProposal(ctx, prop, p.w.metaOf(plc.ProposalsBucket(), id).revision, current, revs)
|
||||
}
|
||||
}
|
||||
|
||||
// pulse writes a bound's key with its time to live (§4): its expiry, heard on the watch as the server's delete marker,
|
||||
// is the event that ends the wait. Written again over one that stands, so its time starts anew.
|
||||
func (p *passer) pulse(ctx context.Context, key string, ttl time.Duration) {
|
||||
kv := p.kvs[plc.PulsesBucket]
|
||||
kv := p.kvs[plc.PulsesBucket()]
|
||||
_ = kv.Delete(ctx, key)
|
||||
rev, err := kv.Create(ctx, key, []byte(`{}`), jetstream.KeyTTL(ttl))
|
||||
if err != nil {
|
||||
p.logf("the bound %s is not written, so its wait ends at once: %v", key, err)
|
||||
return
|
||||
}
|
||||
p.w.putEntry(plc.PulsesBucket, key, []byte(`{}`), rev, p.clock())
|
||||
p.w.putEntry(plc.PulsesBucket(), key, []byte(`{}`), rev, p.clock())
|
||||
}
|
||||
|
||||
// pulsing says whether a bound's key stands.
|
||||
func (p *passer) pulsing(key string) bool { return p.w.has(plc.PulsesBucket, key) }
|
||||
func (p *passer) pulsing(key string) bool { return p.w.has(plc.PulsesBucket(), key) }
|
||||
|
||||
// deletePulses deletes every bound of a proposal.
|
||||
func (p *passer) deletePulses(ctx context.Context, id string) {
|
||||
for key := range p.w.read(plc.PulsesBucket) {
|
||||
for key := range p.w.read(plc.PulsesBucket()) {
|
||||
if strings.HasSuffix(key, "."+id) || strings.Contains(key, "."+id+".") {
|
||||
if err := p.kvs[plc.PulsesBucket].Delete(ctx, key); err == nil {
|
||||
p.w.put(plc.PulsesBucket, key, nil)
|
||||
if err := p.kvs[plc.PulsesBucket()].Delete(ctx, key); err == nil {
|
||||
p.w.put(plc.PulsesBucket(), key, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -261,7 +261,7 @@ func (p *passer) ask(ctx context.Context, prop mma.Proposal, akey, brief string)
|
||||
p.logf("the ask about the proposal %s is not written: %v", prop.ID, err)
|
||||
return prop, false
|
||||
}
|
||||
p.put(ctx, buckets.Asks, akey, func() ([]byte, error) { return assign.EncodeAsk(akey, a) })
|
||||
p.put(ctx, buckets.Asks(), akey, func() ([]byte, error) { return assign.EncodeAsk(akey, a) })
|
||||
if pk, err := plc.PulseAskKey(prop.ID); err == nil {
|
||||
p.pulse(ctx, pk, assign.ExpiresAt(placed).Sub(p.clock()))
|
||||
}
|
||||
@@ -423,7 +423,7 @@ func (p *passer) mustAnswer(ctx context.Context, prop mma.Proposal, rev uint64,
|
||||
if pl, ok := current[node]; ok {
|
||||
c.Assignments = &pl
|
||||
}
|
||||
p.put(ctx, plc.CandidatesBucket, key, func() ([]byte, error) { return plc.EncodeCandidate(c) })
|
||||
p.put(ctx, plc.CandidatesBucket(), key, func() ([]byte, error) { return plc.EncodeCandidate(c) })
|
||||
}
|
||||
for _, node := range must {
|
||||
if k, err := plc.PulseAnswerKey(prop.ID, node); err == nil {
|
||||
@@ -454,7 +454,7 @@ func (p *passer) layAgain(ctx context.Context, prop mma.Proposal, rev uint64, cu
|
||||
for _, node := range plc.Nodes(again) {
|
||||
key, _ := plc.CandidateKey(prop.ID, node)
|
||||
c := mma.CandidatesByProposalNodeValue{Proposal: prop.ID, Assignments: laid[node]}
|
||||
p.put(ctx, plc.CandidatesBucket, key, func() ([]byte, error) { return plc.EncodeCandidate(c) })
|
||||
p.put(ctx, plc.CandidatesBucket(), key, func() ([]byte, error) { return plc.EncodeCandidate(c) })
|
||||
}
|
||||
again.Written, again.Words = ptr(p.clock().UTC()), why+": laid again over the keys as they stand, to be judged and asked again"
|
||||
p.writeProposal(ctx, again, rev)
|
||||
@@ -527,8 +527,8 @@ func (p *passer) warranted(ctx context.Context, prop mma.Proposal, rev uint64, a
|
||||
}
|
||||
p.deleteCandidates(ctx, prop.ID)
|
||||
p.deletePulses(ctx, prop.ID)
|
||||
_ = p.kvs[plc.ProposalsBucket].Delete(ctx, prop.ID)
|
||||
p.w.put(plc.ProposalsBucket, prop.ID, nil)
|
||||
_ = p.kvs[plc.ProposalsBucket()].Delete(ctx, prop.ID)
|
||||
p.w.put(plc.ProposalsBucket(), prop.ID, nil)
|
||||
p.followWrite(prop, answer, changes)
|
||||
}
|
||||
|
||||
@@ -563,8 +563,8 @@ func (p *passer) addNode(ctx context.Context, prop mma.Proposal, revs map[string
|
||||
c.Before, c.After = uint64(prop.Base[node]), rev
|
||||
p.deleteCandidates(ctx, prop.ID)
|
||||
p.deletePulses(ctx, prop.ID)
|
||||
_ = p.kvs[plc.ProposalsBucket].Delete(ctx, prop.ID)
|
||||
p.w.put(plc.ProposalsBucket, prop.ID, nil)
|
||||
_ = p.kvs[plc.ProposalsBucket()].Delete(ctx, prop.ID)
|
||||
p.w.put(plc.ProposalsBucket(), prop.ID, nil)
|
||||
w := prop.Warrant
|
||||
p.sayChanged(prop, mdc.Answer{Option: assign.OptionYes, Warrant: w.Warrant,
|
||||
Words: fmt.Sprintf("the operator approved the machine %s at %s through the enrolment's ask %s", w.Node, w.Address, w.Ask)},
|
||||
@@ -590,8 +590,8 @@ func (p *passer) removeNode(ctx context.Context, prop mma.Proposal, current map[
|
||||
c.Before, c.After = uint64(prop.Base[node]), rev
|
||||
p.deleteCandidates(ctx, prop.ID)
|
||||
p.deletePulses(ctx, prop.ID)
|
||||
_ = p.kvs[plc.ProposalsBucket].Delete(ctx, prop.ID)
|
||||
p.w.put(plc.ProposalsBucket, prop.ID, nil)
|
||||
_ = p.kvs[plc.ProposalsBucket()].Delete(ctx, prop.ID)
|
||||
p.w.put(plc.ProposalsBucket(), prop.ID, nil)
|
||||
w := prop.Warrant
|
||||
p.sayChanged(prop, mdc.Answer{Option: assign.OptionYes, Warrant: w.Warrant,
|
||||
Words: fmt.Sprintf("the machine %s left assignments: its enrolment (ask %s) ended before it confirmed", w.Node, w.Ask)},
|
||||
@@ -636,28 +636,28 @@ func (p *passer) sayChanged(prop mma.Proposal, answer mdc.Answer, changes []plc.
|
||||
// endProposal ends a proposal: its ask, its candidates and its key deleted, and assignments-withdrawn said carrying it
|
||||
// whole with why it ended.
|
||||
func (p *passer) endProposal(ctx context.Context, prop mma.Proposal, why string) {
|
||||
if akey, err := asks.AssignmentsAskKey(prop.ID); err == nil && p.w.has(buckets.Asks, akey) {
|
||||
if err := p.kvs[buckets.Asks].Delete(ctx, akey); err == nil {
|
||||
p.w.put(buckets.Asks, akey, nil)
|
||||
if akey, err := asks.AssignmentsAskKey(prop.ID); err == nil && p.w.has(buckets.Asks(), akey) {
|
||||
if err := p.kvs[buckets.Asks()].Delete(ctx, akey); err == nil {
|
||||
p.w.put(buckets.Asks(), akey, nil)
|
||||
}
|
||||
}
|
||||
p.deleteCandidates(ctx, prop.ID)
|
||||
p.deletePulses(ctx, prop.ID)
|
||||
if err := p.kvs[plc.ProposalsBucket].Delete(ctx, prop.ID); err != nil && !errors.Is(err, context.Canceled) {
|
||||
if err := p.kvs[plc.ProposalsBucket()].Delete(ctx, prop.ID); err != nil && !errors.Is(err, context.Canceled) {
|
||||
p.logf("the proposal %s is not deleted, and is ended at the next pass: %v", prop.ID, err)
|
||||
return
|
||||
}
|
||||
p.w.put(plc.ProposalsBucket, prop.ID, nil)
|
||||
p.w.put(plc.ProposalsBucket(), prop.ID, nil)
|
||||
p.logf("the proposal %s ends: %s", prop.ID, why)
|
||||
p.sayEvent(mma.EventAssignmentsWithdrawn, mma.AssignmentsEvent{Proposal: prop, Ended: why}, prop.ID+".withdrawn")
|
||||
}
|
||||
|
||||
// deleteCandidates deletes every candidate of a proposal.
|
||||
func (p *passer) deleteCandidates(ctx context.Context, id string) {
|
||||
for key := range p.w.read(plc.CandidatesBucket) {
|
||||
for key := range p.w.read(plc.CandidatesBucket()) {
|
||||
if pid, _, err := plc.ParseCandidateKey(key); err == nil && pid == id {
|
||||
if err := p.kvs[plc.CandidatesBucket].Delete(ctx, key); err == nil {
|
||||
p.w.put(plc.CandidatesBucket, key, nil)
|
||||
if err := p.kvs[plc.CandidatesBucket()].Delete(ctx, key); err == nil {
|
||||
p.w.put(plc.CandidatesBucket(), key, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -671,7 +671,7 @@ func (p *passer) writeProposal(ctx context.Context, prop mma.Proposal, rev uint6
|
||||
p.logf("the proposal %s is not written: %v", prop.ID, err)
|
||||
return false
|
||||
}
|
||||
kv := p.kvs[plc.ProposalsBucket]
|
||||
kv := p.kvs[plc.ProposalsBucket()]
|
||||
var at uint64
|
||||
if rev == 0 {
|
||||
at, err = kv.Create(ctx, prop.ID, raw)
|
||||
@@ -684,7 +684,7 @@ func (p *passer) writeProposal(ctx context.Context, prop mma.Proposal, rev uint6
|
||||
}
|
||||
return false
|
||||
}
|
||||
p.w.putEntry(plc.ProposalsBucket, prop.ID, raw, at, p.clock())
|
||||
p.w.putEntry(plc.ProposalsBucket(), prop.ID, raw, at, p.clock())
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -710,7 +710,7 @@ func (p *passer) writeNode(ctx context.Context, node string, after *mma.Assignme
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
kv := p.kvs[plc.AssignmentsBucket]
|
||||
kv := p.kvs[plc.AssignmentsBucket()]
|
||||
committed, rev := false, uint64(0)
|
||||
for _, w := range writes {
|
||||
got, err := writeKey(ctx, kv, w)
|
||||
@@ -721,7 +721,7 @@ func (p *passer) writeNode(ctx context.Context, node string, after *mma.Assignme
|
||||
}
|
||||
return 0, fmt.Errorf("assignments %s at revision %d: %w", w.Key, w.Base, err)
|
||||
}
|
||||
p.w.putEntry(plc.AssignmentsBucket, w.Key, w.Value, got, p.clock())
|
||||
p.w.putEntry(plc.AssignmentsBucket(), w.Key, w.Value, got, p.clock())
|
||||
if w.Commit {
|
||||
committed, rev = true, got
|
||||
}
|
||||
|
||||
@@ -318,7 +318,7 @@ func TestTheSeatsVerbs(t *testing.T) {
|
||||
kv := &countingKV{memKV: memKV{values: map[string][]byte{}}, writes: &writes}
|
||||
// Issue 1966: nothing is written while the seat list is unread (ADR 0636 §1), and every seat the list names is no
|
||||
// seat outside it once read.
|
||||
p := &passer{w: newWorld(), logf: t.Logf, kvs: kvSet{plc.SeatHoldersBucket: kv}, seats: gateOver(t, true)}
|
||||
p := &passer{w: newWorld(), logf: t.Logf, kvs: kvSet{plc.SeatHoldersBucket(): kv}, seats: gateOver(t, true)}
|
||||
place := func(node string, holds ...mma.Holding) {
|
||||
putAssignments(p.w, mma.Assignments{V: keys.Version, Node: node, Modules: []mma.PlacedModule{{Module: "web"}, {Module: "nats"}}, Holds: holds})
|
||||
}
|
||||
@@ -355,7 +355,7 @@ func TestTheSeatsVerbs(t *testing.T) {
|
||||
t.Run("research 072 row 37: a node is written as its keys, and history reads the node key", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
kv := &revKV{values: map[string][]byte{}, revs: map[string]uint64{}}
|
||||
p := &passer{w: newWorld(), kvs: kvSet{plc.AssignmentsBucket: kv}, now: time.Now, logf: t.Logf}
|
||||
p := &passer{w: newWorld(), kvs: kvSet{plc.AssignmentsBucket(): kv}, now: time.Now, logf: t.Logf}
|
||||
first := mma.Assignments{Node: "anchor", Modules: []mma.PlacedModule{{Module: "web"}}}
|
||||
if _, err := p.writeNode(ctx, "anchor", &first); err != nil || strings.Join(kv.log, " ") != "create anchor.web create anchor.node" {
|
||||
t.Fatalf("the first write = %v, %v", kv.log, err)
|
||||
@@ -447,7 +447,7 @@ func TestTheSeatsVerbs(t *testing.T) {
|
||||
}
|
||||
var logs []string
|
||||
bus := &emitted{}
|
||||
p := &passer{w: newWorld(), kvs: kvSet{retiredBucket: old, plc.AssignmentsBucket: next, copiesBucket: copies}, now: time.Now, seat: &seatActs{bus: bus},
|
||||
p := &passer{w: newWorld(), kvs: kvSet{retiredBucket(): old, plc.AssignmentsBucket(): next, copiesBucket(): copies}, now: time.Now, seat: &seatActs{bus: bus},
|
||||
logf: func(f string, a ...any) { logs = append(logs, fmt.Sprintf(f, a...)) }}
|
||||
if !p.copyRetired(ctx) || !p.copied {
|
||||
t.Fatalf("the copy did not end: %v", logs)
|
||||
@@ -459,7 +459,7 @@ func TestTheSeatsVerbs(t *testing.T) {
|
||||
t.Errorf("assignments = %v, want %v", next.values, c.wantValues)
|
||||
}
|
||||
if _, ok := copies.values[copiedMarker]; !ok {
|
||||
t.Errorf("the end of the copy is not marked in %s: %v", copiesBucket, copies.values)
|
||||
t.Errorf("the end of the copy is not marked in %s: %v", copiesBucket(), copies.values)
|
||||
}
|
||||
if !maps.EqualFunc(old.values, c.old, slices.Equal[[]byte]) || old.deletes != 0 {
|
||||
t.Errorf("the retired state changed: %v, %d deletes; the readers delivered before still read it", old.values, old.deletes)
|
||||
@@ -480,7 +480,7 @@ func TestTheSeatsVerbs(t *testing.T) {
|
||||
if !p.copyRetired(ctx) || len(next.log) != before || len(bus.events) > 1 {
|
||||
t.Errorf("a second copy wrote %v, said %v", next.log[before:], bus.events)
|
||||
}
|
||||
if got := len(p.w.read(plc.AssignmentsBucket)); got != c.said.Copied {
|
||||
if got := len(p.w.read(plc.AssignmentsBucket())); got != c.said.Copied {
|
||||
t.Errorf("%d copied keys are in the world, want %d", got, c.said.Copied)
|
||||
}
|
||||
})
|
||||
@@ -511,7 +511,7 @@ func TestTheSeatsVerbs(t *testing.T) {
|
||||
}
|
||||
// A required provision with no key: pulsed with the bound first, said once the pulse expired, ended once keyed.
|
||||
ctx := context.Background()
|
||||
p.kvs[plc.PulsesBucket] = &memKV{values: map[string][]byte{}}
|
||||
p.kvs[plc.PulsesBucket()] = &memKV{values: map[string][]byte{}}
|
||||
gone := map[string]bool{"ace.photos.s3": true}
|
||||
p.provisionsMissing(ctx, gone)
|
||||
if !p.pulsing("provision-missing.ace.photos.s3") || len(findings) != 0 {
|
||||
@@ -521,7 +521,7 @@ func TestTheSeatsVerbs(t *testing.T) {
|
||||
if len(findings) != 0 {
|
||||
t.Fatal("said before its bound")
|
||||
}
|
||||
p.w.put(plc.PulsesBucket, "provision-missing.ace.photos.s3", nil) // the server expired it
|
||||
p.w.put(plc.PulsesBucket(), "provision-missing.ace.photos.s3", nil) // the server expired it
|
||||
p.provisionsMissing(ctx, gone)
|
||||
if _, ok := findings[assign.ProvisionMissingKey("ace.photos.s3")]; !ok {
|
||||
t.Fatalf("past its bound: findings %v", findings)
|
||||
@@ -816,7 +816,7 @@ func putAssignments(w *world, a mma.Assignments) {
|
||||
panic(err)
|
||||
}
|
||||
for _, k := range slices.Sorted(maps.Keys(values)) {
|
||||
w.putEntry(plc.AssignmentsBucket, k, values[k], 1, time.Now())
|
||||
w.putEntry(plc.AssignmentsBucket(), k, values[k], 1, time.Now())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
mma "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||
plc "mesh-delivery-assigner/internal/assignment"
|
||||
|
||||
"encoding/json"
|
||||
"errors"
|
||||
@@ -385,6 +386,7 @@ func (runtimeStore) keys(state string) ([]string, error) { return stdio.State(st
|
||||
|
||||
// serveTools serves the tools to the runtime until it stops, hearing every put to answer assigner_put with its first steps.
|
||||
func serveTools() error {
|
||||
plc.SetModule(os.Getenv(stdio.ServedAs)) // the module the runtime serves these tools as (A-296)
|
||||
s := newPutTools(runtimeStore{}, stdio.Ask)
|
||||
go func() {
|
||||
<-stdio.Served()
|
||||
|
||||
@@ -29,7 +29,7 @@ import (
|
||||
)
|
||||
|
||||
// marksBucket is the assigner's state of what its views family said each state up to.
|
||||
var marksBucket = assignment.Module + "_" + views.MarksState
|
||||
func marksBucket() string { return assignment.Module() + "_" + views.MarksState }
|
||||
|
||||
// viewedTool is one tool over one state of the assigner's.
|
||||
type viewedTool struct{ tool, state, words string }
|
||||
@@ -56,13 +56,13 @@ var readTools = []viewedTool{
|
||||
|
||||
// viewedStates are the states the views family says, each by its bucket: the six its readers read.
|
||||
var viewedStates = map[string]string{
|
||||
mma.StateAssignments: plc.AssignmentsBucket,
|
||||
mma.StateSeatHolders: plc.SeatHoldersBucket,
|
||||
"wants": buckets.Wants,
|
||||
assign.FindingsState: assign.FindingsBucket,
|
||||
"asks": buckets.Asks,
|
||||
"differences": buckets.Differences,
|
||||
copiesState: copiesBucket,
|
||||
mma.StateAssignments: plc.AssignmentsBucket(),
|
||||
mma.StateSeatHolders: plc.SeatHoldersBucket(),
|
||||
"wants": buckets.Wants(),
|
||||
assign.FindingsState: assign.FindingsBucket(),
|
||||
"asks": buckets.Asks(),
|
||||
"differences": buckets.Differences(),
|
||||
copiesState: copiesBucket(),
|
||||
}
|
||||
|
||||
// viewTools are the assigner's first-view and read tools, reading only its own states, so they answer as soon as its
|
||||
@@ -71,13 +71,13 @@ func viewTools(getenv func(string) string, state func(string) views.Source, now
|
||||
marks := state(views.MarksState)
|
||||
var out []stdio.Tool
|
||||
for _, t := range firstViewTools {
|
||||
out = append(out, views.FirstViewTool(assignment.Module, t.tool, t.state, t.words, views.ParseCallers(getenv(views.CallersEnv(t.tool))), state(t.state), marks, now))
|
||||
out = append(out, views.FirstViewTool(assignment.Module(), t.tool, t.state, t.words, views.ParseCallers(getenv(views.CallersEnv(t.tool))), state(t.state), marks, now))
|
||||
}
|
||||
for _, t := range readTools {
|
||||
out = append(out, views.ReadTool(assignment.Module, t.tool, t.state, t.words, state(t.state), marks, now))
|
||||
out = append(out, views.ReadTool(assignment.Module(), t.tool, t.state, t.words, state(t.state), marks, now))
|
||||
}
|
||||
// Its own first views, of the indexer's manifests, modules and graph (DLV-F3r, ADR 0631 §2): read, and asked again by hand.
|
||||
return append(out, views.StatusTools(assignment.Module, "assigner_first_views", "assigner_first_view_again", stdio.State(views.StatusState), meshhandactsclient.Recorder{Ask: stdio.Ask}, now)...)
|
||||
return append(out, views.StatusTools(assignment.Module(), "assigner_first_views", "assigner_first_view_again", stdio.State(views.StatusState), meshhandactsclient.Recorder{Ask: stdio.Ask}, now)...)
|
||||
}
|
||||
|
||||
func keptSource(name string) views.Source { return views.KeptSource{State: stdio.State(name)} }
|
||||
@@ -85,7 +85,7 @@ func keptSource(name string) views.Source { return views.KeptSource{State: stdio
|
||||
// startViews says each change of the assigner's viewed states on its views family, once its marks state is bound,
|
||||
// each state as soon as its bucket is (kvs, or bound once it appears). A watch that ends is sent on errs.
|
||||
func startViews(ctx context.Context, js jetstream.JetStream, nc process.Subscriber, account process.Account, kvs kvSet, writer views.WireWriter, errs chan<- error, logf func(string, ...any)) {
|
||||
process.AwaitBucket(ctx, js, nc, marksBucket, "no change is said on the views family, so every reader waits on its first view", logf).Ready(func(mk jetstream.KeyValue) {
|
||||
process.AwaitBucket(ctx, js, nc, marksBucket(), "no change is said on the views family, so every reader waits on its first view", logf).Ready(func(mk jetstream.KeyValue) {
|
||||
em := viewsEmitter(js, account, mk, writer, logf)
|
||||
for _, state := range []string{"asks", "differences", assign.FindingsState, mma.StateAssignments, mma.StateSeatHolders, "wants", copiesState} {
|
||||
state := state
|
||||
@@ -106,11 +106,11 @@ func startViews(ctx context.Context, js jetstream.JetStream, nc process.Subscrib
|
||||
// viewsEmitter is the assigner's emitter of its views family: a change not said past views.Bound is its finding
|
||||
// view-unsaid, in its findings, written through its finding writer (review round 2); with none it is said in the log only.
|
||||
func viewsEmitter(js jetstream.JetStream, account process.Account, mk jetstream.KeyValue, writer views.WireWriter, logf func(string, ...any)) views.Emitter {
|
||||
return views.Emitter{Owner: assignment.Module, Subject: account.EventSubject, Publish: views.JetStreamPublish(js), Marks: mk, Findings: writer, Logf: logf}
|
||||
return views.Emitter{Owner: assignment.Module(), Subject: account.EventSubject, Publish: views.JetStreamPublish(js), Marks: mk, Findings: writer, Logf: logf}
|
||||
}
|
||||
|
||||
// firstViewsBucket is the assigner's own state of its first views (views.StatusState).
|
||||
var firstViewsBucket = assignment.Module + "_" + views.StatusState
|
||||
func firstViewsBucket() string { return assignment.Module() + "_" + views.StatusState }
|
||||
|
||||
// catalogViews is the assigner's first view of the catalog half of each watched branch (design 47 G §14, §184, §195, A-26): the
|
||||
// module's views reader adopts each branch's follower, so it asks the seat mesh-catalog's verb `revision` (the trunk's, then
|
||||
@@ -142,7 +142,7 @@ var firstViewBound, firstViewWait time.Duration
|
||||
|
||||
// start adopts the trunk's catalog half through the reader and starts the reader; it answers how many settles to wait for.
|
||||
func (iv *catalogViews) start(ctx context.Context, w *world, wake func(), settled chan<- struct{}) int {
|
||||
reader := &views.ProcessReader{Module: assignment.Module, Call: iv.call, Logf: iv.logf, Hub: iv.hub, Bound: firstViewBound, Wait: firstViewWait}
|
||||
reader := &views.ProcessReader{Module: assignment.Module(), Call: iv.call, Logf: iv.logf, Hub: iv.hub, Bound: firstViewBound, Wait: firstViewWait}
|
||||
if iv.writer != nil {
|
||||
reader.Findings = func() views.WireWriter { return iv.writer }
|
||||
}
|
||||
@@ -172,7 +172,7 @@ func (iv *catalogViews) start(ctx context.Context, w *world, wake func(), settle
|
||||
// notHeardWriter writes the assigner's finding of a family it does not hear into its findings (heard.Writer); with no
|
||||
// findings state it is said in the log alone.
|
||||
func notHeardWriter(findings assign.Findings, logf func(string, ...any)) heard.Writer {
|
||||
w := heard.Writer{Observer: assignment.Module, Logf: logf}
|
||||
w := heard.Writer{Observer: assignment.Module(), Logf: logf}
|
||||
if findings.Put != nil {
|
||||
w.Put = func(_ string, value []byte) error {
|
||||
var f mcseat.Finding
|
||||
|
||||
@@ -98,7 +98,7 @@ func (w *world) putEntry(bucket, key string, value []byte, revision uint64, at t
|
||||
return
|
||||
}
|
||||
w.meta[bucket+"|"+key] = entryMeta{revision: revision, at: at}
|
||||
if bucket == plc.AssignmentsBucket {
|
||||
if bucket == plc.AssignmentsBucket() {
|
||||
_, _ = w.assignmentKeys.Put(key, value, revision, false) // put took it already; this is its revision
|
||||
}
|
||||
}
|
||||
@@ -120,14 +120,14 @@ func (w *world) put(bucket, key string, value []byte) {
|
||||
w.changes[bucket]++ // the bucket's generation (bucketGen), a delete included
|
||||
if value == nil {
|
||||
delete(w.values[bucket], key)
|
||||
if bucket == plc.AssignmentsBucket {
|
||||
if bucket == plc.AssignmentsBucket() {
|
||||
_, _ = w.assignmentKeys.Put(key, nil, 0, true)
|
||||
}
|
||||
return
|
||||
}
|
||||
w.values[bucket][key] = value
|
||||
w.changes[bucket+"|"+key]++
|
||||
if bucket == plc.AssignmentsBucket {
|
||||
if bucket == plc.AssignmentsBucket() {
|
||||
// A key or value that does not read places nothing new on its node; the indexer's read tool says it.
|
||||
_, _ = w.assignmentKeys.Put(key, value, 0, false)
|
||||
}
|
||||
@@ -197,7 +197,7 @@ func (w *world) placed() (map[string][]string, bool) {
|
||||
for node, p := range w.assignments() {
|
||||
out[node] = mmac.Assigned(p)
|
||||
}
|
||||
return out, len(w.read(plc.AssignmentsBucket)) > 0
|
||||
return out, len(w.read(plc.AssignmentsBucket())) > 0
|
||||
}
|
||||
|
||||
// assignments is every node's assignments, assembled from its keys in either form (mmac.Keys).
|
||||
@@ -359,7 +359,7 @@ func (w *world) awayNow() map[string]bool {
|
||||
// the seat's verb is heard (novox/hq ADR 0668 §3).
|
||||
func (w *world) seatHeard(seat string) bool {
|
||||
online := w.online()
|
||||
for _, value := range w.read(plc.SeatHoldersBucket) {
|
||||
for _, value := range w.read(plc.SeatHoldersBucket()) {
|
||||
var sh mma.SeatHolders
|
||||
if json.Unmarshal(value, &sh) != nil || sh.Seat != seat {
|
||||
continue
|
||||
@@ -387,7 +387,7 @@ func (w *world) online() map[string]bool {
|
||||
// wantsOf is the wants the assigner wrote, by module then node.
|
||||
func (w *world) wantsOf() map[string]map[string]mwc.Want {
|
||||
out := map[string]map[string]mwc.Want{}
|
||||
for key, value := range w.read(buckets.Wants) {
|
||||
for key, value := range w.read(buckets.Wants()) {
|
||||
node, module, err := mwc.ParseWantKey(key)
|
||||
if err != nil {
|
||||
continue
|
||||
|
||||
@@ -111,7 +111,7 @@ func WaitsOutsideFinding(node, module, causeKey, on string, at time.Time) Observ
|
||||
// words, under the assigner's own name (ADR 0437 clause 9), keyed by the held fingerprint. needsYou is true for a module holding
|
||||
// data, whose restore ask waits for the operator.
|
||||
func HeldCondition(module, fp, node, words string, raised, needsYou bool) oc.Condition {
|
||||
key := assignment.Module + ".held." + module + "." + strings.TrimPrefix(fp, meshcatalogclient.SourcePrefix2)
|
||||
key := assignment.Module() + ".held." + module + "." + strings.TrimPrefix(fp, meshcatalogclient.SourcePrefix2)
|
||||
subject := oc.ConditionSubject{Scope: "module", ID: module, Machine: node}
|
||||
if !raised {
|
||||
return oc.Condition{Key: key, Subject: subject, State: occ.StateCleared}
|
||||
@@ -134,7 +134,7 @@ func HeldCondition(module, fp, node, words string, raised, needsYou bool) oc.Con
|
||||
// the node keeps its want. It names the node, the module, fp and the have's words, keyed by node and module, and is cleared
|
||||
// (raised false) once the node no longer fails alone.
|
||||
func NodeFailedCondition(node, module, fp string, f NodeFailure, raised bool) oc.Condition {
|
||||
key := assignment.Module + ".failed-alone." + node + "." + module
|
||||
key := assignment.Module() + ".failed-alone." + node + "." + module
|
||||
subject := oc.ConditionSubject{Scope: "module", ID: module, Machine: node}
|
||||
if !raised {
|
||||
return oc.Condition{Key: key, Subject: subject, State: occ.StateCleared,
|
||||
@@ -157,7 +157,7 @@ func NodeFailedCondition(node, module, fp string, f NodeFailure, raised bool) oc
|
||||
// naming the module, the node and the rule holding its first want back with the rule's words, under the assigner's own name keyed by
|
||||
// node and module, and cleared (raised false) when the node has a want for it again.
|
||||
func NeitherCondition(node, module string, h Holding, raised bool) oc.Condition {
|
||||
key := assignment.Module + ".neither." + node + "." + module
|
||||
key := assignment.Module() + ".neither." + node + "." + module
|
||||
subject := oc.ConditionSubject{Scope: "module", ID: module, Machine: node}
|
||||
if !raised {
|
||||
return oc.Condition{Key: key, Subject: subject, State: occ.StateCleared,
|
||||
@@ -333,7 +333,7 @@ func BackupAsk(key, node, module, fp, digest, words string, asking int, now time
|
||||
// BackupCondition is the condition raised when a backup before an apply fails and the module is held on the node (ADR 0539 §16),
|
||||
// naming the node, the module and the backup's words; cleared under the same key.
|
||||
func BackupCondition(node, module, fp, words string, raised bool) oc.Condition {
|
||||
key := assignment.Module + ".backup." + node + "." + module + "." + strings.TrimPrefix(fp, meshcatalogclient.SourcePrefix2)
|
||||
key := assignment.Module() + ".backup." + node + "." + module + "." + strings.TrimPrefix(fp, meshcatalogclient.SourcePrefix2)
|
||||
subject := oc.ConditionSubject{Scope: "module", ID: module, Machine: node}
|
||||
if !raised {
|
||||
return oc.Condition{Key: key, Subject: subject, State: occ.StateCleared}
|
||||
|
||||
@@ -306,7 +306,7 @@ func saidConditionsAreFindings(t *testing.T) {
|
||||
t.Fatalf("%s: %v", c.key, err)
|
||||
}
|
||||
got, ok := puts[c.key]
|
||||
if !ok || got.Severity != c.raised.Severity || got.Headline != c.raised.Headline || got.Observer != assignment.Module ||
|
||||
if !ok || got.Severity != c.raised.Severity || got.Headline != c.raised.Headline || got.Observer != assignment.Module() ||
|
||||
got.Subject.Machine != c.raised.Subject.Machine || said != 0 {
|
||||
t.Fatalf("%s: finding %+v (written %v), said %d", c.key, got, puts, said)
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
// SeatWaitsCondition is the assigner's word on the seat mesh-module-assigner while a state of the seat is not bound (ADR 0567
|
||||
// §1): raised naming each, cleared when none is left.
|
||||
func SeatWaitsCondition(unbound []string) oc.Condition {
|
||||
key := assignment.Module + ".seat-states"
|
||||
subject := oc.ConditionSubject{Scope: "module", ID: assignment.Module}
|
||||
key := assignment.Module() + ".seat-states"
|
||||
subject := oc.ConditionSubject{Scope: "module", ID: assignment.Module()}
|
||||
if len(unbound) == 0 {
|
||||
return oc.Condition{Key: key, Subject: subject, State: occ.StateCleared, Resolved: "the seat mesh-module-assigner holds all its states."}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ import (
|
||||
// FindingsState is the assigner's state of its findings, and FindingsBucket its bucket.
|
||||
const FindingsState = "findings"
|
||||
|
||||
var FindingsBucket = assignment.Module + "_" + FindingsState
|
||||
func FindingsBucket() string { return assignment.Module() + "_" + FindingsState }
|
||||
|
||||
// KeyID is an id as the parts of a key: each dot-separated part made safe for a bus key, as the key of a condition is
|
||||
// (mcc.Key), so a finding's key keeps the shape its condition had.
|
||||
@@ -59,7 +59,7 @@ func (o Observed) Finding(at time.Time) mcseat.Finding {
|
||||
sev = mcseat.FindingSeverityWarning
|
||||
}
|
||||
return mcseat.Finding{Scope: o.Scope, ID: id, Kind: o.Kind, Severity: sev, Summary: o.Summary, Headline: o.Headline,
|
||||
Explanation: o.Explanation, Needs: o.Needs, Actions: o.Actions, Source: o.Source, Observer: assignment.Module, At: at,
|
||||
Explanation: o.Explanation, Needs: o.Needs, Actions: o.Actions, Source: o.Source, Observer: assignment.Module(), At: at,
|
||||
Evidence: []mcseat.Evidence{{At: at, Said: said}},
|
||||
Subject: mcseat.Subject{Scope: o.Scope, ID: id, Machine: o.Machine, Also: o.Also}}
|
||||
}
|
||||
@@ -79,7 +79,7 @@ func CallUnanswered(id, callee, machine, fault, source string, since time.Time)
|
||||
// subject's id when the key names none), its words unchanged but the resolved ones. raised is false for a clear, whose
|
||||
// finding names only its scope, id and kind.
|
||||
func FindingOf(c oc.Condition, at time.Time) (f mcseat.Finding, raised bool, err error) {
|
||||
rest, ok := strings.CutPrefix(c.Key, assignment.Module+".")
|
||||
rest, ok := strings.CutPrefix(c.Key, assignment.Module()+".")
|
||||
if !ok || rest == "" {
|
||||
return f, false, fmt.Errorf("the condition %q is not the assigner's", c.Key)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package assign
|
||||
|
||||
import plc "mesh-delivery-assigner/internal/assignment"
|
||||
|
||||
// The assigner's tests run as the module its manifest names, as a credential's module would say it at its start (design 47 G A-296).
|
||||
func init() { plc.SetModule("mesh-delivery-assigner") }
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
// catalogue.Upgrade: a person's choice over the module's word, its irreplaceable data and the default, the bus as any module (ADR 0601 §1).
|
||||
|
||||
// PoliciesBucket is the assigner's state `policies`.
|
||||
var PoliciesBucket = assignment.Module + "_" + buckets.PoliciesState
|
||||
func PoliciesBucket() string { return assignment.Module() + "_" + buckets.PoliciesState }
|
||||
|
||||
// DecodePolicies reads the policies state's values by key (the module); a value that cannot be read is left out, named.
|
||||
func DecodePolicies(values map[string][]byte) (map[string]catalogue.PersonPolicy, []string) {
|
||||
|
||||
@@ -182,7 +182,7 @@ const (
|
||||
// CallClearedCondition clears the condition of a refused controller call a previous process raised, for its pair (node and
|
||||
// module, or node and provision for a pin).
|
||||
func CallClearedCondition(kind, node, module string) oc.Condition {
|
||||
return oc.Condition{Key: assignment.Module + "." + kind + "." + node + "." + module,
|
||||
return oc.Condition{Key: assignment.Module() + "." + kind + "." + node + "." + module,
|
||||
Subject: oc.ConditionSubject{Scope: "module", ID: module, Machine: node}, State: occ.StateCleared,
|
||||
Resolved: "the assigner calls no verb of the controller any more: assignments is the one source."}
|
||||
}
|
||||
|
||||
@@ -83,10 +83,10 @@ func TestTheAssignmentsSeatDecides(t *testing.T) {
|
||||
}
|
||||
|
||||
// The seat says it waits while one of its states is not bound, naming it, and clears once all are.
|
||||
for _, unbound := range [][]string{{plc.PulsesBucket}, nil} {
|
||||
for _, unbound := range [][]string{{plc.PulsesBucket()}, nil} {
|
||||
c := SeatWaitsCondition(unbound)
|
||||
if _, err := occ.EncodeCondition(c); err != nil || (len(unbound) > 0) != (c.State == occ.StateRaised) ||
|
||||
(len(unbound) > 0 && !strings.Contains(c.Explanation, "the seat waits for its states: "+plc.PulsesBucket)) {
|
||||
(len(unbound) > 0 && !strings.Contains(c.Explanation, "the seat waits for its states: "+plc.PulsesBucket())) {
|
||||
t.Errorf("waiting for %v: %+v, %v", unbound, c, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package assigngraph
|
||||
|
||||
import plc "mesh-delivery-assigner/internal/assignment"
|
||||
|
||||
// The assigner's tests run as the module its manifest names, as a credential's module would say it at its start (design 47 G A-296).
|
||||
func init() { plc.SetModule("mesh-delivery-assigner") }
|
||||
@@ -13,3 +13,5 @@ func TestMain(m *testing.M) {
|
||||
catalogue.UseSeats(catalogue.SeatsFromDefinitions(seatlisttest.Definitions()))
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func init() { SetModule("mesh-delivery-assigner") }
|
||||
|
||||
@@ -1,19 +1,38 @@
|
||||
package assignment
|
||||
|
||||
import seat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||
import (
|
||||
"sync"
|
||||
|
||||
// Module is the module that holds the seat mesh-module-assigner: the states of the seat are named after it, as every
|
||||
// module state is (a bucket is `<module>_<state>`), and its manifest declares them.
|
||||
const Module = "mesh-delivery-assigner"
|
||||
seat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||
)
|
||||
|
||||
var (
|
||||
moduleMu sync.RWMutex
|
||||
module string
|
||||
)
|
||||
|
||||
// SetModule names the module the assigner runs as, from its credential's module, once at its start: its states are named
|
||||
// after it, as every module state is (a bucket is `<module>_<state>`), and its manifest declares them. No code names the
|
||||
// assigner's module (design 47 G A-296, §428).
|
||||
func SetModule(m string) {
|
||||
moduleMu.Lock()
|
||||
defer moduleMu.Unlock()
|
||||
module = m
|
||||
}
|
||||
|
||||
// Module is the module the assigner runs as, as SetModule said at its start.
|
||||
func Module() string {
|
||||
moduleMu.RLock()
|
||||
defer moduleMu.RUnlock()
|
||||
return module
|
||||
}
|
||||
|
||||
// The buckets of the seat's states, which only the holder writes (ADR 0567 §1).
|
||||
const (
|
||||
AssignmentsBucket = Module + "_" + seat.StateAssignments
|
||||
ProposalsBucket = Module + "_" + seat.StateProposals
|
||||
CandidatesBucket = Module + "_" + seat.StateCandidates
|
||||
PulsesBucket = Module + "_" + seat.StatePulses
|
||||
SeatHoldersBucket = Module + "_" + seat.StateSeatHolders
|
||||
)
|
||||
func AssignmentsBucket() string { return Module() + "_" + seat.StateAssignments }
|
||||
func ProposalsBucket() string { return Module() + "_" + seat.StateProposals }
|
||||
func CandidatesBucket() string { return Module() + "_" + seat.StateCandidates }
|
||||
func PulsesBucket() string { return Module() + "_" + seat.StatePulses }
|
||||
func SeatHoldersBucket() string { return Module() + "_" + seat.StateSeatHolders }
|
||||
|
||||
const (
|
||||
// AssignmentsHistory is the revisions the assignments bucket keeps per key: the bus's most (§6).
|
||||
|
||||
@@ -14,35 +14,40 @@ import (
|
||||
plc "mesh-delivery-assigner/internal/assignment"
|
||||
)
|
||||
|
||||
// The states the assigner keeps: the wants seat's, and its own asks, puts and policies.
|
||||
// The states the assigner keeps beside the seat's: its own asks, puts, waits and policies.
|
||||
const (
|
||||
Wants = plc.Module + "_" + wants.StateWants
|
||||
Differences = plc.Module + "_" + wants.StateDifferences
|
||||
Asks = plc.Module + "_asks"
|
||||
Puts = plc.Module + "_puts"
|
||||
// Waits holds the waits on an outside cause, one key `<node>.<module>.<fingerprint hex>` (design 47 G §706).
|
||||
// WaitsState holds the waits on an outside cause, one key `<node>.<module>.<fingerprint hex>` (design 47 G §706).
|
||||
WaitsState = "waits"
|
||||
Waits = plc.Module + "_" + WaitsState
|
||||
// DeliveredAgain holds each ask of assigner_delivered_again, one key `<module>`, its value the version, who asked and why, and
|
||||
// DeliveredAgainState holds each ask of assigner_delivered_again, one key `<module>`, its value the version, who asked and why, and
|
||||
// why it does not hold yet (design 47 G §376, A-250).
|
||||
DeliveredAgainState = "delivered-again"
|
||||
DeliveredAgain = plc.Module + "_" + DeliveredAgainState
|
||||
// DeliveryWaits holds each dependent's delivery wait the assigner said, one key `<module>`, its value the wait-held said
|
||||
// DeliveryWaitsState holds each dependent's delivery wait the assigner said, one key `<module>`, its value the wait-held said
|
||||
// (design 47 G §376, §498, A-246).
|
||||
DeliveryWaitsState = "delivery-waits"
|
||||
DeliveryWaits = plc.Module + "_" + DeliveryWaitsState
|
||||
// PutWaits holds each put wait the assigner said, one key `<node>.<module>`, its value the wait's id and the put as said
|
||||
// PutWaitsState holds each put wait the assigner said, one key `<node>.<module>`, its value the wait's id and the put as said
|
||||
// (design 47 G A-204, A-211), so a put that ends while the assigner is stopped gets its wait-ended at the next start.
|
||||
PutWaitsState = "put-waits"
|
||||
PutWaits = plc.Module + "_" + PutWaitsState
|
||||
// Inputs holds the inputs of the composition steps, `<time>.<digest>`, which the seat mesh-wants' verb input answers (design 47 G §354).
|
||||
// InputsState holds the inputs of the composition steps, `<time>.<digest>`, which the seat mesh-wants' verb input answers (design 47 G §354).
|
||||
InputsState = "inputs"
|
||||
Inputs = plc.Module + "_" + InputsState
|
||||
// PoliciesState is the state of the policies a person set for a module's upgrade; Policies its bucket.
|
||||
// PoliciesState is the state of the policies a person set for a module's upgrade.
|
||||
PoliciesState = "policies"
|
||||
Policies = plc.Module + "_" + PoliciesState
|
||||
)
|
||||
|
||||
// The assigner's own buckets, `<module>_<state>` of the module it runs as (its credential's, plc.SetModule), never a
|
||||
// constant of its name (design 47 G A-296, §428).
|
||||
func own(state string) string { return plc.Module() + "_" + state }
|
||||
|
||||
func Wants() string { return own(wants.StateWants) }
|
||||
func Differences() string { return own(wants.StateDifferences) }
|
||||
func Asks() string { return own("asks") }
|
||||
func Puts() string { return own("puts") }
|
||||
func Waits() string { return own(WaitsState) }
|
||||
func DeliveredAgain() string { return own(DeliveredAgainState) }
|
||||
func DeliveryWaits() string { return own(DeliveryWaitsState) }
|
||||
func PutWaits() string { return own(PutWaitsState) }
|
||||
func Inputs() string { return own(InputsState) }
|
||||
func Policies() string { return own(PoliciesState) }
|
||||
|
||||
// AsksHistory is the revisions the bucket of asks keeps per key.
|
||||
const AsksHistory = 5
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package buckets
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"testing"
|
||||
|
||||
plc "mesh-delivery-assigner/internal/assignment"
|
||||
)
|
||||
|
||||
// The names are the ones the earlier shared form held (mesh-sdk's go/delivery), and the ones the manifest's reads and
|
||||
// states say: a bucket is `<module>_<state>`.
|
||||
@@ -8,11 +12,26 @@ func TestBucketsAreTheEarlierNames(t *testing.T) {
|
||||
for name, c := range map[string][2]string{
|
||||
"Have": {Have, "node-engine_have"}, "ComposedRead": {ComposedRead, "mesh-host.composed"},
|
||||
"Modules": {Modules, "mesh-delivery-indexer_modules"}, "Manifests": {Manifests, "mesh-delivery-indexer_manifests"},
|
||||
"Answers": {Answers, "mesh-delivery-teller_answers"},
|
||||
"Grants": {Grants, "mesh-grants_grants"},
|
||||
"Wants": {Wants, "mesh-delivery-assigner_wants"}, "Differences": {Differences, "mesh-delivery-assigner_differences"},
|
||||
"Asks": {Asks, "mesh-delivery-assigner_asks"}, "Puts": {Puts, "mesh-delivery-assigner_puts"},
|
||||
"Policies": {Policies, "mesh-delivery-assigner_policies"}, "Provisions": {Provisions, "mesh-provisioner_provisions"}, "TellerModule": {TellerModule, "mesh-delivery-teller"},
|
||||
"Answers": {Answers, "mesh-delivery-teller_answers"},
|
||||
"Grants": {Grants, "mesh-grants_grants"},
|
||||
"Provisions": {Provisions, "mesh-provisioner_provisions"}, "TellerModule": {TellerModule, "mesh-delivery-teller"},
|
||||
} {
|
||||
if c[0] != c[1] {
|
||||
t.Errorf("%s = %q, want %q", name, c[0], c[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The assigner's own buckets are named from the module it runs as, its credential's, never from a constant of its name
|
||||
// (design 47 G A-296, §428): a bucket is `<module>_<state>` of whatever module SetModule was told.
|
||||
func TestTheAssignersOwnBucketsTakeTheModuleItRunsAs(t *testing.T) {
|
||||
defer plc.SetModule(plc.Module())
|
||||
plc.SetModule("some-assigner")
|
||||
for name, c := range map[string][2]string{
|
||||
"Wants": {Wants(), "some-assigner_wants"}, "Differences": {Differences(), "some-assigner_differences"},
|
||||
"Asks": {Asks(), "some-assigner_asks"}, "Puts": {Puts(), "some-assigner_puts"}, "Policies": {Policies(), "some-assigner_policies"},
|
||||
"Waits": {Waits(), "some-assigner_waits"}, "Inputs": {Inputs(), "some-assigner_inputs"},
|
||||
"Assignments": {plc.AssignmentsBucket(), "some-assigner_assignments"}, "SeatHolders": {plc.SeatHoldersBucket(), "some-assigner_seat-holders"},
|
||||
} {
|
||||
if c[0] != c[1] {
|
||||
t.Errorf("%s = %q, want %q", name, c[0], c[1])
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package buckets
|
||||
|
||||
import plc "mesh-delivery-assigner/internal/assignment"
|
||||
|
||||
// The assigner's tests run as the module its manifest names, as a credential's module would say it at its start (design 47 G A-296).
|
||||
func init() { plc.SetModule("mesh-delivery-assigner") }
|
||||
@@ -0,0 +1,6 @@
|
||||
package judge
|
||||
|
||||
import plc "mesh-delivery-assigner/internal/assignment"
|
||||
|
||||
// The assigner's tests run as the module its manifest names, as a credential's module would say it at its start (design 47 G A-296).
|
||||
func init() { plc.SetModule("mesh-delivery-assigner") }
|
||||
Reference in New Issue
Block a user