Skip to content

Add the BlockCAS driver, and stop GenerateSnapshotName panicking - #258

Open
anisurrahman75 wants to merge 11 commits into
masterfrom
block-cas-driver
Open

anisurrahman75 wants to merge 11 commits into
masterfrom
block-cas-driver

Conversation

@anisurrahman75

@anisurrahman75 anisurrahman75 commented Aug 25, 2026 •

Copy link
Copy Markdown
Contributor

Two changes — say the word and I will split them.

1. The BlockCAS driver and its stats

Adds a BlockCAS driver alongside the existing per-addon drivers, with a matching BlockCASStats on Component.

Incremental backups of a KubeVirt VM disk cannot be represented by a restic snapshot id: a restic snapshot of an incremental export holds only the changed bytes with no offset information, so it is not restorable. The block-CAS model cuts a disk on a fixed grid, stores each block once under the hash of its contents, and writes one complete manifest per snapshot. An incremental merges its changed slots over its parent, so a restore reads exactly one manifest rather than replaying a chain.

BlockCASStats records the manifest key, grid size, parent snapshot and checkpoint, and what the run moved (slots considered, blocks uploaded vs deduped, zero slots skipped, bytes uploaded).

Note that apis.Driver and the Addon task field each carry their own enum marker, and controller-gen emits both under allOf — a value must satisfy both, so both are updated.

2. GenerateSnapshotName panics on names without a numeric suffix

subMatches := backupSessionRegex.FindStringSubmatch(backupSession)
return meta.ValidNameWithPrefixNSuffix(repoName, subMatches[1], subMatches[2])

FindStringSubmatch returns nil when the name does not end in -<digits>, and a hand-created BackupSession rarely does. Splitting is now the named helper SplitBackupSessionName, which returns the whole name and an empty suffix instead. kubestash/kubestash#388 uses it to fix three more copies of the same bug, one of which sits on a delete path and wedged sessions permanently.

Both changes are purely additive; the split helper is covered by a table test.

Consumed by kubestash/kubevirt#4.

Related

The CBT work spans three repositories and reviews best in this order:

PR Role
#258 the BlockCAS driver, BlockCASStats, the KubeVirtArchiver CRD, and SplitBackupSessionName
kubestash/kubestash#388 operator RBAC, the KubeVirtArchiver reconciler, and the regex-panic fixes
kubestash/kubevirt#5 the plugin: block store, resident archiver loop, restore, GC — and docs/cbt/, including the design and an agent-facing testing runbook
kubestash/installer#367 the cbt-volume-backup catalog task
kubestash/kubevirt#4 Phase A, merged — full CBT backups

Picks up the local-backend repository path normalization (filepath.Join),
which removes the redundant double slash in RESTIC_REPOSITORY for local
(PVC/NFS) backends.

Signed-off-by: Anisur Rahman <anisur@appscode.com>
A component stored in a content-addressed block store records the key of its
manifest rather than a restic snapshot id: a disk is cut on a fixed grid, each
block is kept once under the hash of its contents, and one manifest lists the
block for every slot. That is what lets an incremental backup be restored on
its own instead of by replaying a chain.

Follows the existing per-driver stats pattern (WalG, Medusa, Solr,
ClickHouseBackup, Neo4jAdmin).

Signed-off-by: Anisur Rahman <anisur@appscode.com>
apis.Driver and the Addon task field each carry their own enum marker, and
controller-gen emits both under allOf, so a value must satisfy both. Adding it
in one place only left BlockCAS rejected.

Signed-off-by: Anisur Rahman <anisur@appscode.com>
GenerateSnapshotName indexed the regex match without checking it, so any
BackupSession whose name does not end in -<digits> panicked the caller. A
hand-created session rarely does. Splitting is now a named helper that returns
the whole name and an empty suffix when there is no match.

