Documentation
¶
Overview ¶
Package state is billet's durable control-plane store.
The default store is SQLite, deliberately. A billet deployment has one authoritative server process, and the data it keeps — the capacity ledger, job history, and advisory pointers to node-local volumes — is small and hot. SQLite gives that ACID semantics with no daemon to operate.
THE ENGINE IS A SEAM AND THE INVARIANTS ARE NOT. What differs between engines lives behind the backend interface in backend.go; everything below is billet's own and is enforced here, once, for all of them:
- ONE authoritative process. An exclusive lock on the state directory is held for the lifetime of DB, so a second server exits immediately instead of taking turns writing conflicting scheduling decisions. A database's own ability to serialize writes prevents simultaneous writes; it does not prevent two control planes.
- ONE writer within the process. Mutations go through a single-connection pool so an allocation decision serializes. Reads go through a separate pool exposed only as a query interface, so a caller cannot write through it by accident.
- Durability actually verified. The settings that were asked for are read back and the store fails closed on any mismatch — on SQLite that is what catches a state directory on a network filesystem, where WAL cannot work at all because its shared-memory coordination assumes a single host.
- Contention is a RACE, not a verdict, and is retried rather than returned, with opposite patience for a control plane and for an operator command.
What this store is NOT: authoritative for cache generation pointers. Those live on the node that owns the underlying volume, because a commit here cannot be made atomic with a snapshot on a remote machine. What is kept here is advisory metadata used for scheduling affinity.
Index ¶
- Constants
- Variables
- func AdoptDeploymentID(stateDir, id string) (string, error)
- func ClearMaintenanceFence(stateDir, reason string) error
- func DeploymentID(stateDir string) (string, error)
- func DirectoryLockPath(stateDir string) string
- func LatestSchemaVersion() int
- func LedgerPath(stateDir string) string
- func MaintenanceFencePath(stateDir string) string
- func MaintenanceFenceReason(stateDir string) (string, bool, error)
- func PeekDeploymentID(stateDir string) (string, bool, error)
- func RefuseUnknownVersions(applied []AppliedMigration) error
- func WriteMaintenanceFence(stateDir, reason string) (bool, error)
- func WriterBarrier(ctx context.Context, stateDir string) error
- type ActionsCacheScope
- type Admission
- type AdmissionMode
- type AppliedMigration
- type ControllerClaim
- type CredentialSweepRecord
- type DB
- func Open(ctx context.Context, stateDir string, opts ...OpenOption) (*DB, error)
- func OpenAdmin(ctx context.Context, stateDir string, opts ...OpenOption) (*DB, error)
- func OpenMaintenance(ctx context.Context, stateDir string, opts ...OpenOption) (*DB, error)
- func OpenPostgres(ctx context.Context, stateDir, dsn string, opts ...OpenOption) (*DB, error)
- func OpenPostgresAdmin(ctx context.Context, stateDir, dsn string, opts ...OpenOption) (*DB, error)
- func OpenPostgresProbe(ctx context.Context, stateDir, dsn string, opts ...OpenOption) (*DB, error)
- func OpenPostgresStandby(ctx context.Context, stateDir, dsn string, opts ...OpenOption) (*DB, error)
- func (db *DB) AcknowledgePendingCompletion(ctx context.Context, tier string, requestID, messageID int64) error
- func (db *DB) ActionsCacheAllowed(ctx context.Context, owner, repository string) (bool, error)
- func (db *DB) Admission(ctx context.Context) (Admission, error)
- func (db *DB) AppliedMigrations(ctx context.Context) ([]AppliedMigration, error)
- func (db *DB) AwaitController(ctx context.Context, holder, deployment string, ...) (ControllerClaim, error)
- func (db *DB) ClaimController(ctx context.Context, holder, deployment string) (ControllerClaim, error)
- func (db *DB) Close() error
- func (db *DB) ControllerHolder(ctx context.Context) (ControllerClaim, error)
- func (db *DB) CredentialSweeps(ctx context.Context) ([]CredentialSweepRecord, error)
- func (db *DB) DeploymentBinding(ctx context.Context) (string, error)
- func (db *DB) ForceTargets(ctx context.Context, generation int64) ([]ForceTarget, error)
- func (db *DB) ForgetScaleSet(ctx context.Context, target, group, label string) error
- func (db *DB) IntegrityCheck(ctx context.Context) error
- func (db *DB) LatestForceDestroy(ctx context.Context) (ForceDestroy, bool, error)
- func (db *DB) LeadershipLost() bool
- func (db *DB) LeadershipLostSignal() <-chan struct{}
- func (db *DB) OpenForceDestroy(ctx context.Context) (ForceDestroy, bool, error)
- func (db *DB) PendingCompletions(ctx context.Context, tier string) ([]PendingCompletion, error)
- func (db *DB) PendingForceTargets(ctx context.Context, generation int64, tier string) ([]ForceTarget, error)
- func (db *DB) PingContext(ctx context.Context) error
- func (db *DB) PutPendingCompletion(ctx context.Context, completion PendingCompletion) (PendingCompletionDisposition, error)
- func (db *DB) Reader() Querier
- func (db *DB) RecordCredentialSweep(ctx context.Context, rec CredentialSweepRecord) error
- func (db *DB) RecordScaleSet(ctx context.Context, rec ScaleSetRecord) error
- func (db *DB) ReleaseWatermark(ctx context.Context) (string, string, error)
- func (db *DB) RequestForceDestroy(ctx context.Context, req ForceDestroyRequest) (ForceDestroy, error)
- func (db *DB) Resume(ctx context.Context, req ResumeRequest) (Admission, error)
- func (db *DB) RetirePendingCompletion(ctx context.Context, tier string, requestID, messageID int64) error
- func (db *DB) ScaleSets(ctx context.Context, target string) ([]ScaleSetRecord, error)
- func (db *DB) Seal(ctx context.Context, req SealRequest) (Admission, error)
- func (db *DB) SetActionsCacheEnabled(ctx context.Context, scope ActionsCacheScope, enabled bool) error
- func (db *DB) SetReleaseWatermark(ctx context.Context, release string) error
- func (db *DB) SettleForceTarget(ctx context.Context, generation int64, leaseID, state, detail string) error
- func (db *DB) SnapshotInto(ctx context.Context, path string) error
- func (db *DB) Tx(ctx context.Context, fn func(*sql.Tx) error) error
- func (db *DB) VerifyDeploymentBinding(ctx context.Context, deployment string) error
- func (db *DB) View(ctx context.Context, fn func(Querier) error) error
- type DeploymentLock
- type DirectoryLock
- type ForceDestroy
- type ForceDestroyRequest
- type ForceTarget
- type IdentityProbe
- type LedgerContents
- type LockOptions
- type OpenOption
- type PendingCompletion
- type PendingCompletionDisposition
- type Querier
- type ReadOps
- type ResumeRequest
- type ScaleSetRecord
- type SealRequest
- type WriteOps
Constants ¶
const ( // ProvenanceLocalDown is a seal held by a lifecycle command for the duration // of its own shutdown. A later successful `billet local up` clears it. ProvenanceLocalDown = "local-down" // ProvenanceOperator is a seal somebody took deliberately. It survives a // control-plane restart and a lifecycle command, and only an explicit resume // clears it — silently reopening admission because a service was restarted is // exactly the failure this exists to prevent. ProvenanceOperator = "operator" )
Provenance says who took a seal, which decides who may clear it.
const ( ForceRequested = "requested" ForceCompleted = "completed" )
Where one force-destroy request has got to.
const ( // ForceTargetPending is a lease no listener has acted on yet. ForceTargetPending = "pending" // ForceTargetDestroyed is compute a listener confirmed gone. ForceTargetDestroyed = "destroyed" // ForceTargetFailed is a lease whose destroy did not confirm. // // NOT "UNKNOWN", AND THE DISTINCTION IS THE POINT. A failed destroy is not // proof the container survived, so nothing here releases capacity on it: the // lease stays charged and the row says so, which is what an operator reads // when a force reports that it did not finish. ForceTargetFailed = "failed" )
The disposition of one lease inside a force-destroy request.
Variables ¶
var ErrAdmissionGeneration = errors.New("state: the admission generation moved")
ErrAdmissionGeneration means the seal changed between reading it and acting on it, so the caller was about to undo a decision it never saw.
var ErrAdmissionProvenance = errors.New("state: this seal was taken by somebody else")
ErrAdmissionProvenance means the caller is not entitled to clear this seal. It is a sentinel so a command can add the remedy for its own case — the state layer knows which provenance holds the seal, and only the caller knows which command clears it.
var ErrConflict = errors.New("state: compare-and-swap conflict")
ErrConflict is returned when a compare-and-swap loses. Callers should re-read and decide, never blindly retry — a lost cache publication is correct behaviour, not a transient failure.
var ErrControllerHeld = errors.New("state: another billet process is this deployment's controller")
ErrControllerHeld means another process is already this deployment's controller.
ITS OWN ERROR because the remedy is specific and nothing else in this package implies it: stop the other controller, or fix the configuration that pointed two of them at one ledger. An ordinary failure to open would send an operator looking at the database.
var ErrDeploymentLocked = errors.New("state: this deployment is already running on this host")
ErrDeploymentLocked means another billet is already running under this deployment identity.
var ErrForceDestroyNotSealed = errors.New("state: this deployment is still admitting work")
ErrForceDestroyNotSealed means the deployment is still admitting work.
A FORCE ENUMERATES A SET AND THEN ASKS A PERSON ABOUT IT, so admission has to be closed before the enumeration or a job admitted in between is destroyed without ever appearing in the diagnostic the operator approved — or, worse, starts just after the destroy pass and survives a force that reported success.
var ErrForceDestroyOpen = errors.New("state: a force-destroy request is already open")
ErrForceDestroyOpen means another force-destroy request has not finished.
A SENTINEL BECAUSE THE REMEDY IS THE CALLER'S. Two concurrent forces would each enumerate a target set the other was midway through destroying, and neither diagnostic would describe what happened — but only the command knows how to say "watch the one that is running with `billet status`".
var ErrForeignLedger = errors.New("state: this ledger belongs to another deployment")
ErrForeignLedger means these rows belong to a different deployment than the identity directory this process is using.
ITS OWN ERROR because the remedy is specific and nothing else implies it: one of the two halves is pointed at the wrong place, and which half is the operator's to decide. An ordinary failure to open would send them to the database.
var ErrLeadershipLost = errors.New("state: this process is no longer this deployment's controller")
ErrLeadershipLost means this process WAS this deployment's controller and no longer is: a successor has claimed, and every write from here is refused.
ITS OWN ERROR, AND DISTINCT FROM ErrControllerHeld, because the two arrive at opposite moments and ask opposite things of a caller. ErrControllerHeld is a process that never started; this is one that has been running, may have compute in flight, and must now act on nothing. It must also be distinguishable from alloc.ErrFenced and alloc.ErrLeaseNotFound, which are statements about ONE LEASE that a listener answers by dropping it — this is a statement about the whole deployment, and dropping anything on it would be a fenced controller taking one last authoritative decision.
var ErrLocked = errors.New("state: another billet process holds this state directory")
ErrLocked means another billet process already owns this state directory.
var ErrMaintenance = errors.New("state: the ledger is fenced for host maintenance")
ErrMaintenance means a host upgrade fenced the ledger against operator traffic.
var ErrReleaseBehind = errors.New("state: this billet is older than the release that " +
"last served this ledger")
ErrReleaseBehind means this binary is older than the release that last served the ledger it is opening.
ITS OWN ERROR because the remedy is specific: install the release the ledger names or newer, restore the archive that matches this binary, or downgrade on purpose with `billet host-upgrade --allow-downgrade`. A bare failure to open would send an operator to the database.
var ErrSchemaBehind = errors.New("state: the ledger needs migrating and another billet process is using it")
ErrSchemaBehind means the ledger needs a migration this process is not allowed to apply, because another billet is already using it.
Its own error rather than a string, so an operator command can tell "the running control plane is older than this binary" — which is fixed by restarting it — from an ordinary failure to open the database.
var ErrStandby = errors.New(
"state: this process is a standby and has not claimed this deployment's controller")
ErrStandby means this handle was opened as a standby and has not yet claimed the controller, so it may not write.
ITS OWN ERROR, AND THE THIRD IN THIS FAMILY, because all three arrive at different moments and none of the other two describes this one. ErrControllerHeld is a process that could not start; ErrLeadershipLost is one that was replaced; this is one that has not started YET and is waiting on purpose. A standby that reported either of the others would look like a fault rather than a design.
Functions ¶
func AdoptDeploymentID ¶
AdoptDeploymentID records an identity this installation was handed.
A NODE DOES NOT GET TO INVENT ITS DEPLOYMENT, and letting it was a defect that made standalone enrollment impossible. DeploymentID mints a random identity when a state directory has none — right for a control plane, which is where an installation begins, and wrong for a node, which JOINS one. A fresh node minted its own, the control plane compared it with its own and refused the registration, and nothing in the enrollment instructions could have prevented it: the bundle carried a certificate and no identity.
So the certificate carries it, and this writes it down. Refuses rather than overwrites when the directory already holds a DIFFERENT one — that state directory's containers are labelled with the old identity, and quietly relabelling the node would orphan every one of them.
func ClearMaintenanceFence ¶
ClearMaintenanceFence reopens the ledger, and only for the reason that fenced it.
THE REASON IS CHECKED, for the same argument admission provenance makes one layer up: clearing a fence somebody else established reopens a ledger in the middle of their operation, and the evidence is a write landing during a window that was supposed to be closed.
func DeploymentID ¶
DeploymentID returns the stable identity of the billet installation rooted at this state directory, creating it on first use.
THIS IS WHAT MAKES DESTRUCTIVE RECONCILIATION SAFE, and the reason the node name cannot be used for it: the node name defaults to the hostname, so two installations on one machine carry the same name while keeping separate state directories, and the process lock guards a directory rather than a name. Labelling compute by node name would let one installation enumerate the other's containers, find their lease ids absent from its own database, and destroy live jobs. It cannot be derived from configuration either — a derived label would change under the operator's feet and orphan every running container.
RANDOM rather than derived from the path, so copying a state directory does not silently produce two installations claiming one identity: the copy carries the original's id, which is what makes it DETECTABLE — LockDeployment keys a host-wide lock on the id, so running the copy alongside the original fails as a lock conflict.
func DirectoryLockPath ¶
DirectoryLockPath is where LockStateDir puts its lock.
EXPORTED SO NOBODY COPIES THE NAME. A privileged `billet local restore` takes this lock as root and thereby CREATES the file inside a directory the service account owns, so what hands it back afterwards has to name the same file — and a second literal elsewhere is a control plane that cannot take its own lock, discovered on the first start after a restore.
func LatestSchemaVersion ¶
func LatestSchemaVersion() int
LatestSchemaVersion is the highest migration this build knows.
PUBLISHED IN THE RELEASE MANIFEST, which is the reason it is exported. A candidate release carries this number so an updater can refuse, BEFORE it stops anything, a binary that would inherit a ledger it cannot open: migrations are append-only and `migrate` refuses a database carrying a version it has never heard of, so a release behind the installed schema starts, refuses, and leaves the control plane down with a database no installed binary can read.
DERIVED FROM THE MIGRATION LIST rather than written down beside it. A constant somebody has to remember to bump is a constant that is wrong exactly once — on the release that adds a migration, which is the only release where the number matters.
ONE NUMBER DESCRIBES THE BINARY, NOT THE DEPLOYMENT'S BACKEND, which is why this reads a timeline rather than taking one. Two binaries compare this across an upgrade and neither knows what backend the other was configured for, so a backend-dependent answer would make the fence compare two different scales. Every timeline declares the same versions, so the SQLite one is read here as the canonical numbering rather than as a statement about storage.
func LedgerPath ¶ added in v0.6.0
LedgerPath is where the SQLite ledger lives in a state directory, for a caller that must know whether one exists without being handed one.
func MaintenanceFencePath ¶
MaintenanceFencePath is where the fence lives, so a caller can name it in a diagnostic without knowing the filename.
func MaintenanceFenceReason ¶
MaintenanceFenceReason reports what a fence says it is for, and whether there is one.
A READER, BECAUSE THE ALTERNATIVE IS A HAND-PARSE. WriteMaintenanceFence already compares this body exactly — that comparison is what stops one operation replacing another's fence — so a caller that needs to know WHOSE fence it found would otherwise open the file and trim it themselves, which is a second reading of a format this package owns. It answers three states rather than two: present with a reason, absent, or unreadable, because "billet could not tell" must never become "there is no fence" for a caller about to decide whether a directory is safe to act on.
func PeekDeploymentID ¶
PeekDeploymentID reads the deployment identity WITHOUT minting one, for a read-only caller such as `billet init iam` that must not create an identity as a side effect of asking. found is false when the state directory has none yet (the caller decides whether that is an error); an existing-but-invalid file is returned as an error rather than a miss.
func RefuseUnknownVersions ¶
func RefuseUnknownVersions(applied []AppliedMigration) error
RefuseUnknownVersions rejects a migration set carrying a version this binary has never heard of.
EXPORTED FOR THE RESTORE PLANNER, and deliberately the SAME rule the migrator and the schema verifier apply — written twice, they drift, and the failure would be a restore that installs a ledger the control plane then refuses to start against.
ITS DIAGNOSTIC IS NOT ErrSchemaBehind, and keeping them apart is the point. ErrSchemaBehind means a running plane is holding a ledger that needs migrating; this means the thing in front of you was written by a NEWER billet and the remedy is a newer binary, not a restart. IT IS THE ONE COLD ENTRY POINT INTO THIS RULE, so it asks whether this binary can read its own migrations before answering. Everything else that reaches refuseUnknownVersions came through openDir, which refuses first.
Without that, a binary whose embedded set failed to load answers this question from an EMPTY known set, and both of its answers are wrong: an archive carrying migrations is refused as "written by a newer version", which sends an operator after a newer binary for a fault in the one they have; and an archive whose applied set is empty is ACCEPTED, because the loop has nothing to iterate — the restore planner reads that as permission to install a ledger this binary could never open. IT ANSWERS FROM THE SQLITE TIMELINE AND IS STILL BACKEND-INDEPENDENT, because the rule compares VERSIONS and a version is the same identity on every timeline. The cold caller has an archive and no open ledger, so it cannot know which engine wrote the set it is asking about — asking a question only versions can answer is what makes that survivable.
func WriteMaintenanceFence ¶
WriteMaintenanceFence closes this ledger to every handle, including ones that are already open.
THE FILE IS THE FENCE, and that is what makes it reach a handle somebody else is holding: Tx and View consult it on entry, so an operator command that opened through OpenAdmin before this was written finds the fence on its next transaction rather than committing into a ledger being replaced underneath it. The directory lock cannot do that — OpenAdmin deliberately proceeds without it.
IDEMPOTENT ON ITS OWN REASON AND REFUSING ON ANYBODY ELSE'S. A fence already standing belongs to whoever wrote it; overwriting it would let a restore silently adopt an Ansible host upgrade's fence and then CLEAR it at the end, reopening a ledger mid-upgrade.
IT REPORTS WHETHER THIS CALL CREATED THE FENCE, and the caller needs that to know what it may undo. An operation that fences a ledger, fails before changing anything, and leaves the fence standing has taken a healthy control plane offline over an operation that did nothing — so a caller clears only a fence it established, and never one that predated it.
ALL OR NOTHING. A write or sync that fails partway would otherwise leave an empty or truncated fence, which is worse than either state: it closes the ledger and no caller can recognise it as its own to clear. The file is removed on any failure after creation — safe by the one argument this package accepts for a pathname removal, that the O_EXCL open is what created the name.
func WriterBarrier ¶
WriterBarrier proves that every write transaction which began before the fence has finished.
THE FENCE IS NOT ENOUGH BY ITSELF. It is consulted when a transaction STARTS, so a handle that got past that check a moment earlier is still free to commit. Taking the write lock is the proof: BEGIN IMMEDIATE acquires it up front, so holding it for an instant means nobody else is mid-write.
IT WILL NOT CREATE A LEDGER. A caller asking this about a directory with no billet.db must not be handed one — sql.Open would create the file, and the next thing a restore does is decide whether a ledger is already there.
Types ¶
type ActionsCacheScope ¶
ActionsCacheScope names one organisation or one repository below it.
type Admission ¶
type Admission struct {
Mode AdmissionMode
Generation int64
Provenance string
Reason string
Actor string
ChangedAt string
}
Admission is the deployment's current admission state.
func PeekAdmission ¶
PeekLedger reports whether a ledger file holds anything a deployment wrote.
WHAT IT EXISTS FOR: `billet check` creates billet.db and its schema on a host nobody has commissioned yet, so the presence of the FILE cannot be what stops a restore. The deployment identity and the CA marker are what prove a directory is committed; this is what proves the ledger beside them is the preflight's and not somebody's capacity record.
EVERY TABLE, DISCOVERED FROM sqlite_master RATHER THAN LISTED. A hand-written list goes stale the next time a migration adds a table, and the direction it goes stale in is the dangerous one: a new table full of rows would be invisible and the ledger would read as empty.
Two tables are exempt and both are schema rather than content: schema_migrations is the bookkeeping every ledger has, and admission is a singleton its own migration INSERTS, so a pristine ledger has exactly one row in it — which is checked rather than assumed. PeekAdmission reads a ledger's admission row without taking the directory lock, without migrating and without consulting the fence.
FOR THE ONE CALLER THAT HOLDS THE LOCK ALREADY AND MUST STILL ASK. A restore or a recovery finishes with the directory lock in hand and the fence still up, and the last thing it has to establish is that the ledger it is about to unfence will not admit work. Every ordinary route is closed to it: OpenAdmin HONOURS the fence, which is its whole job, and OpenMaintenance crosses the fence but takes the directory lock — which a second descriptor in the same process is refused. Reaching for OpenMaintenance before taking the lock is what this replaces, and that had three costs: it MIGRATED a ledger before the caller had established the operation was even its own, it left a window between the answer and the lock in which admission could change, and it made the check impossible to run at the moment it is acted on.
IT DOES NOT MIGRATE, AND IT DOES NOT VERIFY THE SCHEMA, which is worth saying out loud rather than leaving to be assumed: what comes back is whatever this build's admission query reads out of that file, and a ledger from a NEWER billet can answer it perfectly well. The caller's schema story has to come from somewhere else — for `billet local recover` it is the OpenMaintenance this runs behind, which migrates the restored ledger before anything asks it this question. Do not read a successful answer here as "billet understands this ledger".
func ReadAdmission ¶
ReadAdmission reads the admission state through any querier, so the same answer serves a status command on the read-only pool and an allocation decision inside its own write transaction.
The caller decides what an error means. This does NOT fold a failed read into a mode, because the two are different facts: a read that failed says nothing about the deployment, and a caller that must fail closed and a caller that must report uncertainty need to tell them apart.
type AdmissionMode ¶
type AdmissionMode int
AdmissionMode is whether the deployment is accepting new work.
UNKNOWN IS A VALUE, and it is the zero value deliberately. A caller that cannot read the ledger has not learned that admission is open, and the whole point of a seal is that the failure to observe it must not become permission to admit. Every consumer therefore has to say what it does about Unknown, rather than inheriting an answer from a bool.
const ( AdmissionUnknown AdmissionMode = iota AdmissionOpen AdmissionSealed )
func (AdmissionMode) String ¶
func (m AdmissionMode) String() string
type AppliedMigration ¶
type AppliedMigration struct {
Version int `json:"version"`
Name string `json:"name"`
Checksum string `json:"checksum"`
}
AppliedMigration is one row of a ledger's migration bookkeeping, as a caller outside this package sees it.
EXPORTED BECAUSE A BACKUP HAS TO RECORD IT AND A RESTORE HAS TO JUDGE IT. The unexported appliedMigration is keyed by version in a map for the migrator's own use; this is the ordered, self-describing form that goes into an archive manifest and comes back out of one.
func PeekMigrations ¶
func PeekMigrations(ctx context.Context, dbPath string) ([]AppliedMigration, error)
PeekMigrations reads the applied-migration set out of a database FILE.
IT MUST NOT BE state.Open. This is asked about a snapshot, and about a target directory a restore has not committed to yet — Open creates the directory, chmods it, takes the process lock and MIGRATES, so using it to ask a question would upgrade a stopped ledger on the way to telling the operator the restore is refused.
query_only, and the file must already exist: a caller asking about a database that is not there must not be handed one that now is.
type ControllerClaim ¶
type ControllerClaim struct {
// Holder is whatever the claiming process called itself. It is a
// DIAGNOSTIC, not an identity: it decides nothing, and nothing compares it.
// A refusal quotes it so an operator knows which machine to look at.
Holder string
// Epoch goes up by one every time the claim is taken, and never down.
//
// IT IS THE FENCE. Every write transaction this handle opens re-reads the
// recorded epoch and refuses if it has moved — see checkLeadership — so a
// controller that lost its exclusion without noticing is REFUSED rather than
// detected. That is what makes it a fencing token rather than a diagnostic,
// and it is why the value is computed by the ledger rather than supplied by a
// caller: two controllers agreeing on a number is the one thing a fencing
// token must never allow.
//
// WHAT STILL HAS NO ELECTION BEHIND IT is the promotion. Nothing here decides
// that a leader is dead or that a follower should take over, and the
// controller election is where that lands; this is the half it has to be built
// on.
Epoch int64
}
ControllerClaim is proof that this process may make scheduling decisions for this deployment.
type CredentialSweepRecord ¶
type CredentialSweepRecord struct {
Region string
Path string
// SweptAt is when the pass ran.
SweptAt time.Time
// Removed is what the pass deleted; RemovedTotal accumulates across passes and
// is only ever read back, never written by a caller.
Removed int
RemovedTotal int
// Kept is what is waiting on a lease that is open or too recently closed.
Kept int
// Unaccounted names the ledger has never heard of, which a person has to look
// at: a ledger restored from an older backup, or a foreign writer on the path.
Unaccounted int
// ForeignNames are entries under the path that are not billet's at all.
ForeignNames int
// Error is why the pass stopped, or empty for one that completed. A pass that
// could not read the ledger keeps everything and says so here.
Error string
}
CredentialSweepRecord is what one pass of the control plane's sweep over one Parameter Store path found and did.
A RECORD, NOT A DECISION. Nothing reads it to decide whether a parameter may be deleted; it exists because `billet status` runs in another process and a count held in the control plane's memory would be invisible to it.
type DB ¶
type DB struct {
// contains filtered or unexported fields
}
DB wraps two connection pools over one ledger: a single-connection writer so mutations serialize, and a read-only pool so status reads never queue behind an allocation.
func Open ¶
Open prepares the state directory, takes the exclusive process lock, opens the database, and verifies that the durability pragmas actually took effect.
The caller's context bounds startup only; it does not own the returned DB.
func OpenAdmin ¶
OpenAdmin opens the ledger for a ONE-SHOT OPERATOR COMMAND.
THE DIRECTORY LOCK EXISTS TO STOP TWO CONTROL PLANES, not to stop two processes. Its whole argument is that SQLite's single-writer rule prevents simultaneous writes but does not prevent two billets both long-polling GitHub and taking turns writing conflicting scheduling decisions. A command that approves an enrollment or forces one quarantined lease back is not a second control plane: it makes no scheduling decisions and holds nothing open, and the writes it does make are ordinary transactions SQLite serialises against the server's own. Some commands commit more than one — `nodes revoke` records each older serial before withdrawing them — which is why the give-up diagnostic says what already stands rather than claiming a no-op.
Opening through Open instead meant every such command failed against a live deployment — `nodes pending|approve|revoke`, `ca token|issue|revoke| revocations`, `leases quarantined|release`, and `check`. The sharpest case was `leases release --force`, whose entire purpose is reclaiming capacity a quarantine has stranded on a RUNNING deployment: the only documented remedy required stopping the thing that was holding the capacity.
It still takes the lock WHEN IT IS FREE, which matters on a fresh control plane: an operator runs `billet ca issue` before the server has ever started, so whoever gets there first has to create the schema, and two commands racing to create it must not both try.
func OpenMaintenance ¶
OpenMaintenance opens the control-plane store for a quiescent upgrade probe. It bypasses a host-upgrade fence without admitting operator or workload writes.
func OpenPostgres ¶
OpenPostgres opens a deployment whose ledger lives in PostgreSQL.
THE STATE DIRECTORY IS STILL A DIRECTORY, and that is not a leftover: the deployment identity, the node-wire CA, the process lock and the maintenance fence are local files under every backend. Only the SQL rows move. What the directory stops holding is billet.db.
THE PROCESS LOCK STILL APPLIES AND IS STILL NOT ENOUGH. It excludes a second control plane on THIS machine, which is the whole of the problem on SQLite and half of it here — a second controller on another host would take its own directory's lock happily. What closes the other half is the pair the ledger carries: the session advisory lock in claimController, which stops a second controller starting, and the epoch in ControllerClaim, which stops the first one writing once a second has legitimately taken over.
func OpenPostgresAdmin ¶
OpenPostgresAdmin is the operator-command form, with the same asymmetry OpenAdmin describes: it proceeds without the directory lock when a control plane holds it, and then VERIFIES the schema rather than migrating it.
func OpenPostgresProbe ¶ added in v0.6.0
OpenPostgresProbe opens the ledger for the host upgrade's quiescent probe on a deployment whose ledger is a database billet does not hold.
THE STANDBY'S OPEN, ALLOWED ACROSS THE FENCE. A candidate probing an external ledger may prove exactly what a standby proves — the DSN resolves, the schema is one it knows and not ahead of it, the deployment binding agrees, the release watermark admits it — and may claim nothing and migrate nothing, because the migration is the controller claim's right and happens when the candidate serves. Every write is refused, structurally, the way a standby's is. What it adds over a standby is crossing a host-upgrade fence, which the transaction may have raised in this host's identity directory; the fence reaches only local handles, so on this backend it is a courtesy rather than the exclusion it is on SQLite.
func OpenPostgresStandby ¶
func OpenPostgresStandby(ctx context.Context, stateDir, dsn string, opts ...OpenOption) (*DB, error)
OpenPostgresStandby opens the ledger for a control plane that is WAITING to become this deployment's controller.
IT TAKES THE DIRECTORY LOCK AND NOTHING ELSE. The lock still means what it always did — two billets must not manage one state directory on one host — and a standby is a control plane in waiting, so a second one here is the same mistake as a second controller. What it does NOT take is the controller exclusion, because taking it is precisely what promotion IS.
A HANDLE THAT CANNOT WRITE. Every write transaction is refused with ErrStandby until ClaimController succeeds; reads are allowed, so the process can report on itself and on the claim it is waiting for.
POSTGRESQL ONLY, AND THERE IS NO SQLITE FORM. A SQLite ledger is a file on local storage that a second machine cannot open at all, so a standby there would be a second process on one host waiting for a lock its own service manager already restarts it to take. Config refuses the pairing, and the absence of an entry point here is the same refusal one layer down.
func (*DB) AcknowledgePendingCompletion ¶
func (db *DB) AcknowledgePendingCompletion( ctx context.Context, tier string, requestID, messageID int64, ) error
AcknowledgePendingCompletion records that GitHub will not redeliver a message.
func (*DB) ActionsCacheAllowed ¶
ActionsCacheAllowed reports whether neither the organisation nor repository is blocked.
func (*DB) AppliedMigrations ¶
func (db *DB) AppliedMigrations(ctx context.Context) ([]AppliedMigration, error)
AppliedMigrations reads the migration set out of an OPEN handle.
THE LIVE DATABASE, WHICH PeekMigrations DELIBERATELY IS NOT. Peek opens a FILE, which is the right instrument for a snapshot: it reads back what was captured rather than what is happening now. This one is for the ledger there is no file of — a PostgreSQL deployment, where `billet local backup` writes an identity-only archive and the manifest records what the ledger carried at that moment as PROVENANCE.
WHAT IT ANSWERS IS TRUE OF ONE INSTANT AND THE DATABASE KEEPS MOVING, so a caller must not treat it as a description of an artifact. The one thing it may still decide is a refusal that is safe when stale in the OLD direction — see deployarchive.LedgerFacts.
On the reader pool: it is a read, and routing it through Tx would reserve the single writer slot while it scans.
func (*DB) AwaitController ¶
func (db *DB) AwaitController( ctx context.Context, holder, deployment string, onWaiting func(ControllerClaim), ) (ControllerClaim, error)
AwaitController waits until this process can become the deployment's controller, and then becomes it.
THIS IS THE ELECTION, AND THERE IS NOTHING ELSE TO IT. PostgreSQL releases a session advisory lock when the session ends, so a controller is dead when its session is — decided by the database rather than by billet — and a standby that keeps asking for that lock becomes the controller at the moment the incumbent's goes away. No lease, no renewal, no timeout, and no failure detector: a lease would need a number that decides whether a live controller is declared dead, and nothing here has one.
ONLY ErrControllerHeld IS WAITED OUT, and that distinction is the whole safety content. A held claim is the ordinary state a standby exists for and resolves by itself. Everything else — a ledger bound to another deployment, a schema this binary cannot read, a database that will not answer — does NOT resolve by waiting, and retrying it forever would turn a misconfiguration into a process that sits there looking healthy. Those are returned.
onWaiting IS CALLED BEFORE EACH WAIT, with whatever the ledger says about the holder, so a caller can log it and report it to a service manager. It is best effort by construction: the row is a diagnostic and the exclusion is the authority, so a standby that cannot read the holder still waits correctly.
func (*DB) ClaimController ¶
func (db *DB) ClaimController( ctx context.Context, holder, deployment string, ) (ControllerClaim, error)
ClaimController takes the deployment's controller claim, or refuses.
IT IS A SEPARATE STEP FROM Open, DELIBERATELY. Open is what every operator command and every test uses, and claiming there would mean an ordinary `billet nodes approve` announced itself as the controller. What this is for is the one process that is about to poll GitHub and dispatch, and it is called BEFORE either of those happens — a claim taken afterwards is a claim taken after the damage.
THE EXCLUSION IS THE BACKEND'S AND THE RECORD IS SHARED. On SQLite the exclusive hold on the state directory has already excluded a second control plane, and there is no second machine to worry about because the ledger is a file. On PostgreSQL the ledger is reachable from anywhere, so the backend takes a session-scoped advisory lock the server releases when the connection dies — no lease, no clock, and no stale row that could refuse a correct restart.
THE EXCLUSION STOPS A SECOND CONTROLLER STARTING; THE EPOCH IS WHAT FENCES ONE THAT WAS ALREADY RUNNING. A controller that loses its session while still running — a partition rather than a crash — releases the lock without noticing, and a replacement can then legitimately claim. Nothing can stop the first one writing up to that moment; what closes the hole is that its next write AFTER the successor's claim is refused, because every write transaction re-reads this epoch. See checkLeadership, and the PostgreSQL backend's claimController for why detection could never have done it.
THE ROW IS WRITTEN AFTER THE EXCLUSION IS HELD, never before and never instead. Deciding from the row would be deciding from what is present rather than from what is proved, and a crashed controller leaves its row exactly as it was.
THE DEPLOYMENT IS BOUND IN THE SAME TRANSACTION AS THE EPOCH, and it is the ONE transaction rather than the order within it that matters. Becoming this deployment's controller and recording which deployment these rows are is one decision: either both land or neither does, so a claim can never leave a ledger carrying a generation of a controller whose identity it does not name.
SPLITTING THEM LOOKS HARMLESS AND IS NOT — measured by doing it. With the bind in a second transaction after the claim, a process pointed at another deployment's rows advances the epoch, is then refused by the binding, and has FENCED THE REAL CONTROLLER OUT OF ITS OWN LEDGER on the way past. The misconfiguration that should have changed nothing takes the deployment down.
func (*DB) Close ¶
Close closes both pools, then releases the controller claim and the process lock.
THE CLAIM GOES LAST, AND THE ORDER IS THE SAFETY CONTENT. Releasing it first lets a REPLACEMENT claim the deployment while this process's writer pool is still finishing a transaction — and the caller of that transaction may still go on to make the dispatch it recorded. Two controllers, briefly, produced by the shutdown of one.
The first version of this had them the other way round, on the argument that the claim is the one thing that outlives the handle. It does, which is why it must be released — but AFTER the writes it was excluding have stopped.
errors.Join evaluates its arguments in order, so the sequence is the source order and not an accident of how the results are combined.
WHAT THIS STILL DOES NOT COVER is a caller that has already read a lease and is about to act on it outside any transaction. Closing the store cannot reach that; the scheduler's own shutdown is what does, and it runs first.
func (*DB) ControllerHolder ¶
func (db *DB) ControllerHolder(ctx context.Context) (ControllerClaim, error)
ControllerHolder reports who the ledger says holds the claim.
FOR A DIAGNOSTIC AND FOR `billet status`, never for a decision. An absent row is an ordinary state — a deployment nothing has ever claimed — and is reported as an empty holder rather than as an error.
func (*DB) CredentialSweeps ¶
func (db *DB) CredentialSweeps(ctx context.Context) ([]CredentialSweepRecord, error)
CredentialSweeps lists every path the sweep has recorded a pass over, with its most recent pass.
On the read-only pool: a read routed through Tx would reserve the single writer slot while it scans.
func (*DB) DeploymentBinding ¶
DeploymentBinding reports which deployment the ledger says it belongs to, or an empty string for one that has never been bound.
FOR A DIAGNOSTIC, never for a decision — the same rule ControllerHolder follows. An unbound ledger is an ordinary state and is reported as an empty value rather than as an error.
func (*DB) ForceTargets ¶
ForceTargets lists every lease one request covers, whatever became of it.
func (*DB) ForgetScaleSet ¶
ForgetScaleSet drops the record for a scale set that is gone.
Called when billet deletes one, so the orphan report stops naming something an operator has already cleaned up. Removing a record billet never had is not an error: teardown may be run against a deployment whose ledger predates this.
func (*DB) IntegrityCheck ¶
IntegrityCheck refuses to serve from a corrupt ledger.
EXPORTED so `billet check` can ask for it explicitly, because that command exists to prove a deployment is sane and this is most of what that means. Nothing else should: it reads the whole file, and doing it on every operator command put a growing scan in front of `leases release --force`, which is the command an operator runs when capacity is already missing.
func (*DB) LatestForceDestroy ¶
LatestForceDestroy reads the most recent request, open or finished, for the report an operator reads after the fact.
func (*DB) LeadershipLost ¶
LeadershipLost reports whether a write from this handle has been refused because another process has become this deployment's controller.
A LATCH THAT NEVER CLEARS. A fenced process cannot win leadership back by trying again — the successor holds the exclusion — so a caller that reads true must never read false afterwards and act on it.
WHAT IT IS FOR is the control plane's own teardown, which must act on nothing: destroy no compute, close no message session, hand back no capacity. Each of those is an authoritative act this process no longer has the right to perform, and the successor performs every one of them correctly. Deriving it from an error would tell the ONE caller that saw the refusal while its siblings unwound none the wiser; this is set synchronously inside Tx, before the refusal reaches anybody, so it is already true when anything begins tearing down.
func (*DB) LeadershipLostSignal ¶
func (db *DB) LeadershipLostSignal() <-chan struct{}
LeadershipLostSignal is closed the first time a write is refused because a successor claimed. It never carries a value and is never closed twice.
A SIGNAL AS WELL AS A FLAG, BECAUSE REFUSING THE WRITE IS NOT STOPPING THE PROCESS. Every background writer in the control plane is deliberately patient with an error it cannot classify — a heartbeat keeps its lease, a reap logs and tries again, a cleanup retry backs off — because the alternative is a database blip dropping leases and failing builds. So a replaced controller whose writes are all being refused would carry on polling GitHub, holding its message session and running its cleanup loop until something unrelated happened to return an error out of a poll. That loop calls Runner.Destroy, which does not go through the ledger and is therefore not fenced by anything.
The control plane selects on this and cancels itself, which is what turns a refusal into a stop. Polling LeadershipLost on a timer would work and would be a second clock deciding how long a replaced controller keeps touching the fleet; this fires at the instant the refusal happens.
NIL FOR A HANDLE openDir DID NOT BUILD, which blocks forever in a select. That is the right answer for a handle that never claimed and can never be fenced.
func (*DB) OpenForceDestroy ¶
OpenForceDestroy reads the force-destroy request that has not finished, if any.
ON THE READ-ONLY POOL, because every listener asks this on its poll while the control plane is doing real work, and a question must not reserve the single writer slot to answer itself.
func (*DB) PendingCompletions ¶
PendingCompletions returns one tier's obligations from the read-only pool.
func (*DB) PendingForceTargets ¶
func (db *DB) PendingForceTargets( ctx context.Context, generation int64, tier string, ) ([]ForceTarget, error)
PendingForceTargets lists the leases one tier still owes a destroy for.
SCOPED TO A TIER because a listener may only act on its own escrow. A listener destroying another tier's compute would be tearing down a lease it never held and cannot release.
func (*DB) PingContext ¶
PingContext proves the database is reachable AND configured as promised.
The integrity SCAN is deliberately not part of this. It is a whole-file read whose cost grows with job_history, and it answers a question only a control plane about to schedule against the ledger has to ask. See IntegrityCheck.
func (*DB) PutPendingCompletion ¶
func (db *DB) PutPendingCompletion( ctx context.Context, completion PendingCompletion, ) (PendingCompletionDisposition, error)
PutPendingCompletion durably records a result-delivery obligation.
func (*DB) RecordCredentialSweep ¶
func (db *DB) RecordCredentialSweep(ctx context.Context, rec CredentialSweepRecord) error
RecordCredentialSweep records one pass over one path, accumulating what it removed onto what earlier passes removed.
func (*DB) RecordScaleSet ¶
func (db *DB) RecordScaleSet(ctx context.Context, rec ScaleSetRecord) error
RecordScaleSet remembers that billet created this scale set.
Idempotent by (runner_group, label): the server reconciles every tier on every start, so this runs constantly against rows that already exist. The id is refreshed rather than kept, because a scale set deleted and recreated outside billet keeps its name and takes a new id, and the stale one would send an operator looking for an object that is gone.
func (*DB) ReleaseWatermark ¶ added in v0.6.0
ReleaseWatermark reports the newest release that has served this ledger and when it was recorded, or empty strings for a ledger nothing has recorded on.
FOR A DIAGNOSTIC AND FOR THE OPEN-TIME CHECK, never for a scheduling decision. An absent row is an ordinary state — every ledger upgrading through the release that adds the table — and is reported as empty rather than as an error.
func (*DB) RequestForceDestroy ¶
func (db *DB) RequestForceDestroy( ctx context.Context, req ForceDestroyRequest, ) (ForceDestroy, error)
RequestForceDestroy records an operator's decision and the exact leases it covers.
THE PRECONDITIONS ARE CHECKED INSIDE THE WRITE TRANSACTION, against the rows the write acts on. Checking them in the command and writing afterwards proves nothing about the state at the moment of the write: a resume committing in between would have this authorise destruction on a deployment that is admitting work again.
func (*DB) Resume ¶
Resume lets the deployment admit work again.
IT WILL NOT CLEAR A SEAL IT DID NOT TAKE. A `billet local up` that reopened an operator's maintenance seal because it happened to restart the services would admit work into a deployment somebody had deliberately quiesced, and would do it silently — the operator's evidence would be a job running during their maintenance window.
func (*DB) RetirePendingCompletion ¶
func (db *DB) RetirePendingCompletion( ctx context.Context, tier string, requestID, messageID int64, ) error
RetirePendingCompletion durably makes replay a no-op before deletion is tried.
func (*DB) ScaleSets ¶
ScaleSets returns every scale set billet recorded creating for one target.
On the read-only pool: a read routed through Tx would reserve the single writer slot while it scans. One statement needs no snapshot, so it does not go through View either — which is exactly why it has to translate a cancellation ITSELF. Server.Run calls this before any listener starts and returns what comes back, so a stop landing in that window used to leave the unit `failed` over a read the shutdown had interrupted. See asCancellation.
func (*DB) SetActionsCacheEnabled ¶
func (db *DB) SetActionsCacheEnabled( ctx context.Context, scope ActionsCacheScope, enabled bool, ) error
SetActionsCacheEnabled updates one explicit interception policy scope.
func (*DB) SetReleaseWatermark ¶ added in v0.6.0
SetReleaseWatermark moves the mark to a release an operator chose, in either direction.
THE ONE WRITE THAT MAY LOWER IT, and it is allowed through the maintenance handle — the typed entry the host-upgrade transaction opens after it has snapshotted the ledger, so the higher mark survives in the snapshot a rollback restores — and, on an external ledger, through the operator handle, because there the maintenance open is a probe that refuses every write and there is no snapshot for the mark to survive in. Every other writer moves the mark forwards or not at all.
func (*DB) SettleForceTarget ¶
func (db *DB) SettleForceTarget( ctx context.Context, generation int64, leaseID, state, detail string, ) error
SettleForceTarget records what became of one lease, and completes the request once nothing is pending.
COMPLETION IS DECIDED IN THE SAME TRANSACTION AS THE LAST SETTLEMENT. Deciding it from a separate read would let two listeners settling their last target concurrently both see work outstanding, and leave a request open that nothing will ever finish — which reads to an operator as a force that hung.
A FAILED TARGET STILL COMPLETES THE REQUEST. The alternative is a request that stays open forever against compute nothing can confirm, blocking the next force; the row keeps saying `failed`, which is what an operator acts on.
func (*DB) SnapshotInto ¶
SnapshotInto writes a consistent copy of the ledger to path.
VACUUM INTO IS THE MECHANISM, AND THE CONSTRAINTS ARE MEASURED RATHER THAN READ. Three of them decide the shape of this function:
- It cannot run inside a transaction — "SQL logic error: cannot VACUUM from within a transaction (1)" — so this must not go through DB.Tx.
- It is refused on the query-only reader pool — "attempt to write a readonly database (8)" — so it must go through the WRITER connection, which is why this lives here rather than being something a caller could assemble out of Reader().
- It REFUSES an existing destination: "SQL logic error: output file already exists (1)". That is a no-clobber install for free, and it is the backstop behind the check below rather than a replacement for it.
A WAL reader includes committed frames up to its snapshot end mark, so the result is a consistent copy of a LIVE database and billet.db-wal need not be copied beside it. Measured: the snapshot carries no -wal of its own.
IT HOLDS THE SINGLE WRITER SLOT FOR ITS DURATION, which is said here rather than discovered. The writer pool is one connection, so nothing else in this process commits while this runs, and for this ledger's size that is a short pause rather than a stall. Contention with ANOTHER process is retried on the same terms as any other write: busy is a race, not a verdict.
The file SQLite creates is mode 0644 under the usual umask (measured), so it is tightened here — the caller's directory mode is not the only thing standing between a ledger copy and every account on the host.
func (*DB) Tx ¶
Tx runs fn inside a single write transaction. Every mutation goes through here so that an allocation decision — read current usage, decide, record it — is one atomic step rather than a read followed by a hopeful write.
func (*DB) VerifyDeploymentBinding ¶
VerifyDeploymentBinding refuses a handle whose identity directory disagrees with what the ledger records, and writes nothing.
THE READ-ONLY HALF, FOR EVERY PROCESS THAT IS NOT THE CONTROLLER. An operator command binds nothing — it is not the authority for what this ledger is — but pointing one at another deployment's rows is exactly as wrong as pointing a control plane at them, and it is reachable by one wrong DSN. So it asks, and an unbound ledger answers yes because that is what every deployment upgrading through this release looks like.
func (*DB) View ¶
View runs fn inside a READ-ONLY transaction on the query-only pool.
THE COMPANION TO Tx, AND NOT AN OPTIMISATION. Every write transaction now begins IMMEDIATE, which takes SQLite's single writer slot at BEGIN — so a read-only operation routed through Tx reserves the right to write while it scans, and can delay a scheduling decision in the control plane. That was harmless when one process wrote; it is not now that operator commands share the ledger, and `billet leases quarantined` scanning a large table is exactly the shape that would do it.
The reader pool is query_only, so a write attempted in here fails rather than quietly succeeding on a connection nobody expected to mutate anything — the same reason Reader hands back a Querier rather than the pool.
A TRANSACTION rather than bare queries, so a caller issuing several of them sees one consistent snapshot instead of rows from either side of a commit.
type DeploymentLock ¶
type DeploymentLock struct {
// contains filtered or unexported fields
}
DeploymentLock is an exclusive host-wide lock on one deployment identity.
THE DIRECTORY LOCK GUARDS A PATH; THIS GUARDS AN IDENTITY, and the difference is a hole I documented rather than closed. `billet.lock` is flocked inside the state directory, so a COPY of that directory is a different inode and both copies lock happily. Both then carry the same deployment id — deliberately, because the copy's containers are labelled with it — so both enumerate the same compute against the same daemon. Recovery adopts rather than destroys, which makes the consequence smaller than it was, but "two processes managing one set of containers" is not a state either of them can reason about: each will heartbeat leases the other owns and hold capacity for work it did not start.
Keyed by the identity precisely so a copy collides. That is the point: the copy IS the same installation as far as its containers are concerned, and the honest answer to running it twice is to refuse.
func LockDeployment ¶
func LockDeployment(id string, opts LockOptions) (*DeploymentLock, error)
LockDeployment takes the host-wide lock for a deployment identity.
FAILING TO PLACE THE LOCK IS AN ERROR, and it did not used to be. The first version returned a degraded lock for every reason the file could not be locked, on the reasoning that a host with nowhere to put one is far more often a single deployment than two, so refusing to boot would trade a rare hazard for a common outage.
That is the wrong shape even where the conclusion is defensible, because it DERIVES AUTHORIZATION FROM AN I/O FAILURE. A symlink loop, a permissions change, ENOLCK, descriptor exhaustion, or a service manager that provides no HOME would each silently switch the protection off, leaving one log line among many as the operator's only evidence. Every one of those is a misconfiguration that looks exactly like the benign case from in here. An operator who knows their host has nowhere to put a lock says so with AllowUnplaceable; billet does not decide it for them.
A CONTENDED lock is an error under either setting, because that one is not ambiguous: something else is running under this identity right now.
func (*DeploymentLock) Degraded ¶
func (d *DeploymentLock) Degraded() string
Degraded reports why no lock was taken, or "" when one was.
func (*DeploymentLock) Path ¶
func (d *DeploymentLock) Path() string
Path reports where the lock lives, for diagnostics. Empty when degraded.
func (*DeploymentLock) Release ¶
func (d *DeploymentLock) Release() error
Release drops the lock. Safe on a degraded lock, which holds nothing.
type DirectoryLock ¶
type DirectoryLock struct {
// contains filtered or unexported fields
}
DirectoryLock is an exclusive hold on a state directory, for a caller that is not opening the ledger.
EXPORTED FOR RESTORE, which needs the exclusion WITHOUT the rest of what Open does: Open migrates, integrity-checks and creates. A restore has to prove no control plane holds this directory before it publishes anything into it, and it must prove that before deciding whether the directory may be written at all.
func LockStateDir ¶
func LockStateDir(stateDir string) (*DirectoryLock, error)
LockStateDir takes the same lock a control plane holds, or reports who has it.
SUCCESS PROVES NO CONTROL PLANE HOLDS THIS DIRECTORY. It proves nothing about another host, another path, or an operator command that opened through OpenAdmin without the lock — which is why a restore needs the maintenance fence and a writer barrier beside it, and an explicit fencing assertion from the operator for the deployment-wide half. See ErrLocked.
type ForceDestroy ¶
type ForceDestroy struct {
Generation int64
// AdmissionGeneration is the seal this was authorised against, so a reader
// can see afterwards that the seal did not move underneath the request.
AdmissionGeneration int64
State string
Reason string
Actor string
RequestedAt string
CompletedAt string
}
ForceDestroy is one operator decision to destroy running compute.
type ForceDestroyRequest ¶
type ForceDestroyRequest struct {
// ExpectAdmission is the admission generation the caller enumerated its
// targets against. A seal that moved in between means the set was taken from
// a deployment in a different state, so the request is refused rather than
// acted on.
ExpectAdmission int64
Reason string
Actor string
Targets []ForceTarget
}
ForceDestroyRequest is one decision to destroy running compute.
type ForceTarget ¶
type ForceTarget struct {
Generation int64
LeaseID string
Tier string
Node string
RunID string
SchedulerRequest int64
Phase string
State string
Detail string
}
ForceTarget is one lease an operator authorised destroying.
EVERY FIELD IS HERE SO THE DIAGNOSTIC SURVIVES THE COMMAND. A force must name every affected job, lease and node, and a listener acting on this a poll later — or a second control plane after a restart — has no other record of what the person actually approved.
type IdentityProbe ¶
type IdentityProbe int
IdentityProbe is a three-valued answer about a state directory's deployment identity. Three-valued because the caller is a safety gate: an unreadable directory is NOT an absent identity, and collapsing them would let a permissions problem silently disarm the refusal built on this probe.
const ( IdentityAbsent IdentityProbe = iota IdentityPresent IdentityUnknown )
func ProbeDeploymentID ¶
func ProbeDeploymentID(stateDir string) IdentityProbe
ProbeDeploymentID reports whether a state directory already holds a minted deployment identity, WITHOUT minting one — DeploymentID creates the file when absent, which makes it unusable as a probe. `billet init` asks before pointing a config away from (or over) a directory whose identity is live.
type LedgerContents ¶
type LedgerContents struct {
// Populated is true when any table a deployment writes to holds a row.
Populated bool
// NonEmpty names the tables that made it true, for a diagnostic an operator
// can act on.
NonEmpty []string
}
LedgerContents is what a read-only peek can say about a ledger file.
Populated is THREE-VALUED in effect: a caller gets an error when billet could not look, a false when every table a deployment writes to is empty, and a true with the tables named otherwise. "Could not tell" must never collapse into "empty", because what is done with an empty answer is deleting the file.
func PeekLedger ¶
func PeekLedger(ctx context.Context, dbPath string) (LedgerContents, error)
type LockOptions ¶
type LockOptions struct {
// Dir overrides the default location. Empty uses defaultLockDir.
Dir string
// AllowUnplaceable downgrades "nowhere to put the lock" from an error to a
// degraded lock the caller must report. Contention is never downgraded.
AllowUnplaceable bool
}
LockOptions configures where the host-wide lock goes and what happens when it cannot be placed.
type OpenOption ¶ added in v0.6.0
type OpenOption func(*openMode)
OpenOption configures one open of the ledger.
func WithRunningRelease ¶ added in v0.6.0
func WithRunningRelease(release string) OpenOption
WithRunningRelease names the billet that is opening the ledger, so the open can refuse a proved downgrade and the control plane can record a proved upgrade.
PASSED IN RATHER THAN READ HERE, for the reason releasesource.Current gives: a package that read version.Version() itself could only ever be tested against the build running the test. cmd/billet passes it on every open, and a structural test there proves no open site forgets; a caller that passes nothing gets no check and no record, which is what every test that opens a throwaway ledger wants.
type PendingCompletion ¶
type PendingCompletion struct {
Tier string
RequestID int64
RunID int64
Result string
LeaseID string
LeaseEpoch int64
LeaseNode string
Outcome string
ReleaseOnly bool
MessageID int64
Retired bool
Acknowledged bool
}
PendingCompletion is a GitHub result and capacity obligation the control plane must settle before the source message can be forgotten.
type PendingCompletionDisposition ¶
type PendingCompletionDisposition uint8
PendingCompletionDisposition says whether an incoming delivery may perform teardown.
const ( // PendingCompletionActionable is the current delivery and still needs settlement. PendingCompletionActionable PendingCompletionDisposition = iota // PendingCompletionRetired has already settled and exists only to stop replay. PendingCompletionRetired // PendingCompletionStale was superseded by a later delivery for the same request id. PendingCompletionStale )
type Querier ¶
type Querier interface {
QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
}
Querier is the read surface. It is deliberately narrower than *sql.DB: handing out the pool would let any caller issue writes on a connection that is supposed to be read-only, which is exactly the invariant this package exists to hold.
type ReadOps ¶
type ReadOps interface {
AnyBarrierRunExists(ctx context.Context) (bool, error)
BarrierRunExists(ctx context.Context, node string) (bool, error)
CertRevocation(ctx context.Context, serial string) (int64, error)
CountActiveRunnerLeases(ctx context.Context, tier string) (int64, error)
CountCacheBlocks(ctx context.Context, arg ledgerdb.CountCacheBlocksParams) (int64, error)
CountForceDestroyInState(ctx context.Context, state string) (int64, error)
CountForceTargetsInState(ctx context.Context, arg ledgerdb.CountForceTargetsInStateParams) (int64, error)
CountLiveWorkOnNode(ctx context.Context, arg ledgerdb.CountLiveWorkOnNodeParams) (int64, error)
CountOpenInTier(ctx context.Context, tier string) (int64, error)
CountOpenPerTier(ctx context.Context) ([]ledgerdb.CountOpenPerTierRow, error)
CountOutstandingLeasesOnNode(ctx context.Context, node string) (int64, error)
DisruptableLease(ctx context.Context, id string) ([]string, error)
DisruptableLeasesOnNode(ctx context.Context, node string) ([]string, error)
FleetClaimHolder(ctx context.Context, arg ledgerdb.FleetClaimHolderParams) (string, error)
ForceDestroyInState(ctx context.Context, state string) (ledgerdb.ForceDestroy, error)
ForceTargets(ctx context.Context, generation int64) ([]ledgerdb.ForceTargetsRow, error)
HighestForceDestroyGeneration(ctx context.Context) (int64, error)
HighestRolloutGeneration(ctx context.Context) (int64, error)
HostReportsCompute(ctx context.Context, node string) (bool, error)
LatestForceDestroy(ctx context.Context) (ledgerdb.ForceDestroy, error)
ListAdmissionRows(ctx context.Context) ([]ledgerdb.Admission, error)
ListAppliedMigrations(ctx context.Context) ([]ledgerdb.ListAppliedMigrationsRow, error)
ListAttributedFailures(ctx context.Context, arg ledgerdb.ListAttributedFailuresParams) ([]ledgerdb.ListAttributedFailuresRow, error)
ListBarrierRuns(ctx context.Context, barrierID string) ([]ledgerdb.ListBarrierRunsRow, error)
ListCodeBuildRegistrationPaths(ctx context.Context, provider string) ([]ledgerdb.ListCodeBuildRegistrationPathsRow, error)
ListCredentialSweeps(ctx context.Context) ([]ledgerdb.CredentialSweep, error)
ListEnrollments(ctx context.Context) ([]ledgerdb.NodeEnrollment, error)
ListEnrollmentsInState(ctx context.Context, state string) ([]ledgerdb.NodeEnrollment, error)
ListExpiredLeases(ctx context.Context, arg ledgerdb.ListExpiredLeasesParams) ([]ledgerdb.ListExpiredLeasesRow, error)
ListFleetClearance(ctx context.Context) ([]ledgerdb.ListFleetClearanceRow, error)
ListForceDestroyCandidates(ctx context.Context, arg ledgerdb.ListForceDestroyCandidatesParams) ([]ledgerdb.ListForceDestroyCandidatesRow, error)
ListHeldLeases(ctx context.Context, arg ledgerdb.ListHeldLeasesParams) ([]ledgerdb.ListHeldLeasesRow, error)
ListHostsReportingCompute(ctx context.Context) ([]string, error)
ListJobConclusionsForRequest(ctx context.Context, requestID sql.NullInt64) ([]sql.NullString, error)
ListJobHistory(ctx context.Context, maxRows int64) ([]ledgerdb.ListJobHistoryRow, error)
ListJoinTokenHashes(ctx context.Context) ([]string, error)
ListJoinTokens(ctx context.Context) ([]ledgerdb.ListJoinTokensRow, error)
ListLeaseIDsOnNode(ctx context.Context, arg ledgerdb.ListLeaseIDsOnNodeParams) ([]string, error)
ListNodeInventories(ctx context.Context) ([]ledgerdb.ListNodeInventoriesRow, error)
ListNodeWireVersions(ctx context.Context) ([]ledgerdb.ListNodeWireVersionsRow, error)
ListOutstandingLeases(ctx context.Context) ([]ledgerdb.ListOutstandingLeasesRow, error)
ListOutstandingRemoteShapes(ctx context.Context) ([]ledgerdb.ListOutstandingRemoteShapesRow, error)
ListPendingCompletions(ctx context.Context, tier string) ([]ledgerdb.ListPendingCompletionsRow, error)
ListPlaceableNodes(ctx context.Context) ([]ledgerdb.ListPlaceableNodesRow, error)
ListPoolRunnersInTier(ctx context.Context, tier string) ([]ledgerdb.PoolRunner, error)
ListQuarantinedLeaseIDsOn(ctx context.Context, arg ledgerdb.ListQuarantinedLeaseIDsOnParams) ([]string, error)
ListQuarantinedLeases(ctx context.Context, phase string) ([]ledgerdb.ListQuarantinedLeasesRow, error)
ListRegisteredNodes(ctx context.Context) ([]ledgerdb.ListRegisteredNodesRow, error)
ListRemoteCostNodes(ctx context.Context) ([]ledgerdb.ListRemoteCostNodesRow, error)
ListRevokedCerts(ctx context.Context) ([]ledgerdb.RevokedCert, error)
ListRolloutHistory(ctx context.Context, maxRows int64) ([]ledgerdb.Rollout, error)
ReadNewestRolloutForTarget(ctx context.Context, targetDigest string) (ledgerdb.Rollout, error)
ListRolloutNodePhases(ctx context.Context, rolloutID string) ([]ledgerdb.ListRolloutNodePhasesRow, error)
ListRolloutNodes(ctx context.Context, rolloutID string) ([]ledgerdb.ListRolloutNodesRow, error)
ListRunningLeasesWithReplacedHolder(ctx context.Context, arg ledgerdb.ListRunningLeasesWithReplacedHolderParams) ([]ledgerdb.ListRunningLeasesWithReplacedHolderRow, error)
ListScaleSets(ctx context.Context, org string) ([]ledgerdb.ListScaleSetsRow, error)
ListServiceableRunnerLeaseIDs(ctx context.Context, tier string) ([]string, error)
LiveCertsFor(ctx context.Context, arg ledgerdb.LiveCertsForParams) ([]ledgerdb.IssuedCert, error)
LiveJoinTokenExists(ctx context.Context, arg ledgerdb.LiveJoinTokenExistsParams) (bool, error)
NodeRevocationCutoff(ctx context.Context, node string) (string, error)
PendingCompletionMessage(ctx context.Context, arg ledgerdb.PendingCompletionMessageParams) (ledgerdb.PendingCompletionMessageRow, error)
PendingForceTargets(ctx context.Context, arg ledgerdb.PendingForceTargetsParams) ([]ledgerdb.PendingForceTargetsRow, error)
ReadAdmission(ctx context.Context) (ledgerdb.ReadAdmissionRow, error)
ReadBarrierRun(ctx context.Context, arg ledgerdb.ReadBarrierRunParams) (ledgerdb.ReadBarrierRunRow, error)
ReadComputeBarrier(ctx context.Context) (ledgerdb.ReadComputeBarrierRow, error)
ReadControllerClaim(ctx context.Context) (ledgerdb.ReadControllerClaimRow, error)
ReadDeploymentBinding(ctx context.Context) (ledgerdb.ReadDeploymentBindingRow, error)
ReadNodeHighestRelease(ctx context.Context, name string) (string, error)
ReadReleaseWatermark(ctx context.Context) (ledgerdb.ReadReleaseWatermarkRow, error)
ReadEnrollment(ctx context.Context, name string) (ledgerdb.NodeEnrollment, error)
ReadEnrollmentFingerprint(ctx context.Context, name string) (string, error)
ReadJobConclusion(ctx context.Context, leaseID string) (sql.NullString, error)
ReadJobFailureReason(ctx context.Context, leaseID string) (string, error)
ReadJobNode(ctx context.Context, leaseID string) (sql.NullString, error)
ReadJobPlacement(ctx context.Context, leaseID string) (ledgerdb.ReadJobPlacementRow, error)
ReadJobIdentity(ctx context.Context, jobID string) (int64, error)
ReadJobResult(ctx context.Context, leaseID string) (string, error)
ReadJobStarted(ctx context.Context, leaseID string) (bool, error)
ReadLease(ctx context.Context, id string) (ledgerdb.ReadLeaseRow, error)
ReadLeaseCharge(ctx context.Context, id string) (ledgerdb.ReadLeaseChargeRow, error)
ReadLeaseClosure(ctx context.Context, id string) (ledgerdb.ReadLeaseClosureRow, error)
ReadLeaseEpoch(ctx context.Context, id string) (int64, error)
ReadLeaseJob(ctx context.Context, id string) (ledgerdb.ReadLeaseJobRow, error)
ReadLeaseSettlement(ctx context.Context, id string) (ledgerdb.ReadLeaseSettlementRow, error)
ReadLeaseTargetSize(ctx context.Context, id string) (ledgerdb.ReadLeaseTargetSizeRow, error)
ReadNodeBarrierFence(ctx context.Context, name string) (ledgerdb.ReadNodeBarrierFenceRow, error)
ReadNodeCapacity(ctx context.Context, name string) (ledgerdb.ReadNodeCapacityRow, error)
ReadNodeEpoch(ctx context.Context, name string) (int64, error)
ReadNodeFence(ctx context.Context, name string) (ledgerdb.ReadNodeFenceRow, error)
ReadNodeIncarnation(ctx context.Context, name string) (string, error)
ReadNodeLiveness(ctx context.Context, name string) (int64, error)
ReadNodeProvider(ctx context.Context, name string) (string, error)
ReadNodeRegistration(ctx context.Context, name string) (ledgerdb.ReadNodeRegistrationRow, error)
ReadNodeSize(ctx context.Context, name string) (ledgerdb.ReadNodeSizeRow, error)
ReadPoolRunnerByLease(ctx context.Context, leaseID string) (ledgerdb.PoolRunner, error)
ReadPoolRunnerByName(ctx context.Context, runnerName string) (ledgerdb.PoolRunner, error)
ReadPoolRunnerSettlementByRequest(ctx context.Context, arg ledgerdb.ReadPoolRunnerSettlementByRequestParams) (ledgerdb.ReadPoolRunnerSettlementByRequestRow, error)
ReadPoolSlotIdentity(ctx context.Context, leaseID string) (int64, error)
ReadRolloutControllerPhase(ctx context.Context, id string) (string, error)
ReadRolloutInState(ctx context.Context, state string) (ledgerdb.Rollout, error)
ReadRolloutNodeProgress(ctx context.Context, arg ledgerdb.ReadRolloutNodeProgressParams) (ledgerdb.ReadRolloutNodeProgressRow, error)
TotalUsage(ctx context.Context) (ledgerdb.TotalUsageRow, error)
UsageByNode(ctx context.Context) ([]ledgerdb.UsageByNodeRow, error)
UsageOnNode(ctx context.Context, node string) (ledgerdb.UsageOnNodeRow, error)
}
ReadOps is every generated query that only reads.
HAND-WRITTEN, AND THAT IS THE POINT: it is what ReadQueries returns, so an adapter bound to the query-only pool has no mutation in its method set. Adding a query means listing it in exactly one of these two interfaces, which TestEveryGeneratedQueryIsClaimedByExactlyOneHalf checks, and putting a mutation in the wrong one is caught by TestReadOpsHoldsExactlyTheQueriesThatOnlyRead rather than by review.
func ReadQueries ¶
ReadQueries binds the read half to anything that can answer a query.
It takes Querier rather than a concrete type so it serves all three readers: the query-only pool inside View, a read transaction, and the bare *sql.DB the Peek functions open over a ledger file they must not migrate or create.
It returns ReadOps rather than WriteOps, so a caller handed a read handle -- including one in internal/alloc or internal/rollout -- has no mutation in its method set at all. That is the compile-time half; readOnlyDBTX underneath is the runtime half.
WHAT IT GUARANTEES IS THE METHOD SET, NOT THE POOL, and the difference matters because a *sql.Tx satisfies Querier: a caller inside DB.Tx may legitimately bind this to the WRITE connection, which is what a read that must happen inside a write transaction needs. Nothing can mutate through it either way.
type ResumeRequest ¶
type ResumeRequest struct {
Expect int64
// Clears names the provenance this caller is entitled to undo. A lifecycle
// command may clear its own seal and must not clear an operator's.
Clears string
Actor string
}
ResumeRequest is one decision to admit work again.
type ScaleSetRecord ¶
ScaleSetRecord is a scale set billet created, and where.
Target is the GitHub PATH of the target the scale set belongs to — `owner` for an organization, `owner/name` for a repository — never the target's config name, which an operator may rename. It is stored in the `org` column, which predates repository targets and keeps its name: every row written before them is an organization's and still reads as one, and a repository's path is simply a value that column had never carried.
type SealRequest ¶
type SealRequest struct {
// Expect is the generation the caller believes is current. A seal that finds
// a different one has been overtaken and refuses rather than overwriting
// somebody else's decision.
Expect int64
// Provenance decides who may clear this seal.
Provenance string
// Reason and Actor are what an operator reads later when they find their
// deployment admitting nothing. Neither is optional in practice: a seal
// nobody can attribute is one nobody dares clear.
Reason string
Actor string
// KeepExisting asks for "make sure this is sealed" rather than "seal this":
// when a seal of the same provenance is already held, keep it, leave the
// generation where it is, and return what is there.
//
// IT IS OPT-IN BECAUSE IT CHANGES WHAT A SEAL MEANS. Applied to every caller,
// a second operator deliberately resealing with a new reason would silently
// do nothing, and — worse — the generation would stop moving, so a fence
// another operator was holding would still look current after somebody else
// had taken the seal. An existing test caught exactly that.
//
// The caller that wants it is an idempotent command: `billet drain` run twice
// must not rewrite somebody's attribution or invalidate their fence. It has to
// be decided HERE rather than by the command reading first, because between
// that read and this transaction another operator can resume, and the command
// would report "already sealed" against a deployment that is now open.
KeepExisting bool
}
SealRequest is one decision to stop admitting work.
type WriteOps ¶
type WriteOps interface {
ReadOps
AcknowledgePendingCompletion(ctx context.Context, arg ledgerdb.AcknowledgePendingCompletionParams) error
AcknowledgePoolRunnerSource(ctx context.Context, arg ledgerdb.AcknowledgePoolRunnerSourceParams) error
AdvanceRolloutController(ctx context.Context, arg ledgerdb.AdvanceRolloutControllerParams) error
AdvanceRolloutNode(ctx context.Context, arg ledgerdb.AdvanceRolloutNodeParams) error
ArchiveJobHistory(ctx context.Context, arg ledgerdb.ArchiveJobHistoryParams) error
AssignLease(ctx context.Context, arg ledgerdb.AssignLeaseParams) error
BindDeployment(ctx context.Context, arg ledgerdb.BindDeploymentParams) error
BindLease(ctx context.Context, arg ledgerdb.BindLeaseParams) error
BindPoolRunnerJob(ctx context.Context, arg ledgerdb.BindPoolRunnerJobParams) error
BumpDispatchGeneration(ctx context.Context, name string) (int64, error)
ClaimController(ctx context.Context, arg ledgerdb.ClaimControllerParams) (int64, error)
ClaimPoolRunnerForRetirement(ctx context.Context, arg ledgerdb.ClaimPoolRunnerForRetirementParams) (sql.Result, error)
CompleteForceDestroy(ctx context.Context, arg ledgerdb.CompleteForceDestroyParams) error
CorrectProvisionalHistory(ctx context.Context, arg ledgerdb.CorrectProvisionalHistoryParams) error
CorrectProvisionalLease(ctx context.Context, arg ledgerdb.CorrectProvisionalLeaseParams) error
DecideEnrollment(ctx context.Context, arg ledgerdb.DecideEnrollmentParams) error
DecommissionNode(ctx context.Context, arg ledgerdb.DecommissionNodeParams) error
DeleteAcknowledgedCompletion(ctx context.Context, arg ledgerdb.DeleteAcknowledgedCompletionParams) error
DeleteBarrierRun(ctx context.Context, node string) error
DeleteCacheBlock(ctx context.Context, arg ledgerdb.DeleteCacheBlockParams) error
DeleteComputeBarrier(ctx context.Context) error
DeleteEveryBarrierRun(ctx context.Context) error
DeleteEveryNodeInventory(ctx context.Context) error
DeleteMovedScaleSet(ctx context.Context, arg ledgerdb.DeleteMovedScaleSetParams) error
DeletePoolRunner(ctx context.Context, leaseID string) error
DeleteRetiredCompletion(ctx context.Context, arg ledgerdb.DeleteRetiredCompletionParams) error
DeleteScaleSet(ctx context.Context, arg ledgerdb.DeleteScaleSetParams) error
ExpireLease(ctx context.Context, arg ledgerdb.ExpireLeaseParams) error
FenceQuarantinedLease(ctx context.Context, arg ledgerdb.FenceQuarantinedLeaseParams) error
FinishRollout(ctx context.Context, arg ledgerdb.FinishRolloutParams) error
ForgetEveryNode(ctx context.Context) error
HeartbeatLease(ctx context.Context, arg ledgerdb.HeartbeatLeaseParams) error
HoldLease(ctx context.Context, arg ledgerdb.HoldLeaseParams) error
InsertEnrollment(ctx context.Context, arg ledgerdb.InsertEnrollmentParams) error
InsertForceDestroy(ctx context.Context, arg ledgerdb.InsertForceDestroyParams) error
InsertForceDestroyTarget(ctx context.Context, arg ledgerdb.InsertForceDestroyTargetParams) error
InsertJobIdentity(ctx context.Context, jobID string) error
InsertJoinToken(ctx context.Context, arg ledgerdb.InsertJoinTokenParams) error
InsertLease(ctx context.Context, arg ledgerdb.InsertLeaseParams) error
InsertPoolRunner(ctx context.Context, arg ledgerdb.InsertPoolRunnerParams) error
InsertPoolSlotIdentity(ctx context.Context, leaseID string) error
InsertRollout(ctx context.Context, arg ledgerdb.InsertRolloutParams) error
InsertRolloutNode(ctx context.Context, arg ledgerdb.InsertRolloutNodeParams) error
MarkLeaseDeregistered(ctx context.Context, id string) error
MarkLeaseFailure(ctx context.Context, arg ledgerdb.MarkLeaseFailureParams) error
MarkNodeNotLive(ctx context.Context, arg ledgerdb.MarkNodeNotLiveParams) error
MarkPoolRunnerBusy(ctx context.Context, arg ledgerdb.MarkPoolRunnerBusyParams) error
MarkPoolRunnerRetired(ctx context.Context, arg ledgerdb.MarkPoolRunnerRetiredParams) error
MarkPoolRunnerRetiring(ctx context.Context, arg ledgerdb.MarkPoolRunnerRetiringParams) error
ReclaimLease(ctx context.Context, arg ledgerdb.ReclaimLeaseParams) error
RecordCredentialSweep(ctx context.Context, arg ledgerdb.RecordCredentialSweepParams) error
RefreshLeaseHolder(ctx context.Context, arg ledgerdb.RefreshLeaseHolderParams) error
RecordBarrierRun(ctx context.Context, arg ledgerdb.RecordBarrierRunParams) error
RecordHistoryCacheObservation(ctx context.Context, arg ledgerdb.RecordHistoryCacheObservationParams) error
RecordHistoryDisruption(ctx context.Context, arg ledgerdb.RecordHistoryDisruptionParams) error
RecordIssuedCert(ctx context.Context, arg ledgerdb.RecordIssuedCertParams) error
RecordJobAssignment(ctx context.Context, arg ledgerdb.RecordJobAssignmentParams) error
RecordJobResult(ctx context.Context, arg ledgerdb.RecordJobResultParams) error
RecordJobRun(ctx context.Context, arg ledgerdb.RecordJobRunParams) error
RecordJobStart(ctx context.Context, arg ledgerdb.RecordJobStartParams) error
BackfillFailureReason(ctx context.Context, arg ledgerdb.BackfillFailureReasonParams) error
BackfillLeaseFailureReason(ctx context.Context, arg ledgerdb.BackfillLeaseFailureReasonParams) error
RecordLeaseCacheObservation(ctx context.Context, arg ledgerdb.RecordLeaseCacheObservationParams) error
RecordLeaseDisruption(ctx context.Context, arg ledgerdb.RecordLeaseDisruptionParams) error
RecordMigration(ctx context.Context, arg ledgerdb.RecordMigrationParams) error
RecordNodeRevocation(ctx context.Context, arg ledgerdb.RecordNodeRevocationParams) error
ReplaceDeniedEnrollment(ctx context.Context, arg ledgerdb.ReplaceDeniedEnrollmentParams) error
RequestForceRelease(ctx context.Context, arg ledgerdb.RequestForceReleaseParams) error
ResizeLease(ctx context.Context, arg ledgerdb.ResizeLeaseParams) error
RetirePendingCompletion(ctx context.Context, arg ledgerdb.RetirePendingCompletionParams) error
RevokeCert(ctx context.Context, arg ledgerdb.RevokeCertParams) error
SetAdmission(ctx context.Context, arg ledgerdb.SetAdmissionParams) error
SetLeasePhase(ctx context.Context, arg ledgerdb.SetLeasePhaseParams) error
SettleForceTarget(ctx context.Context, arg ledgerdb.SettleForceTargetParams) (sql.Result, error)
SetReleaseWatermark(ctx context.Context, arg ledgerdb.SetReleaseWatermarkParams) error
SpendJoinToken(ctx context.Context, arg ledgerdb.SpendJoinTokenParams) (sql.Result, error)
StartPoolRunner(ctx context.Context, arg ledgerdb.StartPoolRunnerParams) error
TerminalizeQuarantinedLease(ctx context.Context, arg ledgerdb.TerminalizeQuarantinedLeaseParams) error
TerminalizeQuarantinedLeaseWithReason(ctx context.Context, arg ledgerdb.TerminalizeQuarantinedLeaseWithReasonParams) error
TerminateForcedLease(ctx context.Context, arg ledgerdb.TerminateForcedLeaseParams) error
UpsertCacheBlock(ctx context.Context, arg ledgerdb.UpsertCacheBlockParams) error
UpsertComputeBarrier(ctx context.Context, arg ledgerdb.UpsertComputeBarrierParams) error
UpsertIssuedEnrollment(ctx context.Context, arg ledgerdb.UpsertIssuedEnrollmentParams) error
UpsertNodeInventory(ctx context.Context, arg ledgerdb.UpsertNodeInventoryParams) error
UpsertNodeRegistration(ctx context.Context, arg ledgerdb.UpsertNodeRegistrationParams) (int64, error)
UpsertPendingCompletion(ctx context.Context, arg ledgerdb.UpsertPendingCompletionParams) error
UpsertScaleSet(ctx context.Context, arg ledgerdb.UpsertScaleSetParams) error
WithdrawNode(ctx context.Context, arg ledgerdb.WithdrawNodeParams) (int64, error)
}
WriteOps is ReadOps plus every generated query that mutates.
It EMBEDS ReadOps because a write transaction reads too: a seal compares the admission generation it is about to overwrite, and a force-destroy reads the admission row it authorises against, both inside the transaction that writes.
func WriteQueries ¶
WriteQueries binds the generated set to a write transaction.
IT GRANTS NO AUTHORITY THE CALLER DID NOT ALREADY HAVE, which is why exporting it is safe: whatever a *sql.Tx can do, it can do without this. What it adds is the read/write split and one place that lists every query.
IT DOES NOT PROVE THE TRANSACTION CAME FROM DB.Tx, and the difference is worth stating rather than implying. In practice DB.Tx is the only source in this program -- it is what has taken the process lock, begun IMMEDIATE, consulted the maintenance fence and installed the busy retry -- but nothing in the type says so, and a caller that opened its own handle could begin its own transaction. The driver import that would need is confined to this package by depguard, and every statement such a caller made would be reported by the rawsql analyzer, so the bypass is guarded elsewhere rather than here.
Source Files
¶
- admin.go
- admission.go
- backend.go
- cache_policy.go
- completion.go
- controller.go
- credentialsweep.go
- deployment.go
- deploymentbinding.go
- deploymentlock.go
- fence.go
- forcedestroy.go
- lock_unix.go
- migrationfiles.go
- postgresbackend.go
- queryset.go
- releasewatermark.go
- scaleset.go
- searchdir_linux.go
- snapshot.go
- sqlitebackend.go
- state.go