Skip to content

fix(cdi): omit the CDI clone strategy override when unset - #4138

Merged
Aleksei Sviridkin (lexfrei) merged 1 commit into
mainfrom
fix/cdi-clone-strategy-optional
Sep 10, 2026
Merged

Aleksei Sviridkin (lexfrei) merged 1 commit into
mainfrom
fix/cdi-clone-strategy-optional

Conversation

@lexfrei

@lexfrei Aleksei Sviridkin (lexfrei) commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

What this PR does

cdi-cr.yaml rendered cloneStrategyOverride unconditionally, so there was no way to let CDI pick a clone strategy per StorageProfile. The CRD enum is copy/snapshot/csi-clone with no empty member, so neither obvious workaround got there: an empty value rendered cloneStrategyOverride: "", which that enum does not accept, and a null one rendered a bare key. Both checked against main with helm template rather than assumed.

Now the key is guarded on a non-empty value, same as uploadProxyURLOverride just below it in the same file. With the field absent, CDI reads the target StorageClass's StorageProfile and uses its clone strategy.

Default stays csi-clone. Rendered at the default and diffed against main, the output is byte-identical, so nothing already deployed moves.

Tests

tests/clone_strategy_test.yaml pins both directions: absent when the value is empty, absent when it is null, csi-clone on the chart default, snapshot through unchanged. It sits alongside the existing importer-resources and standalone_render_test suites, with its own file and suite name and no shared test names; make test runs all three.

I checked the suite can fail rather than assuming it works. Reverting the guard reddens the two absent-field cases, deleting the field outright reddens the default and passthrough cases, hardcoding the value reddens the passthrough case, and rendering a bare key reddens all four. The two sibling suites stay green through every one of those, so the reds are attributable to this suite.

The null case is pinned as an input rather than argued from with treating null and empty alike. It cannot pass vacuously: flipping its assertion to expect the default returns unknown path, not a csi-clone mismatch, which is what shows the null actually reaches the template instead of the assertion passing on a value that never arrived.

Known gaps

copy is accepted by the enum but no test drives it, so the suite does not pin that it reaches the CR. Concretely: wrapping the guard in {{- if ne . "copy" }}, which silently drops that one strategy, still passes all nine tests. Named here rather than fixed so the boundary is visible to the next reader instead of being rediscovered.

Scope

Context: #2908. This is the first of the three directions that issue lists, so it does not close it and carries no Closes. The default is deliberately left at csi-clone, since whether that is the right default is the second direction and a separate decision. The third, vm-disk warning on a cross-StorageClass request, is untouched. An operator who has not set cloneStrategyOverride: "" sees the reported symptom exactly as before.

Screenshots

Not a UI change.

Downstream repositories

Walked the trigger map against the diff. It is one guarded key in a packages/system chart, a values comment, and a test suite: no package added or renamed, no values.schema.json (this chart has none), no ApplicationDefinition, no namespace, nothing under hack/, no platform values key. The ccp entry covers hack/package.mk and make-target behaviour, and this touches neither. Nothing in the map reaches it.

Release note

fix(cdi): `cloneStrategyOverride` in the `kubevirt-cdi` package can now be left empty, which omits the field and lets CDI pick a clone strategy per StorageProfile. The default is unchanged at `csi-clone`.

@github-actions github-actions Bot added area/virtualization Issues or PRs related to virtualization (kubevirt, cdi, vmi, vm-import) size/M This PR changes 30-99 lines, ignoring generated files kind/bug Categorizes issue or PR as related to a bug labels Sep 7, 2026
@coderabbitai

coderabbitai Bot commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: 2ef02d64-5052-4f8e-9329-47042d2beb79

📥 Commits

Reviewing files that changed from the base of the PR and between 829926b and 650ada1.

📒 Files selected for processing (1)
  • packages/system/kubevirt-cdi/tests/clone_strategy_test.yaml

Included review availability: Your plan provides up to 8 included reviews per hour; 2 remain after this review.


📝 Walkthrough

Walkthrough

The CDI chart now omits cloneStrategyOverride for empty or null values. Helm tests validate omission, the default csi-clone, and a configured snapshot value. The values file documents the empty-value behavior.

Changes

CDI clone strategy configuration

Layer / File(s) Summary
Conditional rendering and validation
packages/system/kubevirt-cdi/templates/cdi-cr.yaml, packages/system/kubevirt-cdi/values.yaml, packages/system/kubevirt-cdi/tests/clone_strategy_test.yaml
The template emits cloneStrategyOverride only when configured. The values file documents empty-value behavior. Helm tests cover empty, null, default, and explicit values.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to 650ad