Signed-off-by: Anisur Rahman <anisur@appscode.com>
@anisurrahman75 anisurrahman75 changed the title Add the BlockCAS driver and its stats Add the BlockCAS driver, and stop GenerateSnapshotName panicking Aug 25, 2026
Upstream fixed the GenerateSnapshotName panic independently while this branch
was open. Kept that behaviour but expressed through SplitBackupSessionName,
which kubestash/kubestash#388 needs to fix the same bug in three more callers.

Signed-off-by: Anisur Rahman <anisur@appscode.com>
A scheduled backup captures one point in time and records a single ManifestKey.
An archiver captures a series, so it records a checkpoint index key, a bounded
recent window, the first and last checkpoint times that bound the restorable
range, and counters.

The index itself stays in the backend. It cannot be trimmed — a point-in-time
restore may need any entry — so at a ten-minute interval the object would
outgrow the API server long before retention expires.

FailedCheckpoints and LastFailure matter more than they look: a resident loop
keeps running through failures, so counters are the only signal it is sick.

Signed-off-by: Anisur Rahman <anisur@appscode.com>
archiver.kubestash.com/v1alpha1 is addon-agnostic, so future addons add their
own archiver kinds beside KubeVirtArchiver instead of inventing a group each.

The spec follows KubeDB's archivers: virtualMachines, backupStorage,
encryptionSecret, retentionPolicy, fullBackup, manifestBackup, cbtBackup, pause
and deletionPolicy. Three things differ, and each is forced:

  * opt-in is selector-only. We do not own the VirtualMachine CRD, so there is
    no field on the VM to set the way KubeDB sets one on a database.
  * fullBackup defaults to the BlockCAS driver, not VolumeSnapshotter. Fulls and
    checkpoints share one block store, which is what makes a daily full a hash
    comparison rather than a whole-disk upload.
  * cbtBackup carries no scheduler. Its absence is what marks the resident half,
    the same convention KubeDB uses for logBackup; a test asserts no such field
    creeps in.

Status reports per matched VM whether changed-block tracking is Enabled, the
raw state and a reason. Enabling CBT needs a VM restart, so the controller will
validate and report rather than restart production VMs on a label change — the
per-VM list is where the user reads what to do about it.

Signed-off-by: Anisur Rahman <anisur@appscode.com>
It reconciles into BackupConfiguration, Job and Snapshot, so it belongs
in the group that already owns backup configuration rather than in an
archiver group of its own. KubeDB splits archivers by group because it
has one kind per database engine; there is only ever one of these.

Drops apis/archiver entirely: its doc.go, groupversion_info.go, install
and fuzzer packages existed only to stand up the second group.

Signed-off-by: Anisur Rahman <anisur@appscode.com>
A KubeVirt CBT archiver writes a checkpoint every ten minutes, and none of
them gets a Snapshot object of its own: the resident loop keeps ONE Snapshot
Running and records the checkpoints in an index in the backend. So the
restorable points are far finer-grained than the Snapshot list a `pitr`
targetTime can select from, and the addon has to do the resolving.

pointInTime is that surface: the newest restorable point at or before the
given time, resolved by the addon against its own history.

Signed-off-by: Anisur Rahman <anisur@appscode.com>
A resident archiver's snapshot never completes by design: its component stays
Running until archiving stops, so its metadata cannot have been uploaded yet.
CalculatePhase read that as Failed, and because the upload is only attempted
once every component has finished, nothing ever retried it — a healthy archiver
writing checkpoints every two minutes read as permanently broken.

Seen live: one transient DNS failure to S3 latched the snapshot to Failed while
the loop went on to write 507 checkpoints.

Signed-off-by: Anisur Rahman <anisur@appscode.com>
A cleaner only creates prune_errors_by_repo.json when it has an error to
record, and the BlockCAS cleaner never writes one at all. Reading it
unconditionally failed every BlockCAS retention job at the final
update-retention-policy-status step, even though the prune itself had
succeeded. Absence means no errors; a cleaner that died before it could
report is caught by the job's own exit code.

Signed-off-by: Anisur Rahman <anisur@appscode.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant