Skip to content

fix(vm-disk): refuse to clone a golden image that does not exist - #4567

Merged
IvanHunters merged 1 commit into
mainfrom
fix/vm-disk-refuse-missing-golden
Oct 5, 2026
Merged

IvanHunters merged 1 commit into
mainfrom
fix/vm-disk-refuse-missing-golden

Conversation

@lexfrei

Copy link
Copy Markdown
Contributor

What this PR does

Stacked on #4392, so the base is fix/vm-default-images-health-check. Retarget to main once #4392 merges.

A new vm-disk with source.image now fails to render when the golden image it names does not exist, and the error names the image and cozy-public. Before this the chart cloned cozy-public/vm-default-images-<name> without looking, and CDI waited for that PVC forever while the VMDisk stayed Ready. That happens on a typo, and on every cluster where the opt-in cozystack.vm-default-images package is off, so the message also says to add it to bundles.enabledPackages.

The golden counts as missing only when neither its DataVolume nor its PVC exists. CDI clones the PVC, so a PVC with no DataVolume is still a usable source, and a DataVolume whose PVC is not created yet is an import about to start.

Under helm template every lookup is empty, so the check runs only when the release namespace itself is visible. The same trick is already used in the kubernetes and seaweedfs charts. Existing disks are exempt the same way #4392 exempts them, because their DataVolume spec is immutable. A disk refused while the catalog is off renders by itself once the golden appears, since app HelmReleases retry on failure.

Tests are in packages/apps/vm-disk/tests/dv_golden_image_test.yaml: refusal of a missing golden, a PVC without a DataVolume, a DataVolume before its PVC, rendering with no cluster, and an existing disk whose golden was removed.

Closes #4548

Screenshots

No UI change.

Downstream repositories

Release note

fix(vm-disk): a new disk with source.image now fails with a clear error when the golden image does not exist in cozy-public, instead of waiting forever for a clone that never starts.

@coderabbitai

coderabbitai Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository: cozystack/cozystack/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 15d35458-becf-4a1d-9d7d-79de2f67a112

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added size/L This PR changes 100-499 lines, ignoring generated files area/virtualization Issues or PRs related to virtualization (kubevirt, cdi, vmi, vm-import) kind/bug Categorizes issue or PR as related to a bug labels Sep 29, 2026
A new disk on source.image turns the name into a clone of
cozy-public/vm-default-images-<name> without looking it up. When that
golden does not exist, because of a typo or because the opt-in
vm-default-images package is not enabled, CDI records
CloneWithoutSource on the DataVolume and waits forever, while the
VMDisk stays Ready and the user never sees why.

Fail the render instead, naming the image and cozy-public, the same way
a golden whose import Failed is already refused. The golden counts as
missing only when neither its DataVolume nor its PVC exists: CDI
clones the PVC, so a PVC with no DataVolume is a usable source, and a
DataVolume whose PVC CDI has not created yet is an import about to
start. An empty lookup only means "missing" when the release namespace
itself is visible; under helm template every lookup is empty and the
chart must still render. Existing disks stay exempt, as their
DataVolume spec is immutable.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>
@lexfrei
Aleksei Sviridkin (lexfrei) force-pushed the fix/vm-default-images-health-check branch from 5cc9052 to 5d2c82c Compare September 29, 2026 19:16
@lexfrei
Aleksei Sviridkin (lexfrei) force-pushed the fix/vm-disk-refuse-missing-golden branch from 1a5b7fb to 3d5f9f2 Compare September 29, 2026 19:16
Base automatically changed from fix/vm-default-images-health-check to main October 2, 2026 12:44
@IvanHunters
IvanHunters deleted the branch main October 2, 2026 12:44
@IvanHunters IvanHunters closed this Oct 2, 2026

@IvanHunters IvanHunters left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

This refuses a source.image disk whose golden is absent, and it does so in the one way that is safe at render time. I checked the three things that make or break a guard like this, and all three hold.

A valid golden is never refused. The miss fires only when the golden's DataVolume and its PVC are both absent, so the two orders CDI can leave a half-created golden in are each safe: a DataVolume that exists before its PVC keeps not $golden false, and a PVC that outlives its DataVolume is caught by the PVC term. Go's and short-circuits, so when the DataVolume is present the PVC and Namespace lookups never run. The "DataVolume exists before CDI created its PVC" and "golden PVC that has no DataVolume" cases cover both.