Empty or null clone-strategy overrides are now omitted so CDI can select a StorageProfile-specific strategy, while default and explicit strategy behavior remain covered.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: omitting the CDI clone strategy override when it is unset.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/cdi-clone-strategy-optional

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.

@lexfrei
Aleksei Sviridkin (lexfrei) force-pushed the fix/cdi-clone-strategy-optional branch 2 times, most recently from ef31220 to b3d134f Compare September 7, 2026 19:20
@lexfrei
Aleksei Sviridkin (lexfrei) force-pushed the fix/cdi-clone-strategy-optional branch 2 times, most recently from 829926b to 650ada1 Compare September 8, 2026 00:01
templates/cdi-cr.yaml rendered cloneStrategyOverride unconditionally,
so the chart's values offered no way to leave the choice to CDI's own
per-StorageProfile selection. The CRD enum takes copy, snapshot and
csi-clone and carries no empty member, so an empty value rendered
cloneStrategyOverride: "" which that enum does not accept, and a null
one rendered a bare key. Guard the field on a non-empty value, the
way uploadProxyURLOverride is already guarded just below it.

The default stays csi-clone, so a release that overrides nothing
renders what it rendered before. Whether csi-clone is the right
default is a separate question, untouched here.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>

@kvaps Andrei Kvapil (kvaps) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Verdict

LGTM as of 5db773892. Cleanest of the four CDI-family PRs in this batch, and the test earns its place.

The premise checks out from the tree: packages/system/kubevirt-cdi-operator/templates/cdi-operator.yaml:101-108 (and the v1beta1 copy at :2624-2631) declares cloneStrategyOverride with enum: [copy, snapshot, csi-clone] and no empty member, while the template rendered {{ quote .Values.cloneStrategyOverride }} unconditionally — so an operator who set the value to "" produced cloneStrategyOverride: "", which that enum refuses at admission. There was no way to ask CDI to make its own per-StorageProfile choice.

Wrapping the line in {{- with }} is the right fix and omitting is the right target state rather than substituting something: upstream v1.66.1's own cdi-cr.yaml carries no cloneStrategyOverride at all, and with the field absent CDI falls back to the per-StorageProfile cloneStrategy. Both "" and ~ take the falsy branch, so the key is absent rather than set to a different value.

The test is non-vacuous and I had it checked by execution rather than by reading: reverting the template to the unconditional line turns two cases red (omits the field when no strategy is configured, omits the field when the strategy is null), and the suite also pins the opposite direction with a configured snapshot passing through unchanged, so a template that dropped the field unconditionally would not satisfy it either. The null case cannot pass vacuously — if set: cloneStrategyOverride: null were a no-op the chart default csi-clone would render and the assertion would fail. 9/9 green at head.

No upgrade impact: values.yaml:9 keeps csi-clone as the default, so every existing cluster renders identically and the change only adds a state that was previously unreachable.

It composes with #4107 rather than colliding: no file overlap, and that PR's CR_SHA256 pins the upstream asset rather than the in-tree file, so this edit does not invalidate it. Its tests/local_templates_test.yaml asserts csi-clone against the chart default, which still holds.

One nit, pre-existing and not yours: packages/system/kubevirt-cdi/Makefile declares test: twice with identical recipes, so make warns and uses the last. #4107 removes the duplicate; whichever of these lands first could just as easily take it.

@lexfrei
Aleksei Sviridkin (lexfrei) merged commit 7c739a1 into main Sep 10, 2026
25 of 28 checks passed
@lexfrei
Aleksei Sviridkin (lexfrei) deleted the fix/cdi-clone-strategy-optional branch September 10, 2026 15:22
Aleksei Sviridkin (lexfrei) added a commit that referenced this pull request Oct 12, 2026
…4796)

## What this PR does

A VM disk created from a golden image into a StorageClass other than the
image's one can hang forever: its temporary clone PVC stays unbound, and
the user sees nothing. With the default `csi-clone` strategy, CDI checks
the source and target classes for a common provisioner, a matching
volume mode and room to expand. It never compares class names. So a
target class backed by the same provisioner gets no host-assisted
fallback, and the clone is left to the CSI driver. LINSTOR creates it
next to the source data, so on a target class that does not allow remote
volume access from every node (a node-local class, for example) the PVC
may never bind.

The vm-disk chart now refuses such a disk at render time and tells the
user which StorageClass to set. The check follows CDI's own clone
planner. The effective strategy comes from the CDI override, or else
from the target StorageProfile. It has to be `csi-clone`, or `snapshot`
when CDI gets a VolumeSnapshotClass of the provisioner. Once the
snapshot path has such a class it runs the same checks as csi-clone, and
LINSTOR ships one. Both classes have to share a driver (the golden's is
read from its PV, as CDI does), the volume modes have to match, and the
target class has to allow expansion. On top of that the chart refuses
only a LINSTOR target whose `allowRemoteVolumeAccess`, read the way
linstor-csi reads it, is not true; without the parameter that means a
layer list with no DRBD or NVMe layer. A remote-accessible LINSTOR class
and other drivers render. When the provisioner or the volume mode
differs, CDI falls back to a host-assisted copy, which completes, so the
disk renders as before. A target class without expansion is left alone
too, see the follow-ups.

