Delivery 2 deleted the move and chain_moved, so the layout of the builds under their modules is made by the controller's start (LayBuildsOut), recorded by the key layout.done in the builds bucket, retried every half minute with an urgent condition while it fails; the chain on the bus is refused until it exists. Its source readers use the module and pointer reads.
269 lines
12 KiB
Go
269 lines
12 KiB
Go
// Package chain keeps the controller's chain on the bus alone (novox/hq ADR 0408, design 47): module, commit, build,
|
|
// published artifact or package, rollout and assignment are one chain, a bucket each of the bus's key-value store,
|
|
// each the only source of its kind, written by the controller that holds the lease by compare-and-set.
|
|
//
|
|
// **No key is removed.** A Backend has no delete and no purge (ADR 0408 §4): an assignment of a module taken off its
|
|
// node is kept, saying so. **A value is never cut** (§6): one over the largest the bus holds is refused, and the key
|
|
// stays as it was. **Each key keeps its earlier values** (§5), read back with History.
|
|
package chain
|
|
|
|
import (
|
|
"encoding/json"
|
|
"time"
|
|
)
|
|
|
|
// Ref names a build and the commit it was made from: one end of a change.
|
|
type Ref struct {
|
|
Build string `json:"build,omitempty"`
|
|
Commit string `json:"commit,omitempty"`
|
|
}
|
|
|
|
// Package is a release a build published to the package registry.
|
|
type Package struct {
|
|
Name string `json:"name"`
|
|
Version string `json:"version"`
|
|
}
|
|
|
|
// Published is what a build published: an artifact by its digest, or a package release by name and version.
|
|
type Published struct {
|
|
Artifact string `json:"artifact,omitempty"`
|
|
Package *Package `json:"package,omitempty"`
|
|
}
|
|
|
|
// Artifact is one thing a build made.
|
|
type Artifact struct {
|
|
Name string `json:"name"`
|
|
Kind string `json:"kind"`
|
|
Reference string `json:"reference"`
|
|
}
|
|
|
|
// ReadRepository is a repository a build read source from besides the module's own.
|
|
type ReadRepository struct {
|
|
Repository string `json:"repository"`
|
|
Ref string `json:"ref,omitempty"`
|
|
Paths []string `json:"paths,omitempty"`
|
|
Own bool `json:"own,omitempty"`
|
|
}
|
|
|
|
// Source is the build source a build read in one repository.
|
|
type Source struct {
|
|
Repository string `json:"repository,omitempty"`
|
|
Ref string `json:"ref,omitempty"`
|
|
Paths []string `json:"paths"`
|
|
}
|
|
|
|
// The states of a build record. A record in state asked is a request that no outcome has answered (issue 1091); one
|
|
// without a state is an outcome, and keeps the Request it was asked with.
|
|
const BuildAsked = "asked"
|
|
|
|
// The states of a request's Reask.
|
|
const (
|
|
ReaskPending = "pending"
|
|
ReaskClaimed = "claimed"
|
|
)
|
|
|
|
// Request is a build as it was asked for (novox/hq issue 325, ADR 0408 §1): kept in the build's own record under its id,
|
|
// first alone as state asked and then beside the outcome that answered it.
|
|
type Request struct {
|
|
// Repository and Seat are the source as the mesh spells it; Path is the module's directory in it, Ref the branch or
|
|
// commit asked, and Commit the commit that made the ask, where one did.
|
|
Repository string `json:"repository,omitempty"`
|
|
Seat string `json:"seat,omitempty"`
|
|
Path string `json:"path,omitempty"`
|
|
Ref string `json:"ref,omitempty"`
|
|
Commit string `json:"commit,omitempty"`
|
|
// For says who asked: merge, plan, build (a person), assign.
|
|
For string `json:"for,omitempty"`
|
|
// NotAsked is why the ask could not be handed over; OutcomeUnknown why an asker that handed it over stopped waiting.
|
|
NotAsked string `json:"not-asked,omitempty"`
|
|
OutcomeUnknown string `json:"outcome-unknown,omitempty"`
|
|
At time.Time `json:"at,omitzero"`
|
|
AtTerminal bool `json:"at-terminal,omitempty"`
|
|
// Reask is, on a request the move found never handed over, "pending" until the controller asks it again, "claimed"
|
|
// while it does, and the build id it was asked as once it has: published once, never twice (issue 981 note 7 Q5).
|
|
Reask string `json:"reask,omitempty"`
|
|
}
|
|
|
|
// Build is one attempt, whichever way it went, kept under its id. It names its module, its repository and commit, its
|
|
// reuse key (novox/hq ADR 0350) and what it published (ADR 0408 §1).
|
|
type Build struct {
|
|
ID string `json:"id"`
|
|
State string `json:"state,omitempty"`
|
|
Repository string `json:"repository"`
|
|
Ref string `json:"ref,omitempty"`
|
|
Module string `json:"module,omitempty"`
|
|
Commit string `json:"commit,omitempty"`
|
|
On string `json:"on,omitempty"`
|
|
Path string `json:"path,omitempty"`
|
|
Manifest json.RawMessage `json:"manifest,omitempty"`
|
|
// ManifestDigest names the manifest in the manifests bucket (ManifestKey); a record that has it keeps no Manifest.
|
|
ManifestDigest string `json:"manifest-digest,omitempty"`
|
|
Against []string `json:"against,omitempty"`
|
|
Mirrored []string `json:"mirrored,omitempty"`
|
|
Read []ReadRepository `json:"read,omitempty"`
|
|
Sources []Source `json:"sources,omitempty"`
|
|
SourceFingerprint string `json:"source-fingerprint,omitempty"`
|
|
Failed string `json:"failed,omitempty"`
|
|
Made []Artifact `json:"made,omitempty"`
|
|
Asked time.Time `json:"asked,omitzero"`
|
|
At time.Time `json:"at,omitzero"`
|
|
ReuseKey string `json:"reuse-key,omitempty"`
|
|
Published *Published `json:"published,omitempty"`
|
|
Request *Request `json:"request,omitempty"`
|
|
}
|
|
|
|
// Commit is one repository's commit a rollout carries.
|
|
type Commit struct {
|
|
Repository string `json:"repository"`
|
|
Branch string `json:"branch,omitempty"`
|
|
Commit string `json:"commit"`
|
|
Merged time.Time `json:"merged,omitzero"`
|
|
}
|
|
|
|
// Change is one assignment a rollout changed: the build and commit it ran before and the ones it runs after.
|
|
type Change struct {
|
|
Node string `json:"node"`
|
|
Module string `json:"module"`
|
|
Before Ref `json:"before"`
|
|
After Ref `json:"after"`
|
|
}
|
|
|
|
// Step is one tier sent to one machine.
|
|
type Step struct {
|
|
Tier int `json:"tier"`
|
|
Node string `json:"node"`
|
|
Sent *time.Time `json:"sent,omitempty"`
|
|
Reported *time.Time `json:"reported,omitempty"`
|
|
}
|
|
|
|
// Links are the fields a rollout gains on the chain: the builds it carried, the machine steps it has taken and the
|
|
// assignments it changed, each with the build before and after. The controller's plan embeds them, so the plan's own
|
|
// fields and these are one value (ADR 0408 §1).
|
|
type Links struct {
|
|
Builds []string `json:"builds,omitempty"`
|
|
Steps []Step `json:"steps,omitempty"`
|
|
Changed []Change `json:"changed,omitempty"`
|
|
}
|
|
|
|
// HeldMerge is a merge a rollout holds: the columns the controller's table of batched merges had, kept in the record of the
|
|
// batch or walk that answers it (novox/hq ADR 0276, issue 1092). A merge no plan answers (Waiting) stays in the record
|
|
// that held it last. Placed is when it entered this record, so one found in two records after a crash between the two
|
|
// writes is read from the one it entered last.
|
|
type HeldMerge struct {
|
|
Repository string `json:"repository"`
|
|
Branch string `json:"branch,omitempty"`
|
|
Commit string `json:"commit"`
|
|
Merged time.Time `json:"merged,omitzero"`
|
|
Heard time.Time `json:"heard,omitzero"`
|
|
Event json.RawMessage `json:"event,omitempty"`
|
|
Alone bool `json:"alone,omitempty"`
|
|
Waiting bool `json:"waiting,omitempty"`
|
|
Placed time.Time `json:"placed,omitzero"`
|
|
}
|
|
|
|
// Rollout is the controller's record of one batch's delivery, kept under the batch's id: the commit it fixed per
|
|
// repository, its tiers, the builds it carried and the assignments it changed (ADR 0408 §1). The controller's plan
|
|
// carries more (its modules, its times, its backups) beside these, under the same key.
|
|
type Rollout struct {
|
|
ID string `json:"id"`
|
|
State string `json:"state,omitempty"`
|
|
Commits []Commit `json:"commits,omitempty"`
|
|
Tiers [][]string `json:"tiers,omitempty"`
|
|
Links
|
|
}
|
|
|
|
// Assignment is one module on one node, kept under `<node>.<module>`. The first five fields are the ones its readers
|
|
// have always read, with the names they have always had (novox/hq ADR 0408 §3); the rest name the chain: the build, the
|
|
// artifact (by digest) or the package it runs, its commit, the rollout that put it there and since when.
|
|
type Assignment struct {
|
|
Node string `json:"node"`
|
|
Module string `json:"module"`
|
|
Assigned time.Time `json:"assigned,omitzero"`
|
|
RequiredBy string `json:"required-by,omitempty"`
|
|
Provisioning json.RawMessage `json:"provisioning,omitempty"`
|
|
|
|
Build string `json:"build,omitempty"`
|
|
Artifact string `json:"artifact,omitempty"`
|
|
Package *Package `json:"package,omitempty"`
|
|
Commit string `json:"commit,omitempty"`
|
|
Rollout string `json:"rollout,omitempty"`
|
|
Since time.Time `json:"since,omitzero"`
|
|
|
|
// Ended is when the assignment ended (an unassignment): absent while it stands. **No key of the chain is removed**
|
|
// (ADR 0408 §4), so an unassigned module keeps its key, saying so; every reader treats a value with Ended, or with a
|
|
// State, as no assignment. State is StatePending while the key holds an assignment waiting for its build, or one that
|
|
// was never made (issue 1094).
|
|
Ended time.Time `json:"ended,omitzero"`
|
|
State string `json:"state,omitempty"`
|
|
// Pending is the assignment's wait for its build (novox/hq ADR 0261), kept in the value of the key it waits for and, when
|
|
// it is made, carried into the value that stands: the record of what became of the wait.
|
|
Pending *Pending `json:"pending,omitempty"`
|
|
|
|
// PulledIn marks a standing value of a module that is on the machine only because another requires it, or because a
|
|
// send carried it: no time assigned, but a key all the same, so what the machine was sent of it is kept and read
|
|
// (issue 1109). Never a state: every reader treats a non-empty state as no assignment, and would take it off the machine.
|
|
PulledIn bool `json:"pulled-in,omitempty"`
|
|
// SentUnknown is when a send by hand made what the machine runs unknown (a declaration sent with no builds known): the
|
|
// commit is cleared with it. Absent normally; a send that names its builds clears it.
|
|
SentUnknown time.Time `json:"sent-unknown,omitzero"`
|
|
}
|
|
|
|
// StatePending is the State of a value that holds no assignment but a pending one.
|
|
const StatePending = "pending"
|
|
|
|
// The states of a pending assignment (novox/hq ADR 0261).
|
|
const (
|
|
PendingWaiting = "waiting"
|
|
PendingApplying = "applying"
|
|
PendingApplied = "applied"
|
|
PendingRefused = "refused"
|
|
PendingExpired = "expired"
|
|
PendingWithdrawn = "withdrawn"
|
|
)
|
|
|
|
// Pending is an assignment kept until its module is registered: the build it waits for, where that build is from, and the
|
|
// times of its claim and its end. Its id is p-<unix nanoseconds of Since>, unique for a key, which holds one at a time.
|
|
type Pending struct {
|
|
ID string `json:"id"`
|
|
State string `json:"state"`
|
|
Build string `json:"build,omitempty"`
|
|
Repository string `json:"repository,omitempty"`
|
|
Path string `json:"path,omitempty"`
|
|
Since time.Time `json:"since"`
|
|
Note string `json:"note,omitempty"`
|
|
Settled *time.Time `json:"settled,omitempty"`
|
|
Raised *time.Time `json:"raised,omitempty"`
|
|
Acknowledged *time.Time `json:"acknowledged,omitempty"`
|
|
Cleared *time.Time `json:"cleared,omitempty"`
|
|
// Earlier are the waits of this key that ended unmade and were replaced by this one before their condition was answered
|
|
// (taken back or cleared): each stays, with its own marks, so a replaced wait never leaves the operator without the
|
|
// condition it raised or was to raise. Dropped once answered.
|
|
Earlier []Pending `json:"earlier,omitempty"`
|
|
}
|
|
|
|
// Open says the assignment is still to be made.
|
|
func (p Pending) Open() bool { return p.State == PendingWaiting || p.State == PendingApplying }
|
|
|
|
// CanSettle says a pending assignment in state from may end in state to: a wait expires or is withdrawn, a claim becomes
|
|
// applied or refused, and nothing else.
|
|
func CanSettle(from, to string) bool {
|
|
switch from {
|
|
case PendingWaiting:
|
|
return to == PendingExpired || to == PendingWithdrawn
|
|
case PendingApplying:
|
|
return to == PendingApplied || to == PendingRefused
|
|
}
|
|
return false
|
|
}
|
|
|
|
// CanMove says a pending assignment in state from may go to state to without ending: claimed, or released again.
|
|
func CanMove(from, to string) bool {
|
|
return (from == PendingWaiting && to == PendingApplying) || (from == PendingApplying && to == PendingWaiting)
|
|
}
|
|
|
|
// Stands says the value is an assignment that holds: it has not ended and is not waiting in a state of its own.
|
|
func (a Assignment) Stands() bool { return a.Ended.IsZero() && a.State == "" }
|
|
|
|
// Key is an assignment's key.
|
|
func (a Assignment) Key() string { return a.Node + "." + a.Module }
|