A transient read is not mistaken for absence. Helm's lookup returns empty only on a 404; any other error (timeout, Forbidden, 5xx) propagates and aborts the render, so the HelmRelease fails and retries rather than rendering as if the image were gone. I confirmed this against helm.sh/helm v3.20.2 and v4.2.3 pkg/engine/lookup_func.go, which are identical here. The read is a live dynamic-client GET, not a cached informer, so a just-created golden is seen immediately.

The error is raised, not swallowed: it is a fail, which is the whole point of the change.

The existing-disk exemption still wraps the whole golden block, so an in-place upgrade of an already-created disk is never blocked, and the immutability logic below sits under the opposite if $existingDV branch, so the two never interact. helm template stays green through the release-namespace gate, and both that trick and a cross-namespace PVC lookup in cozy-public are already in tree (packages/apps/kubernetes/templates/cluster.yaml:156, packages/apps/kubernetes-nodes/templates/nodegroup.yaml:371); the renderer runs as flux cluster-admin, so the two new lookups add no RBAC surface.

Tests pass 60/60, and each term of the new conjunction is independently pinned: reverting the missing-golden branch, the Namespace gate, or the PVC term each reddens a distinct case, which I checked by mutation. Merging current origin/main into the head keeps them green.

Two smaller things, neither blocking. The Namespace gate is pinned only by the "renders without a cluster" case; a case with a cluster present but the release namespace not visible would name the gate directly. And the body still says the PR is stacked on #4392 and should be retargeted once it merges, but the base is already main and the merge-base is #4392's own commit, so that note is stale now.

{{- $golden := lookup "cdi.kubevirt.io/v1beta1" "DataVolume" "cozy-public" $desiredSource.pvc.name }}
{{- if eq (dig "status" "phase" "" $golden | toString) "Failed" }}
{{- fail (printf "source.image %q: golden DataVolume %q in cozy-public failed to import (phase Failed), so a clone of it would never complete. Ask the platform administrator to check the importer pod in cozy-public, then delete that DataVolume to retry the import." .Values.source.image.name $desiredSource.pvc.name) }}
{{- else if and (not $golden) (not (lookup "v1" "PersistentVolumeClaim" "cozy-public" $desiredSource.pvc.name)) (lookup "v1" "Namespace" "" .Release.Namespace) }}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MINOR] "existing disk" here means existing DataVolume, not existing release

The golden block is skipped only when $existingDV is set, i.e. when the disk's own DataVolume is present. If that DataVolume is deleted out of band while the HelmRelease lives on, and the golden is also gone, this new branch now fails the render on every reconcile instead of recreating a DataVolume that would hang Pending forever. The direction is an improvement (a stuck disk becomes a visible error), but it is a change to the behaviour of an already-installed release that neither the comment nor a test records. Worth a line in the comment, since "an existing disk is exempt" reads as "an existing release is exempt".

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed, the exemption keys on the DataVolume, not on the release. I am keeping the behaviour, since a render error is better than a recreated DataVolume stuck in Pending. The comment should say "existing DataVolume", and I will change it when we next touch these lines.

asserts:
- notFailedTemplate: {}

- it: does not look the golden up for a source other than an image

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[NIT] this case asserts notFailedTemplate, which cannot see whether the lookup ran

notFailedTemplate only proves the render did not fail; it cannot observe that the golden lookup was skipped for a non-image source. The case has teeth only incidentally: moving the guard out of the image branch would nil-deref $desiredSource.pvc.name. The title promises it checks that the golden is not looked up, which the assertion does not actually verify.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, the case pins that an http source is never refused, and a lookup whose result goes unused would still pass it. I will rename it when we next touch the file.

@IvanHunters
IvanHunters merged commit ed20c53 into main Oct 5, 2026
37 checks passed
@IvanHunters
IvanHunters deleted the fix/vm-disk-refuse-missing-golden branch October 5, 2026 11:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/virtualization Issues or PRs related to virtualization (kubevirt, cdi, vmi, vm-import) kind/bug Categorizes issue or PR as related to a bug size/L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

vm-disk: source.image does not refuse a golden image that does not exist

2 participants