The class check compares nothing when a lookup it needs returns nothing:
under `helm template`, before the golden PVC exists, or without the CDI
resource. An empty `storageClass` is compared as the class CDI would
pick for a kubevirt DataVolume, which is the default virt class,
otherwise the Kubernetes default class. Existing disks are exempt,
because their DataVolume spec is immutable. A disk counts as existing
when its DataVolume or its PVC exists, since CDI can garbage-collect a
completed DataVolume. Under `snapshot`, a snapshot class that both
StorageProfiles name but that does not exist is refused too, for any
driver, because CDI then fails to plan the clone instead of falling
back.

This is a breaking change: a new disk in a LINSTOR class without remote
access from every node rendered before and is refused now, and so is one
whose StorageProfiles name a snapshot class that does not exist. The
first case can include a clone that would happen to work: on a class
with `Immediate` binding, which I have not verified, or when the node
the clone is placed on already holds a replica of the golden image. The
way out is the golden image's class or the `copy` clone strategy.

How this covers the three fix directions in the issue:

1. Omitting `cloneStrategyOverride` when it is empty already landed on
main in #4138: the template skips the field for an empty or null value,
the values schema accepts the empty string, and the chart tests pin
both. This PR does not touch it.
2. I kept `csi-clone` as the default on purpose. Same-class clones are
the common case, and `csi-clone` is the fast path for them. Changing the
default is a separate decision, tracked in #4850.
3. vm-disk refuses the mismatch where the clone can hang, as described
above. That is narrower than the issue proposed, which was to refuse
every mismatch: every case CDI falls back on completes, and a clone into
a remote-accessible LINSTOR class should complete too. The last part
comes from reading the CDI and linstor-csi sources, I have not run it on
a cluster.

Follow-ups this PR does not cover:

- When several classes claim the default, CDI picks the newest one. The
chart does not reproduce that tie-break and compares nothing in that
case.
- The chart does not compare sizes. A target class without volume
expansion is never refused for remote access, even when the disk is no
larger than the golden image and CDI would attempt the clone.
- Under `snapshot` on a cluster that does not serve the snapshot API,
StorageProfiles naming a snapshot class still make CDI fail, and the
chart does not look that class up.
- A disk cloned from another disk (`source.disk`) into another class of
the same provisioner can hang the same way, and nothing refuses it yet.
- Whether `csi-clone` should remain the platform default now that it can
be turned off: #4850.

Closes #2908

### Screenshots

Not a UI change.

### Downstream repositories

- [x] No downstream repository is affected by this change
- [ ] [cozystack/website](https://github.com/cozystack/website) -
follow-up:
- [ ]
[cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack)
- follow-up:
- [ ]
[cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack)
- follow-up:
- [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up:
- [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up:
- [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) -
follow-up:
- [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) -
follow-up:
- [ ]
[cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server)
- follow-up:
- [ ]
[cozystack/external-apps-example](https://github.com/cozystack/external-apps-example)
- follow-up:
- [ ] [cozystack/examples](https://github.com/cozystack/examples) -
follow-up:
- [ ] [cozystack/community](https://github.com/cozystack/community) -
follow-up:

No values, schema or ApplicationDefinition change. Re-check notes for
the next CDI and linstor-csi bumps sit next to the version pins in the
kubevirt-cdi-operator and linstor Makefiles. The new note in the vm-disk
README reaches the website through the regular reference-page
regeneration.

### Release note

```release-note
fix(vm-disk)!: a new disk from a golden image is now refused with a clear error when CDI would CSI-clone it into another LINSTOR StorageClass that does not allow remote volume access from every node. Before, such a disk could stay pending forever with no error. A clone that would have landed on a node already holding a replica of the golden image is refused as well; use the golden image's StorageClass or the copy clone strategy.
```

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Prevented image-backed disks from being rendered when CDI’s selected
clone strategy cannot safely clone across differing storage classes,
including applicable LINSTOR remote-access and snapshot-class checks.
* Existing DataVolumes and same-namespace PVCs are not blocked by these
checks. Disk creation can proceed when required information is
unavailable or the selected strategy supports the operation.
* **Documentation**
* Clarified when image-backed disks may be refused and recommended using
the golden image’s storage class or the copy strategy as alternatives.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
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/M This PR changes 30-99 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants