Files
mesh-controller/internal/chain/bus.go
T
jschoubben c795915315
mesh/merge-gate head c79591531575 merged into trunk aa02e7bf4e78 — pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every …
mesh/repo-check head c79591531575 merged into trunk aa02e7bf4e78 — pass: its merge-check.sh passed
mesh/delivery delivered
Keep builds on the bus under their module, so no lookup reads the bucket whole (issue 1121, ADR 0427)
Milestone 5 moved 8,599 build records onto the bus under bare build ids, each
carrying its module's whole manifest. Every build lookup then read the whole
bucket, and a delivery plan's send did that per module per machine while it
held the plans and the machine's declaration: the send of build-agent never
finished, and every other act and the status waited behind it.

Builds now live under <module>.build.<id> (requests under req.<id>, id.<id>
names a build's module), with pointer keys newest, worked, held,
commit.<sha> and source.<reuse key> that move only to a newer build and are
read as hints, repaired when wrong. Manifests are kept once in the same
bucket under manifest.sha256-<hex>, so no grant the running bus lacks is
needed. The old keys are laid out once at start, idempotently, and left
unread. The calls a plan look makes on the silent clocks while it holds the
plans are bounded, since the vendored kv.Keys ignores its context.
2026-10-18 13:32:31 +02:00

146 lines
3.7 KiB
Go

package chain
import (
"context"
"errors"
"fmt"
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
"github.com/novox/mesh-controller/internal/broker"
)
// OnTheBus opens the chain's three buckets on a connection. The controller asserts them at its start
// (broker.EnsureControllerBuckets), so one older than this has not, and the error says so.
func OnTheBus(ctx context.Context, conn *nats.Conn, holds Holds) (*Chain, error) {
api, err := jetstream.New(conn)
if err != nil {
return nil, err
}
open := func(bucket string) (Backend, error) {
kv, err := api.KeyValue(ctx, bucket)
if err != nil {
return nil, fmt.Errorf("the bucket %s is not on the bus — the controller asserts it at its start, so one "+
"older than this has not: %w", bucket, err)
}
return busBucket{kv}, nil
}
builds, err := open(broker.BuildsBucket)
if err != nil {
return nil, err
}
rollouts, err := open(broker.RolloutsBucket)
if err != nil {
return nil, err
}
assignments, err := open(broker.AssignmentsBucket)
if err != nil {
return nil, err
}
return New(builds, rollouts, assignments, holds).WithManifests(builds), nil
}
// busBucket is a Backend on a key-value bucket. It calls no delete and no purge: the type has none to call (ADR 0408 §4).
type busBucket struct{ kv jetstream.KeyValue }
func entryOf(e jetstream.KeyValueEntry) Entry {
return Entry{Key: e.Key(), Value: e.Value(), Revision: e.Revision(), At: e.Created()}
}
func (b busBucket) Get(ctx context.Context, key string) (Entry, bool, error) {
e, err := b.kv.Get(ctx, key)
if errors.Is(err, jetstream.ErrKeyNotFound) {
return Entry{}, false, nil
}
if err != nil {
return Entry{}, false, err
}
return entryOf(e), true, nil
}
func (b busBucket) Create(ctx context.Context, key string, value []byte) (uint64, error) {
rev, err := b.kv.Create(ctx, key, value)
if errors.Is(err, jetstream.ErrKeyExists) {
return 0, ErrExists
}
return rev, err
}
func (b busBucket) Update(ctx context.Context, key string, value []byte, revision uint64) (uint64, error) {
rev, err := b.kv.Update(ctx, key, value, revision)
var apiErr *jetstream.APIError
if errors.As(err, &apiErr) && apiErr.ErrorCode == jetstream.JSErrCodeStreamWrongLastSequence {
return 0, ErrMoved
}
return rev, err
}
func (b busBucket) Keys(ctx context.Context) ([]string, error) {
keys, err := b.kv.Keys(ctx)
if errors.Is(err, jetstream.ErrNoKeysFound) {
return nil, nil
}
return keys, err
}
func (b busBucket) Watch(ctx context.Context) (<-chan Entry, error) {
return b.WatchFiltered(ctx, ">")
}
func (b busBucket) WatchFiltered(ctx context.Context, filter string) (<-chan Entry, error) {
var w jetstream.KeyWatcher
var err error
if filter == ">" {
w, err = b.kv.WatchAll(ctx, jetstream.IgnoreDeletes())
} else {
w, err = b.kv.WatchFiltered(ctx, []string{filter}, jetstream.IgnoreDeletes())
}
if err != nil {
return nil, err
}
out := make(chan Entry)
go func() {
defer close(out)
defer func() { _ = w.Stop() }()
for {
select {
case <-ctx.Done():
return
case e, ok := <-w.Updates():
if !ok {
return
}
v := Entry{Caught: true}
if e != nil {
v = entryOf(e)
}
select {
case out <- v:
case <-ctx.Done():
return
}
}
}
}()
return out, nil
}
func (b busBucket) History(ctx context.Context, key string) ([]Entry, error) {
all, err := b.kv.History(ctx, key)
if errors.Is(err, jetstream.ErrKeyNotFound) {
return nil, nil
}
if err != nil {
return nil, err
}
out := make([]Entry, 0, len(all))
for _, e := range all {
if e.Operation() != jetstream.KeyValuePut {
continue // a marker an earlier mirror left; nothing of the chain makes one
}
out = append(out, entryOf(e))
}
return out, nil
}