Documentation
¶
Overview ¶
Package forwardlegacy is the v4.2 forwarding upgrade (forward-sdk.md section 10, F5c, gate H15): it archives the flux forwarding data, checks that every forward node is clean of the old runtime, and, only when an administrator confirms it on the command line, drops the flux tables.
The drop is the only place the flux tables are dropped, and it is never automatic. Once it is recorded (v4_forward_legacy_drop), Control no longer creates those tables at start (KeepModels, UnlessDropped) and no longer runs the flux workers.
Index ¶
- Constants
- Variables
- func AnyDropTablePresent(db *gorm.DB) bool
- func ArchiveFileName(now time.Time) string
- func ControlRunning(ctx context.Context, db *gorm.DB, now time.Time) (string, time.Time, bool, error)
- func Dropped(db *gorm.DB) bool
- func EnsureSchema(db *gorm.DB) error
- func KeepModels(db *gorm.DB, models []any) []any
- func LatestArchive(db *gorm.DB) (*model.ForwardLegacyArchive, error)
- func LatestDrop(db *gorm.DB) (*model.ForwardLegacyDrop, error)
- func MissingTable(err error, table string) bool
- func NodeRef(id uint) string
- func UnlessDropped(step func(*gorm.DB) error) func(*gorm.DB) error
- type Archive
- type ArchiveTable
- type Backup
- type CheckOptions
- type Cleaner
- type DropOptions
- type DropResult
- type NodeInfo
- type NodeStatus
- type PackageAdoption
- type PathResult
- type PreconditionOptions
- type Preconditions
- type RefusedError
- type WriteOptions
- type WriteResult
Constants ¶
const ( PathAgent = "agent" PathNodeX = "nodex" PathAnsible = "ansible" )
Check paths.
const ( StateUnchecked = "unchecked" StateClean = "clean" StateDirty = "dirty" StateUnreachable = "unreachable" StateAbandoned = "abandoned" )
Node states.
const ArchiveDirName = "forward-legacy"
ArchiveDirName is the archive directory under Control's data directory.
const ArchiveSchema = "anixops.forward.legacy-archive.v1"
ArchiveSchema is the archive file's schema version.
const BackupMaxAge = 24 * time.Hour
BackupMaxAge is how old a database backup the drop accepts may be.
const ConfirmPhrase = "DROP v4.1 FORWARDING TABLES"
ConfirmPhrase is what `forward legacy drop --confirm` must be given, exactly.
const DefaultLockTimeout = 10 * time.Second
DefaultLockTimeout bounds how long the drop waits on PostgreSQL for a table another session still reads.
const PathNotNeeded = "not_needed"
PathNotNeeded is a path result for a path that had nothing to clean on the node.
const Redacted = "[redacted]"
Redacted replaces a secret value the archive leaves out.
const SingletonLease = "control.singleton-workers"
SingletonLease is the lease the running Control's singleton workers hold (cmd/server); the drop refuses while it is held.
Variables ¶
var DropTables = []string{
"v2_forward_port_binding",
"v2_forward_traffic_cursor",
"v2_forward_agent_bridge_task",
"v2_forward_runtime_job",
"v2_forward_user_tunnel",
"v2_speed_limit",
"v2_forward",
"v2_forward_tunnel",
"v2_forward_rule",
"v2_forward_route",
"v2_forward_log",
"v2_forward_stats",
}
DropTables are the flux forwarding tables the drop removes, dependents first, so a foreign key never points at a table already gone. The F5 audit: the flux forwards, tunnels, user tunnel grants and speed limits; the legacy rules; the runtime jobs with their port claims, traffic cursors and clean agent bridge tasks; and the pre-flux smart routes, connection log and hourly stats, which nothing reads any more.
var ErrNotUnreachable = errors.New("only a node whose latest check found it unreachable can be abandoned")
ErrNotUnreachable refuses abandoning a node that is not unreachable.
var KeptTables = []string{"v2_forward_node", "v2_forward_clean_agent"}
KeptTables are archived for reference but not dropped:
- v2_forward_node is the node inventory and the Agent identity (forward-<id>), which v4.2 keeps (section 10);
- v2_forward_clean_agent is a table of the node credential split (nodesecrets) and the source of kapi_forward_clean_agent_v1; it goes with the clean agent's code, not with the flux data.
v2_forward_latency_bucket is neither: its probes also cover proxy nodes (LatencyTargetTypeNode), so it is not flux data.
Functions ¶
func AnyDropTablePresent ¶
AnyDropTablePresent reports whether one of the flux tables exists.
func ArchiveFileName ¶
ArchiveFileName is the timestamped name of an archive written at now.
func ControlRunning ¶
func ControlRunning(ctx context.Context, db *gorm.DB, now time.Time) (string, time.Time, bool, error)
ControlRunning answers the holder of the running Control's singleton lease and until when, when it is held now.
func Dropped ¶
Dropped reports whether the flux tables were dropped. An error reading the record answers false: Control then behaves as before the drop.
func EnsureSchema ¶
EnsureSchema creates the upgrade's own tables. They are new tables: nothing existing is altered.
func KeepModels ¶
KeepModels answers models without the flux tables once they were dropped, so the schema migration does not create them again. It keys on table names, so it keeps working whichever models a later release still lists.
func LatestArchive ¶
func LatestArchive(db *gorm.DB) (*model.ForwardLegacyArchive, error)
LatestArchive answers the newest recorded archive, nil when none.
func LatestDrop ¶
func LatestDrop(db *gorm.DB) (*model.ForwardLegacyDrop, error)
LatestDrop answers the recorded drop, nil when the tables were not dropped.
func MissingTable ¶
MissingTable reports whether err is the database saying that table does not exist: SQLite's "no such table: <table>", or PostgreSQL's undefined table (SQLSTATE 42P01) naming it. It is how a statement on a flux table finds out that Drop removed the table, without a metadata query before every statement: the code that runs on a database with the table keeps its statements and its errors, and only this answer means "dropped".
Do not guard with Migrator().HasTable instead. It answers false when its own query fails (a cancelled context, a lost connection, a statement timeout), so a guard built on it takes any failure for a dropped table and answers "nothing there" where the caller must have the error. Another table missing, and every other error, is false.
Types ¶
type Archive ¶
type Archive struct {
Schema string `json:"schema"`
CreatedAt time.Time `json:"created_at"`
ControlVersion string `json:"control_version,omitempty"`
DatabaseDriver string `json:"database_driver"`
Note string `json:"note"`
Tables []ArchiveTable `json:"tables"`
}
Archive is the archive file's document.
func BuildArchive ¶
func BuildArchive(ctx context.Context, db *gorm.DB, now time.Time, controlVersion string) (*Archive, error)
BuildArchive reads the flux tables and the kept reference tables into an archive with every secret left out.
func VerifyArchive ¶
func VerifyArchive(record *model.ForwardLegacyArchive) (*Archive, error)
VerifyArchive reads a recorded archive's file and checks it: readable, the recorded SHA-256, and the archive schema. It answers the archive.
type ArchiveTable ¶
type ArchiveTable struct {
Name string `json:"name"`
// Dropped tells whether the upgrade's drop removes the table.
Dropped bool `json:"dropped"`
// Present is false when the table did not exist (a release that
// already removed it): Rows is then empty.
Present bool `json:"present"`
RowCount int `json:"row_count"`
// RedactedColumns are columns whose values were left out.
RedactedColumns []string `json:"redacted_columns,omitempty"`
Rows []map[string]any `json:"rows"`
}
ArchiveTable is one table's rows.
type Backup ¶
type Backup struct {
// Source is "backup_record" (Control's own backup service) or
// "operator" (--backup-taken).
Source string `json:"source"`
Path string `json:"path"`
SizeBytes int64 `json:"size_bytes"`
SHA256 string `json:"sha256"`
ModifiedAt time.Time `json:"modified_at"`
RecordID uint `json:"record_id,omitempty"`
}
Backup is the database backup a drop accepted.
type CheckOptions ¶
type CheckOptions struct {
NodeX Cleaner
Ansible Cleaner
// Nodes limits the check to these node names or references; empty
// checks every forward node.
Nodes []string
Now time.Time
}
CheckOptions configure a check.
type Cleaner ¶
type Cleaner interface {
CleanNode(ctx context.Context, node NodeInfo) PathResult
}
Cleaner removes the old forward runtime of a node through one path (NodeX's HTTP API, the Ansible fallback) and verifies it is gone.
type DropOptions ¶
type DropOptions struct {
// Confirm must be ConfirmPhrase exactly.
Confirm string
BackupPath string
Actor string
// LockTimeout bounds the wait for a table lock on PostgreSQL;
// DefaultLockTimeout when zero.
LockTimeout time.Duration
Now time.Time
}
DropOptions configure the drop.
type DropResult ¶
type DropResult struct {
Record model.ForwardLegacyDrop `json:"record"`
Dropped []string `json:"dropped"`
AlreadyMissing []string `json:"already_missing"`
Backup *Backup `json:"backup"`
}
DropResult is a completed drop.
func Drop ¶
func Drop(ctx context.Context, db *gorm.DB, options DropOptions) (*DropResult, error)
Drop drops the flux tables, all in one transaction, and records it. It refuses unless the phrase is exact, the archive is readable and current, every forward node is clean or abandoned, no Control runs on the database and a database backup was taken. Tables already gone are reported, not an error.
type NodeInfo ¶
type NodeInfo struct {
ID uint `json:"id"`
Ref string `json:"node_ref"`
Name string `json:"name"`
Host string `json:"host"`
}
NodeInfo is a forward node as a cleaner sees it.
type NodeStatus ¶
type NodeStatus struct {
NodeID uint `json:"node_id"`
Ref string `json:"node_ref"`
Name string `json:"name"`
// State is the node's standing: unchecked, clean, dirty, unreachable
// or abandoned.
State string `json:"state"`
// CheckState is the latest check's own result.
CheckState string `json:"check_state,omitempty"`
Checks []PathResult `json:"checks,omitempty"`
Detail string `json:"detail,omitempty"`
CheckedAt *time.Time `json:"checked_at,omitempty"`
AbandonedAt *time.Time `json:"abandoned_at,omitempty"`
AbandonedBy string `json:"abandoned_by,omitempty"`
AbandonReason string `json:"abandon_reason,omitempty"`
}
NodeStatus is a forward node's standing in the upgrade.
func Abandon ¶
func Abandon(ctx context.Context, db *gorm.DB, given, reason, actor string, now time.Time) (NodeStatus, error)
Abandon marks an unreachable node as abandoned: the drop no longer waits for it. Its old runtime, if any, stays on the machine.
func Check ¶
func Check(ctx context.Context, db *gorm.DB, options CheckOptions) ([]NodeStatus, error)
Check runs every path on the forward nodes, records each node's result and answers the nodes' standing.
func (NodeStatus) Ready ¶
func (s NodeStatus) Ready() bool
Ready reports whether the node no longer holds up the drop.
type PackageAdoption ¶
type PackageAdoption struct {
PluginID string `json:"plugin_id"`
Target string `json:"target"`
Version string `json:"version"`
Tables []string `json:"tables"`
}
PackageAdoption is an installed package release that still adopts flux tables. A storage lease fails as a whole when an adopted table is missing (packagestore.ErrGrantTargetMissing), so dropping a table such a release adopts would take the whole package down, the forward package's v4 API included, until a release without the flux routes (F5d) is installed.
type PathResult ¶
type PathResult struct {
Path string `json:"path"`
// State is clean, dirty, unreachable or not_needed.
State string `json:"state"`
Detail string `json:"detail,omitempty"`
// Items counts the legacy resources the path removed or checked.
Items int `json:"items,omitempty"`
}
PathResult is one path's result on one node.
type PreconditionOptions ¶
type PreconditionOptions struct {
// BackupPath is the operator's database backup (--backup-taken); empty
// looks for a backup Control's backup service made.
BackupPath string
// SkipBackup leaves the backup check out (the status command, which
// only reports).
SkipBackup bool
Now time.Time
}
PreconditionOptions configure the checks.
type Preconditions ¶
type Preconditions struct {
Archive *model.ForwardLegacyArchive `json:"archive"`
Nodes []NodeStatus `json:"nodes"`
Backup *Backup `json:"backup"`
// Packages are installed package releases that still adopt a flux
// table.
Packages []PackageAdoption `json:"packages"`
// Present and Missing split DropTables by whether they exist now.
Present []string `json:"present"`
Missing []string `json:"missing"`
// Blockers lists every unmet precondition; the drop runs only when it
// is empty.
Blockers []string `json:"blockers"`
}
Preconditions are what the drop needs, and what is missing.
func CheckPreconditions ¶
func CheckPreconditions(ctx context.Context, db *gorm.DB, options PreconditionOptions) (*Preconditions, error)
CheckPreconditions answers every precondition of the drop except the confirmation phrase.
type RefusedError ¶
type RefusedError struct{ Reasons []string }
RefusedError lists every reason the drop refused.
func (*RefusedError) Error ¶
func (e *RefusedError) Error() string
type WriteOptions ¶
type WriteOptions struct {
// Output is a file that must not exist yet, or an existing directory
// that receives a timestamped file. Empty means Dir.
Output string
// Dir is the archive directory (created 0700 when missing).
Dir string
Trigger string
Actor string
ControlVersion string
Now time.Time
}
WriteOptions say where an archive goes and who wrote it.
type WriteResult ¶
type WriteResult struct {
Record model.ForwardLegacyArchive `json:"record"`
// Tables is each archived table's row count, -1 when it did not exist.
Tables map[string]int64 `json:"tables"`
}
WriteResult is a written and recorded archive.
func StartupArchive ¶
func StartupArchive(ctx context.Context, db *gorm.DB, dir, controlVersion string, now time.Time) (*WriteResult, error)
StartupArchive writes the archive at Control's first v4.2 start: when a flux table exists, the tables were not dropped, and no recorded archive file is still there. It answers nil when there was nothing to do.
func WriteArchive ¶
func WriteArchive(ctx context.Context, db *gorm.DB, options WriteOptions) (*WriteResult, error)
WriteArchive builds the archive, writes it (mode 0600, never over an existing file) and records it.