VM Snapshot Lifecycle#
KubeVirt VM snapshots capture disk state only across three custom resources: VirtualMachineSnapshot, VirtualMachineSnapshotContent, and VirtualMachineRestore. Snapshots are disk-consistent but never include memory; the runtime VirtualMachineStatus is explicitly zeroed during capture .
Key Source Files#
| File | Role |
|---|---|
pkg/storage/snapshot/snapshot.go | Main snapshot controller: lock, content creation, phase/condition management |
pkg/storage/snapshot/source.go | vmSnapshotSource: lock/unlock, freeze/unfreeze, Spec() divergence |
pkg/storage/snapshot/restore.go | Restore controller: PVC recreation, VM spec rebuild, ControllerRevision restore |
pkg/instancetype/revision/store.go | Instancetype/preference ControllerRevision migration (Spec → Status) |
Capture Flow#
1. Lock the source VM
Lock() sets vm.Status.SnapshotInProgress and adds a snapshot.kubevirt.io/snapshot-source-protection finalizer. If another snapshot is already in progress the lock returns false and the controller retries .
2. Freeze the filesystem (online only)
Freeze() issues a guest-agent freeze call via VirtualMachineInstance.Freeze(). There are three short-circuits:
- Paused VM: freeze is skipped; snapshot proceeds as crash-consistent .
- No guest agent: freeze is skipped with a warning .
- Offline VM: the freeze path is never entered.
3. Build SourceSpec — online vs. offline divergence
Spec() is the critical branch point:
- Online VM: reads the active
ControllerRevisionreferenced byvmi.Status.VirtualMachineRevisionNameviagetVMRevision(). Volume/disk lists are then overwritten from the current VM spec to catch hot-plug changes . - Offline VM: deep-copies
vm.Specdirectly and resets status to empty .
In both paths, captureInstancetypeControllerRevisions() is called immediately after — see ControllerRevision capture below.
4. Create VirtualMachineSnapshotContent and VolumeSnapshots
createContent() builds VolumeBackup entries for each snapshottable volume — PersistentVolumeClaim, DataVolume, and MemoryDump . For each entry, a Kubernetes VolumeSnapshot resource is created by updateVMSnapshotContent() .
5. Unfreeze and unlock
After all VolumeSnapshots are ReadyToUse, the controller calls Unfreeze() and then Unlock() (removes finalizer, clears SnapshotInProgress).
6. Consistency indications
updateSnapshotSourceIndications() stamps VirtualMachineSnapshot.Status.Indications:
| Indication | Condition |
|---|---|
OnlineSnapshot | VM was running |
GuestAgent | Online + agent present |
NoGuestAgent | Online + no agent |
Paused | Online + paused |
QuiesceTimeout | Freeze timed out (Windows VSS "fsfreeze is limited") |
PartialSnapshot | Some volumes lacked a VolumeSnapshotClass |
Offline snapshots clear both Indications and SourceIndications .
ControllerRevision Capture in Snapshots#
When an instancetype or preference is in use, its RevisionName points to a ControllerRevision owned by the VM. captureInstancetypeControllerRevision() deep-copies that CR, replaces the VM name with the snapshot name in the CR name, and re-parents the new CR to the VirtualMachineSnapshot as an owner reference. The resulting snapshot-scoped CR name is written back to vm.Spec.Instancetype.RevisionName (or Preference.RevisionName) in the captured spec .
Restore Flow#
A VirtualMachineRestore targets an existing or new VM. The controller enforces that the target VM must have no running VMI before proceeding; the targetReadinessPolicy field controls behavior when the target is still running (WaitEventually, StopTarget, WaitGracePeriodAndFail, FailImmediate) .
PVC restoration via reconcileVolumeRestores(): for each VolumeBackup in the content (excluding MemoryDump volumes, which are always skipped), a new PVC is created using the VolumeSnapshot as dataSourceRef. The restore policy (InPlace, PrefixTargetName, or default RandomizeNames) controls PVC naming .
VM spec rebuild via generateRestoredVMSpec(): the snapshot spec is copied verbatim except:
- Volume and DataVolumeTemplate names are updated to the new PVC/DV names.
MemoryDumpvolumes are dropped .- A newly created VM starts halted (
Running=falseorRunStrategyHalted); an existing VM preserves its previous run strategy . - If the restore target is the same VM,
setLegacyFirmwareUUID()preserves the firmware UUID for stability .
ControllerRevision restore via restoreInstancetypeControllerRevisions(): the snapshot-scoped CR name is read from vm.Spec.Instancetype.RevisionName, the snapshot name is replaced with the target VM name, and a new CR is created (or an existing identical one reused). If the existing CR's data differs from the snapshot's (CRs are immutable), the existing one is deleted and recreated . Ownership is then transferred to the restored VM by claimInstancetypeControllerRevisionsOwnership().
ControllerRevision Migration: VM Spec → Status#
In current code, the canonical location for instancetype ControllerRevision references is vm.Status.InstancetypeRef / vm.Status.PreferenceRef, not vm.Spec.Instancetype.RevisionName. The migration is handled by pkg/instancetype/revision/store.go:
Store()callsstoreInstancetypeRevision()/storePreferenceRevision(), which populatevm.Status.InstancetypeRef.ControllerRevisionRef.Nameand patch viaPatchStatus().- Read path in
pkg/instancetype/find/revision.go: first checksvm.Spec.Instancetype.RevisionNamefor backward compatibility, then falls back tovm.Status.InstancetypeRef.ControllerRevisionRef.Name. - The snapshot capture code still reads
RevisionNamefrom spec because the snapshot stores a frozen copy of the VM spec at capture time; the restore code similarly writes back tovm.Spec.Instancetype.RevisionNamebefore creating or updating the VM.
Implication for snapshot/restore: A snapshot taken from a VM that has migrated to status-based references will have a non-empty
vm.Spec.Instancetype.RevisionNamein its captured spec (theSpec()path copiesvm.Specdirectly for offline VMs, or the ControllerRevision-decoded spec for online VMs). The restore path therefore correctly reconstructs the CR by reading from spec — no extra handling is needed for the Spec→Status migration boundary.