A wait that ended unmade and whose condition nobody answered is carried by the wait that replaces it, so the operator is never left without the condition for a module still not on its machine, and taking it back, its condition and the orphan sweep read it there. The condition id stays node.module.<id> with the string id. clearOrphaned says a taken-back wait too. TestAnAssignmentWaitingForItsBuildIsOneKeyOfTheAssignmentsBucket enters through the assign verb and reads the key before and after the build's outcome.
259 lines
12 KiB
Go
259 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"`
|
|
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"`
|
|
}
|
|
|
|
// 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 }
|