Skip to content

Fix snapshot restore size, restore resizing, and CreateSnapshot/DeleteSnapshot idempotency - #43

Open
mw-0 wants to merge 6 commits into
shapeblue:mainfrom
mw-0:fix/snapshot-improvements
Open

mw-0 wants to merge 6 commits into
shapeblue:mainfrom
mw-0:fix/snapshot-improvements

Conversation

@mw-0

@mw-0 mw-0 commented Oct 2, 2026

Copy link
Copy Markdown

Fixes #37, Fixes #38, Fixes #39, Fixes #40, Fixes #41

Description of changes:

Each change is a separate commit.

Report snapshot size (#37). CreateSnapshot and ListSnapshots set SizeBytes, using CloudStack's virtualsize and falling back to the source volume's size. GetSnapshotByID and GetSnapshotByName now populate Size (and GetSnapshotByID populates CreatedAt).
Resize restored volumes to the requested size (#38). After CreateVolumeFromSnapshot, a volume smaller than requested is grown with ExpandVolume. If the resize fails, the undersized volume is deleted so the provisioner's retry starts clean.
Make CreateSnapshot idempotent (#39). The snapshot is looked up by name first:
if it exists for the same volume, it is returned;
if it exists for a different volume, the call returns AlreadyExists;
otherwise a new snapshot is created.
The lookup uses the client's default options, so it respects the project ID.
Move the restore path into createVolumeFromSnapshot. No behaviour change; this keeps CreateVolume within the nestif limit, and the now-unused //nolint:gocognit is removed.
Serialize CreateSnapshot per name and report ReadyToUse from the snapshot state (#39, #40).
A per-name lock returns Aborted to a concurrent call while the first is still running against CloudStack, so a retry can't race it.
cloud.Snapshot gains a State field. ReadyToUse is false while the snapshot is in progress, and a snapshot in Error state is returned as an error.
Treat already-destroyed snapshots as deleted (#41). DeleteSnapshot maps "is already destroyed" to not-found, so it succeeds idempotently.

New unit tests:

TestCreateSnapshotReportsSize
TestGrowVolumeFromSnapshot (resize, no-op and failure paths)
TestCreateSnapshotIsIdempotent
TestIsSnapshotReady
TestIsSnapshotGoneError

Testing performed:

golangci-lint run ./... (v1.63.4, repo config), make test and make test-sanity all pass.
Lab: CloudStack 4.23(KVM, NFS), a CKS cluster on Kubernetes 1.37.0. I ran the same end-to-end script against the stock image and against this branch

Additional stress test with csi-snapshotter --timeout=5s:
Stock: 6 duplicate snapshots, and the VolumeSnapshot never became ready.
This PR: one snapshot, READYTOUSE false until CloudStack showed BackedUp, and retries returned the existing snapshot.

If you want a bash script to run against fixed and main releases I can upload it.

mw-0 added 6 commits October 2, 2026 18:16
CreateSnapshotResponse and ListSnapshots entries never set SizeBytes, so
VolumeSnapshot.status.restoreSize was always 0. Kubernetes uses this value
to show the minimum restore size and to reject PVCs that are too small.

Use the snapshot virtualsize from CloudStack, falling back to the source
volume size. Also populate Size/CreatedAt in GetSnapshotByID and Size in
GetSnapshotByName so callers see the real snapshot size.
CloudStack creates a volume from a snapshot at the snapshot's size and
ignores a larger requested size. The driver returned that smaller
capacity, so external-provisioner rejected it with 'created volume
capacity X less than requested capacity Y', deleted the volume and
retried forever.

After creating the volume, resize it to the requested size when it is
smaller. If the resize fails, delete the undersized volume and return an
error. The node plugin already grows the filesystem on NodeStage.
CreateSnapshot always called CloudStack createSnapshot, so a retry from
csi-snapshotter (e.g. after a timeout) created a duplicate snapshot.
The CSI spec requires CreateSnapshot to return the existing snapshot
when one with the same name and source volume already exists.

Look up the snapshot by name first; return it if it belongs to the same
volume, return AlreadyExists if it belongs to a different volume, and
only create a new snapshot when none exists.
… state

A timed-out CreateSnapshot keeps running against CloudStack, so a retry
could miss the in-progress snapshot and create a duplicate. Hold a
per-name lock for the call and return Aborted to concurrent calls.

CreateSnapshot and ListSnapshots always returned ReadyToUse: true, even
while CloudStack was still creating or backing up the snapshot. Report
ReadyToUse=false until the snapshot leaves Allocated/Creating/
CreatedOnPrimary/BackingUp/Copying, and an error for the Error state.
If a snapshot was destroyed outside the driver, or an earlier delete
completed after the RPC timed out, CloudStack returns errorcode 431
'... is already destroyed'. That was not mapped to ErrNotFound, so
DeleteSnapshot returned Internal and the VolumeSnapshotContent (and its
VolumeSnapshot) could never be deleted. The CSI spec requires
DeleteSnapshot to succeed when the snapshot no longer exists.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment