internal/chain holds typed Build, Rollout and Assignment and a Backend with no delete or purge, in memory and on the bus. The builds and rollouts buckets are asserted with history 64, no age and values up to just under a mebibyte; the assignments bucket gets the same, keeping its marker age because a stream cannot be made to disallow message ages. Migration 0104 makes the one-row chain_moved switch. The controller keeps no consolidated schema, so 0104 is the whole schema change. Nothing reads the buckets yet.
436 lines
24 KiB
Go
436 lines
24 KiB
Go
package broker
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/nats-io/nats.go/jetstream"
|
|
)
|
|
|
|
// The controller's own key-value buckets (novox/hq to-be 45 §1, §6, §7).
|
|
//
|
|
// **What the controller must remember across its own restart, it keeps on the bus.** A call's
|
|
// outcome lived in the memory of the process that served it (novox/hq issue 265), so a controller
|
|
// replaced while a push ran answered "no such call" for the one thing its caller had been told to
|
|
// ask about. The bus already outlives the controller and is the shape ADR 0201 gives a module's
|
|
// current state: one value per key, written by one owner, read by anybody granted it. These are the
|
|
// controller's, written by it alone — the writers table of to-be 45 §1 — and asserted on every start
|
|
// like the streams, so a bus raised from nothing has them before the first call is served.
|
|
|
|
// CallsBucket keeps every call of the mesh's own verbs and what came of it; HandActsBucket every act
|
|
// a person did by hand, with why; ConditionsBucket every condition open now (to-be 45 §2), one key
|
|
// each, and ConditionHistoryBucket every transition of one — raised, changed, silenced, cleared —
|
|
// for ninety days.
|
|
//
|
|
// **The history is a bucket of its own** because its keys expire and an open condition's must not:
|
|
// a bucket has one age for every key, and a condition open longer than the history is kept would
|
|
// otherwise vanish from the store while still true.
|
|
var (
|
|
CallsBucket = BucketName(ControllerSeat, "calls")
|
|
HandActsBucket = BucketName(ControllerSeat, "hand-acts")
|
|
ConditionsBucket = BucketName(ControllerSeat, "conditions")
|
|
ConditionHistoryBucket = BucketName(ControllerSeat, "condition-history")
|
|
// LeaseBucket holds the controller's lease (to-be 45 §6): one key, `holder`, which the instance
|
|
// allowed to act writes by compare-and-set and renews; its revision when taken is the epoch.
|
|
LeaseBucket = BucketName(ControllerSeat, "lease")
|
|
// AskedBucket keeps what the controller asked the operator about its conditions (novox/hq ADR 0259):
|
|
// each ask by its id, its options and the actions they stand for, how it ended and whether the
|
|
// controller acted on its warrant — so a restart neither asks twice nor acts twice.
|
|
AskedBucket = BucketName(ControllerSeat, "asked")
|
|
// MergeWindowBucket holds the clock of every open batch's merge window (novox/hq ADR 0407 decision 1): per
|
|
// batch a key `<batch>.quiet`, made again by each merge into it and living the window, and a key
|
|
// `<batch>.most`, made once when it opens and living the window's most. The first to expire closes the
|
|
// batch, and the controller's watcher hears the delete marker. Written by the lease holder alone.
|
|
MergeWindowBucket = BucketName(ControllerSeat, "merge-window")
|
|
// RolloutSilentBucket holds the wait of every machine a rollout has sent a tier to (novox/hq ADR 0406 §5, issue
|
|
// 974): per send a key `<plan>.<tier>.<node>` that lives ApplySilentAfter, made when the controller sends that
|
|
// machine and deleted by the machine's report on that send. Only a missing report lets it expire, and the
|
|
// controller's watcher hearing the expiry ends the wait and names the machine silent. Written by the lease
|
|
// holder alone.
|
|
RolloutSilentBucket = BucketName(ControllerSeat, "rollout-silent")
|
|
// CheckRunningBucket holds the pull request checks the builders are running now (novox/hq ADR 0406 §3, issue 974): per
|
|
// pull request a key `<owner>.<repo>.<number>` (or `group.<name>`) holding the head and trunk commit being checked, the
|
|
// builder and its build. A builder puts it when it starts a check and again on each heartbeat, and deletes it when the
|
|
// check ends; a key not renewed within the bucket's age is a builder that died, and is gone. A builder that takes a
|
|
// waiting request for a head and trunk commit a key names drops it. **Written by the build seat's holders, several by
|
|
// design** — each the key of the check it runs.
|
|
CheckRunningBucket = BucketName(ControllerSeat, "checks-running")
|
|
// DeliveryMergesBucket holds the merges mesh-delivery announced it made, each with the ask whose Approve it merged
|
|
// on (novox/hq ADR 0420 §7, issue 1025): per merge a key `<owner>/<repo>/<number>/<head sha>` holding the
|
|
// announcement, kept DeliveryMergesKeptFor. What lets the build of a moved module register it from its new
|
|
// repository: the controller reads the ask's warrant from the router and accepts only the operator's Approve of
|
|
// those heads. Written by the controller alone, from the announcement, when it holds the lease.
|
|
DeliveryMergesBucket = BucketName(ControllerSeat, "delivery-merges")
|
|
)
|
|
|
|
// DeliveryMergesKeptFor is how long a merge mesh-delivery announced is kept: a week, as the events are.
|
|
const DeliveryMergesKeptFor = 7 * 24 * time.Hour
|
|
|
|
// The controller's record mirrors (novox/hq ADR 0289, design 54, design 57 package F3): a bucket per kind of
|
|
// record, one key per record, written from the store by the lease holder only when the record changed, with
|
|
// one event on the controller's seat per write. Read by whatever names them in `reads` as
|
|
// `mesh-controller.<mirror>` (ADR 0201 §1), written by nobody else. **No secret in either** (ADR 0201): an
|
|
// assignment's value names its module and node; a module's declared config is what its manifest, which is
|
|
// public, declares.
|
|
//
|
|
// AssignmentsBucket holds which module is assigned to which node, keyed `<node>.<module>`; and
|
|
// DeclaredConfigBucket each registered module's config declarations as its manifest holds them, keyed by
|
|
// the module: what mesh-config resolves a config value from (ADR 0348 clauses 2 and 4); and PredefinedBucket the
|
|
// list of predefined keys the mesh published (the newest trunk build's of the catalogue, ADR 0348 §7), one key
|
|
// `list`: what mesh-config publishes as mesh-config_predefined (design 57 K3, issue 662).
|
|
var (
|
|
AssignmentsBucket = BucketName(ControllerSeat, MirrorAssignments)
|
|
DeclaredConfigBucket = BucketName(ControllerSeat, MirrorDeclaredConfig)
|
|
PredefinedBucket = BucketName(ControllerSeat, MirrorPredefined)
|
|
)
|
|
|
|
// The mirrors' local names, as a reader names them in `reads` after `mesh-controller.`.
|
|
const (
|
|
MirrorAssignments = "assignments"
|
|
MirrorDeclaredConfig = "declared-config"
|
|
MirrorPredefined = "predefined"
|
|
)
|
|
|
|
// The chain's buckets (novox/hq ADR 0408, issue 1089): builds, rollouts and assignments are one chain kept on the bus
|
|
// alone, one bucket each, one key per record, written by the lease holder by compare-and-set. The assignments bucket is
|
|
// AssignmentsBucket above, which keeps its name and its readers (ADR 0408 §3). Nothing is removed from them (§4), they keep
|
|
// ChainHistory earlier revisions of every key (§5), and a value is never cut (§6).
|
|
var (
|
|
BuildsBucket = BucketName(ControllerSeat, ChainBuilds)
|
|
RolloutsBucket = BucketName(ControllerSeat, ChainRollouts)
|
|
)
|
|
|
|
// The chain buckets' local names, as a reader names them in `reads` after `mesh-controller.`.
|
|
const (
|
|
ChainBuilds = "builds"
|
|
ChainRollouts = "rollouts"
|
|
)
|
|
|
|
// The chain buckets' bounds. ChainHistory is the bus's largest history (ADR 0408 §5). A value is at most ChainValueBytes,
|
|
// just under the server's largest message of a mebibyte, which also carries the subject and the headers (ADR 0408 §6):
|
|
// the controller writes none larger and the move refuses to. The buckets' size is the arithmetic of the survey of
|
|
// issue 981: 8585 builds of 1 to 40 KB, 1049 rollouts of about 2 KB a revision and 64 revisions, so 512 MiB for the two
|
|
// that grow, and the assignments, 285 of under a kilobyte, 64 MiB.
|
|
const (
|
|
ChainHistory = 64
|
|
ChainValueBytes = 1<<20 - 8<<10
|
|
ChainBuildsBytes = 512 << 20
|
|
ChainRolloutsBytes = 512 << 20
|
|
ChainAssignmentBytes = 64 << 20
|
|
)
|
|
|
|
// ChainBuckets are the three buckets of the chain: builds, rollouts and assignments.
|
|
func ChainBuckets() []string { return []string{BuildsBucket, RolloutsBucket, AssignmentsBucket} }
|
|
|
|
// ControllerMirrors are the local names of the controller's buckets a module may read: the record mirrors and the
|
|
// chain's buckets.
|
|
func ControllerMirrors() []string {
|
|
return []string{MirrorAssignments, MirrorDeclaredConfig, MirrorPredefined, ChainBuilds, ChainRollouts}
|
|
}
|
|
|
|
// The mirrors' caps (design 54 §5): one value per key, no age, 64 KiB a value, 64 MiB a bucket.
|
|
const (
|
|
MirrorValueBytes = 64 << 10
|
|
MirrorBytes = 64 << 20
|
|
// MirrorMarkerTTL is the longest a purge's marker is kept (design 54 §2): the bucket allows a
|
|
// per-message age for it, and the writer gives each marker a day.
|
|
MirrorMarkerTTL = 24 * time.Hour
|
|
)
|
|
|
|
// MergeWindowMarkerTTL is how long the marker an expired window key leaves is kept: long enough for a watcher
|
|
// that is up to hear it, short enough to leave nothing behind.
|
|
const MergeWindowMarkerTTL = time.Minute
|
|
|
|
// RolloutSilentMarkerTTL is how long the marker an expired silent key leaves is kept, for the same reason.
|
|
const RolloutSilentMarkerTTL = time.Minute
|
|
|
|
// RolloutSilentKeptFor is the age of the silent keys' bucket: every report deletes a key and the delete leaves a marker, which
|
|
// with one value per key would stay for ever and fill the bucket. A key lives a quarter of an hour at most, so an hour
|
|
// takes nothing live.
|
|
const RolloutSilentKeptFor = time.Hour
|
|
|
|
// CheckRunningKeptFor is the age of a running check's key: a few heartbeats (ten seconds each) of a builder, so a builder that
|
|
// died is forgotten within two minutes and the check it ran is taken again then.
|
|
const CheckRunningKeptFor = 2 * time.Minute
|
|
|
|
// AskedKeptFor is how long an ask is kept after it was made: a month, as the router keeps its own.
|
|
const AskedKeptFor = 30 * 24 * time.Hour
|
|
|
|
// LeaseTTL is how long the lease's key lives unrenewed (to-be 45 §6): fifteen seconds, renewed
|
|
// every five. The bucket's age, so the bus forgets a holder that stopped renewing.
|
|
const LeaseTTL = 15 * time.Second
|
|
|
|
// The bounds to-be 45 §6 sets for calls: the last thousand, or fourteen days, whichever is fewer.
|
|
// A call is two keys — its record, and its answer apart so a listing does not read every answer —
|
|
// so the stream holds twice as many messages as it keeps calls.
|
|
const (
|
|
KeptCallsDurably = 1000
|
|
CallsKeptFor = 14 * 24 * time.Hour
|
|
// CallAnswerBytes is the most of one answer kept: a whole declaration is far smaller, and an
|
|
// answer larger is cut and says so.
|
|
CallAnswerBytes = 64 << 10
|
|
// HandActsKeptFor is as long as a condition's history (to-be 45 §2): an act by hand is read
|
|
// back beside what it addressed.
|
|
HandActsKeptFor = 90 * 24 * time.Hour
|
|
// ConditionHistoryKeptFor is how long a condition's transitions are kept (to-be 45 §2).
|
|
ConditionHistoryKeptFor = 90 * 24 * time.Hour
|
|
)
|
|
|
|
// IsControllerBucket says a bucket is the controller's own, not a module's state nothing declares.
|
|
func IsControllerBucket(bucket string) bool {
|
|
return bucket == CallsBucket || bucket == HandActsBucket || bucket == ConditionsBucket ||
|
|
bucket == ConditionHistoryBucket || bucket == LeaseBucket || bucket == AskedBucket ||
|
|
bucket == AssignmentsBucket || bucket == DeclaredConfigBucket || bucket == PredefinedBucket ||
|
|
bucket == MergeWindowBucket || bucket == RolloutSilentBucket || bucket == CheckRunningBucket ||
|
|
bucket == DeliveryMergesBucket || bucket == BuildsBucket || bucket == RolloutsBucket
|
|
}
|
|
|
|
// ControllerBuckets are the controller's own buckets, in the order they are asserted.
|
|
func ControllerBuckets() []string {
|
|
return []string{LeaseBucket, CallsBucket, HandActsBucket, ConditionsBucket, ConditionHistoryBucket, AskedBucket,
|
|
AssignmentsBucket, DeclaredConfigBucket, PredefinedBucket, MergeWindowBucket, RolloutSilentBucket, CheckRunningBucket,
|
|
DeliveryMergesBucket, BuildsBucket, RolloutsBucket}
|
|
}
|
|
|
|
// ControllerBucketsAsserter is what raising the controller's buckets needs of a connection.
|
|
type ControllerBucketsAsserter interface {
|
|
EnsureControllerBuckets() error
|
|
}
|
|
|
|
// EnsureControllerBuckets creates the controller's buckets if absent and brings their options to
|
|
// match. An update, never a delete: what they hold is the record of what the mesh was asked.
|
|
func (j *JetStream) EnsureControllerBuckets() error {
|
|
js, err := jetstream.New(j.conn)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
if err := EnsureLeaseBucket(ctx, js); err != nil {
|
|
return err
|
|
}
|
|
if _, err := js.CreateOrUpdateKeyValue(ctx, jetstream.KeyValueConfig{
|
|
Bucket: CallsBucket,
|
|
Description: "the calls of the mesh's own verbs and what came of each (novox/hq to-be 45 §6, issue " +
|
|
"265): written by the controller alone, read through `calls`; the last thousand, or fourteen days",
|
|
History: 1,
|
|
TTL: CallsKeptFor,
|
|
MaxValueSize: CallAnswerBytes + 4<<10,
|
|
MaxBytes: 2 * KeptCallsDurably * (CallAnswerBytes + 4<<10),
|
|
Storage: jetstream.FileStorage,
|
|
}); err != nil {
|
|
return fmt.Errorf("asserting bucket %s: %w", CallsBucket, err)
|
|
}
|
|
// **The count, on the stream under the bucket.** A bucket has an age and a size and no count;
|
|
// the stream it is made of does, and with one value per key the oldest message is the oldest
|
|
// call. Asserted after the bucket, every time, because asserting the bucket writes the stream's
|
|
// configuration whole and puts the count back to none.
|
|
stream, err := js.Stream(ctx, "KV_"+CallsBucket)
|
|
if err != nil {
|
|
return fmt.Errorf("reading the stream under %s: %w", CallsBucket, err)
|
|
}
|
|
cfg := stream.CachedInfo().Config
|
|
if cfg.MaxMsgs != 2*KeptCallsDurably {
|
|
cfg.MaxMsgs = 2 * KeptCallsDurably
|
|
cfg.Discard = jetstream.DiscardOld
|
|
if _, err := js.UpdateStream(ctx, cfg); err != nil {
|
|
return fmt.Errorf("bounding %s to the last %d calls: %w", CallsBucket, KeptCallsDurably, err)
|
|
}
|
|
}
|
|
if _, err := js.CreateOrUpdateKeyValue(ctx, jetstream.KeyValueConfig{
|
|
Bucket: HandActsBucket,
|
|
Description: "every act a person did by hand, with why (novox/hq to-be 45 §7): written by the " +
|
|
"controller's repairing verbs and `hand-act record`, read through `hand-acts`",
|
|
History: 1,
|
|
TTL: HandActsKeptFor,
|
|
MaxValueSize: 16 << 10,
|
|
MaxBytes: 64 << 20,
|
|
Storage: jetstream.FileStorage,
|
|
}); err != nil {
|
|
return fmt.Errorf("asserting bucket %s: %w", HandActsBucket, err)
|
|
}
|
|
// **No age on the open conditions.** A condition is removed when observation clears it and at no
|
|
// other moment: one that expired would be a fault the store forgot while it was still true.
|
|
if _, err := js.CreateOrUpdateKeyValue(ctx, jetstream.KeyValueConfig{
|
|
Bucket: ConditionsBucket,
|
|
Description: "every condition open now, one key each (novox/hq to-be 45 §2): written by the " +
|
|
"controller alone, raised and cleared by observation, read through `conditions`",
|
|
History: 1,
|
|
MaxValueSize: 64 << 10,
|
|
MaxBytes: 64 << 20,
|
|
Storage: jetstream.FileStorage,
|
|
}); err != nil {
|
|
return fmt.Errorf("asserting bucket %s: %w", ConditionsBucket, err)
|
|
}
|
|
if _, err := js.CreateOrUpdateKeyValue(ctx, jetstream.KeyValueConfig{
|
|
Bucket: ConditionHistoryBucket,
|
|
Description: "every transition of a condition — raised, changed, silenced, cleared — kept ninety " +
|
|
"days (novox/hq to-be 45 §2): written by the controller alone, read through `conditions history`",
|
|
History: 1,
|
|
TTL: ConditionHistoryKeptFor,
|
|
MaxValueSize: 64 << 10,
|
|
MaxBytes: 256 << 20,
|
|
Storage: jetstream.FileStorage,
|
|
}); err != nil {
|
|
return fmt.Errorf("asserting bucket %s: %w", ConditionHistoryBucket, err)
|
|
}
|
|
if _, err := js.CreateOrUpdateKeyValue(ctx, jetstream.KeyValueConfig{
|
|
Bucket: AskedBucket,
|
|
Description: "what the controller asked the operator about its conditions, and what came of each (novox/hq " +
|
|
"ADR 0259): written by the controller alone; an ask acted on is acted on once",
|
|
History: 1,
|
|
TTL: AskedKeptFor,
|
|
MaxValueSize: 32 << 10,
|
|
MaxBytes: 32 << 20,
|
|
Storage: jetstream.FileStorage,
|
|
}); err != nil {
|
|
return fmt.Errorf("asserting bucket %s: %w", AskedBucket, err)
|
|
}
|
|
// **The merge window: keys that expire.** A key's own time to live is the window; the marker an expired key
|
|
// leaves is what a watcher hears, kept a minute (ADR 0407 decision 1).
|
|
if _, err := js.CreateOrUpdateKeyValue(ctx, jetstream.KeyValueConfig{
|
|
Bucket: MergeWindowBucket,
|
|
Description: "the clock of every open batch's merge window (novox/hq ADR 0407): `<batch>.quiet` lives the " +
|
|
"window and is made again by each merge, `<batch>.most` lives the most; the first to expire closes the " +
|
|
"batch; written by the controller alone",
|
|
History: 1,
|
|
MaxValueSize: 4 << 10,
|
|
MaxBytes: 8 << 20,
|
|
Storage: jetstream.FileStorage,
|
|
LimitMarkerTTL: MergeWindowMarkerTTL,
|
|
}); err != nil {
|
|
return fmt.Errorf("asserting bucket %s: %w", MergeWindowBucket, err)
|
|
}
|
|
// **The rollout's silent keys: keys that expire** (ADR 0406 §5, issue 974). A key lives as long as the machine
|
|
// may stay silent; the machine's report deletes it, and a marker of expiry is what the watcher hears.
|
|
if _, err := js.CreateOrUpdateKeyValue(ctx, jetstream.KeyValueConfig{
|
|
Bucket: RolloutSilentBucket,
|
|
Description: "the wait of every machine a rollout sent a tier to (novox/hq ADR 0406): `<plan>.<tier>.<node>` " +
|
|
"lives the time a machine may stay silent and is deleted by its report; one that expires names the " +
|
|
"machine silent; written by the controller alone",
|
|
History: 1,
|
|
TTL: RolloutSilentKeptFor,
|
|
MaxValueSize: 4 << 10,
|
|
MaxBytes: 8 << 20,
|
|
Storage: jetstream.FileStorage,
|
|
LimitMarkerTTL: RolloutSilentMarkerTTL,
|
|
}); err != nil {
|
|
return fmt.Errorf("asserting bucket %s: %w", RolloutSilentBucket, err)
|
|
}
|
|
// **The checks running: a key that ages** (ADR 0406 §3, issue 974). One message a key, each renewal a new one, so the
|
|
// age of the bucket is how long a key outlives the builder that stopped renewing it.
|
|
if _, err := js.CreateOrUpdateKeyValue(ctx, jetstream.KeyValueConfig{
|
|
Bucket: CheckRunningBucket,
|
|
Description: "the pull request checks the builders are running now (novox/hq ADR 0406): `<pull request>` holds the " +
|
|
"head and trunk commit, the builder and its build; put by the builder when it starts and at each heartbeat, " +
|
|
"deleted when the check ends, gone within two minutes of a builder that died; written by the build seat's holders",
|
|
History: 1,
|
|
TTL: CheckRunningKeptFor,
|
|
MaxValueSize: 4 << 10,
|
|
MaxBytes: 8 << 20,
|
|
Storage: jetstream.FileStorage,
|
|
}); err != nil {
|
|
return fmt.Errorf("asserting bucket %s: %w", CheckRunningBucket, err)
|
|
}
|
|
// **The merges mesh-delivery announced: keys that age** (ADR 0420 §7, issue 1025). One message a key; a week.
|
|
if _, err := js.CreateOrUpdateKeyValue(ctx, jetstream.KeyValueConfig{
|
|
Bucket: DeliveryMergesBucket,
|
|
Description: "the merges mesh-delivery announced it made, each with the ask whose Approve it merged on (novox/hq " +
|
|
"ADR 0420): `<owner>/<repo>/<number>/<head sha>` holds the announcement, kept a week; what lets the build " +
|
|
"of a moved module register it from its new repository; written by the controller alone",
|
|
History: 1,
|
|
TTL: DeliveryMergesKeptFor,
|
|
MaxValueSize: 16 << 10,
|
|
MaxBytes: 16 << 20,
|
|
Storage: jetstream.FileStorage,
|
|
}); err != nil {
|
|
return fmt.Errorf("asserting bucket %s: %w", DeliveryMergesBucket, err)
|
|
}
|
|
// **The chain: no age, no removal, history 64, no cut** (ADR 0408 §4 to §6). A key is never removed, so no purge
|
|
// marker needs an age and a key that expired would be a record the bus forgot.
|
|
for _, c := range []struct {
|
|
bucket, why string
|
|
bytes int64
|
|
}{
|
|
{BuildsBucket, "every build, one key each by its id, with its repository, commit, reuse key and what it " +
|
|
"published (novox/hq ADR 0408, design 47): written by the controller alone, never removed, read through `reads`",
|
|
ChainBuildsBytes},
|
|
{RolloutsBucket, "every rollout, one key each by its id: the commit it fixed per repository, its tiers, the " +
|
|
"builds it carried and the assignments it changed (novox/hq ADR 0408, design 47): written by the " +
|
|
"controller alone, never removed, read through `reads`", ChainRolloutsBytes},
|
|
} {
|
|
if _, err := js.CreateOrUpdateKeyValue(ctx, chainBucket(c.bucket, c.why, c.bytes, 0)); err != nil {
|
|
return fmt.Errorf("asserting bucket %s: %w", c.bucket, err)
|
|
}
|
|
}
|
|
// **The assignments keep the marker age they had as a mirror.** A stream that allows messages an age cannot be
|
|
// made to disallow them (the server refuses: "message TTL status can not be disabled"), so the bucket that was
|
|
// the assignments mirror is asserted with the age it was made with. Nothing is purged from it, so no marker is ever
|
|
// written and the age ends nothing.
|
|
if _, err := js.CreateOrUpdateKeyValue(ctx, chainBucket(AssignmentsBucket,
|
|
"which module is assigned to which node, keyed <node>.<module>, with the build, commit and rollout it runs "+
|
|
"there (novox/hq ADR 0408, design 47): written by the controller alone, never removed, read through `reads`",
|
|
ChainAssignmentBytes, MirrorMarkerTTL)); err != nil {
|
|
return fmt.Errorf("asserting bucket %s: %w", AssignmentsBucket, err)
|
|
}
|
|
// **The record mirrors: no age** (design 54 §5). A key is removed when its record is, by a purge whose
|
|
// marker lives a day; a key that expired would be a record the bus forgot while the store still holds it.
|
|
for _, m := range []struct{ bucket, why string }{
|
|
{DeclaredConfigBucket, "each registered module's config declarations as its manifest holds them, keyed by " +
|
|
"the module (novox/hq ADR 0348, design 57 F3): written by the controller alone from its store when a " +
|
|
"registration changes them, read through `reads`"},
|
|
{PredefinedBucket, "the list of predefined keys the mesh published, the newest trunk build's of the " +
|
|
"catalogue, one key `list` (novox/hq ADR 0348 §7, design 57 K3): written by the controller alone from " +
|
|
"its store when a newer list is kept, read through `reads`"},
|
|
} {
|
|
if _, err := js.CreateOrUpdateKeyValue(ctx, jetstream.KeyValueConfig{
|
|
Bucket: m.bucket,
|
|
Description: m.why,
|
|
History: 1,
|
|
MaxValueSize: MirrorValueBytes,
|
|
MaxBytes: MirrorBytes,
|
|
Storage: jetstream.FileStorage,
|
|
LimitMarkerTTL: MirrorMarkerTTL,
|
|
}); err != nil {
|
|
return fmt.Errorf("asserting bucket %s: %w", m.bucket, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// chainBucket is the options of a bucket of the chain: sixty-four revisions of each key, no age, a value up to
|
|
// ChainValueBytes, on disk.
|
|
func chainBucket(bucket, why string, bytes int64, markers time.Duration) jetstream.KeyValueConfig {
|
|
return jetstream.KeyValueConfig{
|
|
Bucket: bucket,
|
|
Description: why,
|
|
History: ChainHistory,
|
|
MaxValueSize: ChainValueBytes,
|
|
MaxBytes: bytes,
|
|
Storage: jetstream.FileStorage,
|
|
LimitMarkerTTL: markers,
|
|
}
|
|
}
|
|
|
|
// EnsureLeaseBucket creates the lease's bucket if absent and brings its options to match (to-be 45
|
|
// §6). **Before the lease is taken, by any candidate**: it is the lease's own precondition, and asserting
|
|
// a bucket that exists changes nothing. File storage, so its revisions — the epochs — outlive a restart of
|
|
// the bus; one raised again from nothing is moved past the highest epoch issued when the lease is taken.
|
|
func EnsureLeaseBucket(ctx context.Context, js jetstream.JetStream) error {
|
|
if _, err := js.CreateOrUpdateKeyValue(ctx, jetstream.KeyValueConfig{
|
|
Bucket: LeaseBucket,
|
|
Description: "the controller's lease (novox/hq to-be 45 §6): the key `holder`, written by compare-and-set " +
|
|
"by the one controller instance that may act and renewed every five seconds; its revision when taken " +
|
|
"is the epoch every declaration and plan write carries",
|
|
History: 1,
|
|
TTL: LeaseTTL,
|
|
MaxValueSize: 4 << 10,
|
|
MaxBytes: 1 << 20,
|
|
Storage: jetstream.FileStorage,
|
|
}); err != nil {
|
|
return fmt.Errorf("asserting bucket %s: %w", LeaseBucket, err)
|
|
}
|
|
return nil
|
|
}
|