Skip to content

feat(vm-instance): allow attaching a disk to a running VM - #3978

Open
lf (lfinmauritius) wants to merge 1 commit into
cozystack:mainfrom
lfinmauritius:feat/vm-instance-hotpluggable-disks
Open

lf (lfinmauritius) wants to merge 1 commit into
cozystack:mainfrom
lfinmauritius:feat/vm-instance-hotpluggable-disks

Conversation

@lfinmauritius

@lfinmauritius lf (lfinmauritius) commented Aug 27, 2026 •

Copy link
Copy Markdown
Contributor

What this PR does

A disk added to disks of an existing VMInstance never reaches the running guest. KubeVirt classifies the change as non-live-updatable, sets RestartRequired on the VirtualMachine, and the volume only shows up after the guest is restarted. A blank disk is worse: its PVC binds on WaitForFirstConsumer, and the only consumer that could bind it is the VMI that is not getting the volume. So adding a data disk to a live workload requires a reboot, which defeats the point of a hot-attach for anything built on the aggregated API.

KubeVirt supports this declaratively behind the DeclarativeHotplugVolumes feature gate (Alpha since v1.6.0, Beta in v1.9.0), and the user guide requires the volume to carry hotpluggable: true:

volumes:
  - dataVolume:
      name: hotplug-disk
      hotpluggable: true
    name: disk1

The chart cannot express it — disks[] exposes only name and bus — so no consumer of the aggregated API can ask for a hot-attach. This adds an optional disks[].hotpluggable that renders it.

Why the template refuses some buses

ValidateHotplugDiskConfiguration (pkg/storage/admitters/storagehotplug.go) rejects a hotplugged disk whose bus is neither scsi nor virtio, and nothing defaults an empty bus before that admitter runs — getDiskBus returns the field as written, and every hotplug test in tests/storage/hotplug.go passes an explicit bus.

So hotpluggable: true with bus: sata, or with no bus at all, would render fine and then be refused at admission. That failure is expensive: it leaves the HelmRelease in UpgradeFailed, and a stuck release keeps every subsequent change from reaching the machine — a restart included. The template therefore fails early and names the disk. bootOrder needs no change: the same admitter only requires it to be greater than zero, and the chart already emits add $i 1.

bus itself is untouched, and a non-hotpluggable disk with no bus keeps rendering disk: {} exactly as #2643 intended.

Verification

  • helm unittest . — 5 new cases: the rendered field, its absence, both accepted buses, and the two refusals with their message.
  • Negative control: 4 of the 5 fail without the template change. The fifth is the backward-compatibility assertion, and passes either way by construction.
  • make generate output committed: values.schema.json, README.md, the ApplicationDefinition and api/apps/v1alpha1/vminstance/types.go.

A note for platform operators, beyond this PR

The field is inert until a cluster enables DeclarativeHotplugVolumes, and that gate is documented as incompatible with the deprecated HotplugVolumes one — "if both are declared, HotplugVolumes will have precedence until the time that HotplugVolumes is retired". packages/system/kubevirt/templates/kubevirt-cr.yaml declares HotplugVolumes (and tests/kubevirt_cr_test.yaml pins it), so switching is not additive.

It is also not free. The tenant CSI driver reaches the VMI removevolume subresource directly in the orphan-detach path added by #2866 (packages/apps/kubernetes/images/kubevirt-csi-driver/controller.go, RemoveVolumeFromVMI), and that path is gated on the deprecated HotplugVolumes alone — ephemeralHotplugSupported() in pkg/virt-api/rest/volumes.go ignores DeclarativeHotplugVolumes, and vmiVolumePatch then answers VMI %s/%s is owned by a VM. The normal attach and detach paths (AddVolumeToVM / RemoveVolumeFromVM, VM subresource) keep working under either gate; only that one breaks. Happy to open a separate issue for it if that is the right place.

Screenshots

No console code. The field reaches the dashboard through the schema-driven form (RJSF renders type: boolean as a checkbox from values.schema.json), so there is nothing to change in packages/system/dashboard/images/console — and nothing I can screenshot before the chart is deployed. I can attach one once it is running if that is wanted.

Downstream repositories

Walked the trigger map in docs/agents/contributing.md against the diff, file by file.

The provider hand-models disks[] as {name, bus} in internal/provider/vminstance_schema.go, in both the resource and the data source, plus its model and expand/flatten pair. Filed as an issue rather than a PR there: its api/apps/v1alpha1 pin has to move past this merge first, and shipping the attribute before that would hand users a Terraform field that silently does nothing.

Release note

feat(vm-instance): `disks[]` accepts an optional `hotpluggable` flag, which marks the volume so KubeVirt can attach or detach it while the VM is running. Requires the `DeclarativeHotplugVolumes` feature gate on the cluster, and `bus` to be `scsi` or `virtio`.

Summary by CodeRabbit

  • New Features

    • Added optional hotplug support for VM disks, allowing compatible disks to be attached or detached while the VM is running.
    • Added configuration and schema support for the hotpluggable disk option.
    • Hotplugging is supported with scsi or virtio buses and requires the appropriate feature gate.
  • Bug Fixes

    • Invalid hotplug configurations now fail validation with a clear error message.
  • Documentation

    • Updated VM instance configuration documentation and examples.

Assisted by Codex

A disk added to `disks` of an existing VMInstance never reaches the running
guest. KubeVirt classifies the change as non-live-updatable, sets
`RestartRequired` on the VirtualMachine, and the volume only appears after the
guest is restarted. A blank disk is worse: its PVC binds on
`WaitForFirstConsumer`, and the only consumer that could bind it is the VMI
that is not getting the volume.

KubeVirt supports this declaratively behind the `DeclarativeHotplugVolumes`
feature gate (Alpha since v1.6.0, Beta in v1.9.0), and requires the volume to
carry `hotpluggable: true`. The chart cannot express it: `disks[]` exposes only
`name` and `bus`, so no consumer of the aggregated API can ask for a
hot-attach. This adds an optional `disks[].hotpluggable` that renders it.

The bus is not free here. `ValidateHotplugDiskConfiguration` in
pkg/storage/admitters/storagehotplug.go refuses a hotplugged disk whose bus is
neither `scsi` nor `virtio`, and nothing defaults an empty bus before that
admitter runs - every hotplug test upstream passes an explicit bus. The
template therefore fails early, naming the disk, rather than letting the VM
patch be rejected later: a rejection at that point leaves the HelmRelease in
UpgradeFailed, and a stuck release keeps every subsequent change from reaching
the machine, a restart included. `bootOrder` needs no change, the same
admitter only requires it to be greater than zero.

Rendering is unchanged when `hotpluggable` is unset. Generated files are
updated accordingly, and unit tests cover the rendered field, its absence,
both accepted buses and the two refusals.

The field is inert until the cluster enables `DeclarativeHotplugVolumes`. That
gate is documented as incompatible with the deprecated `HotplugVolumes` one -
"if both are declared, HotplugVolumes will have precedence" - so the
deprecated gate has to be removed, not merely complemented.

Co-Authored-By: Claude Opus 5 <[email protected]>
Signed-off-by: Loïc Fontaine <[email protected]>
@github-actions github-actions Bot added the size/L This PR changes 100-499 lines, ignoring generated files label Aug 27, 2026
@github-actions github-actions Bot added area/uncategorized PR auto-labeler could not map title scope to a known area/*; please review kind/feature Categorizes issue or PR as related to a new feature labels Aug 27, 2026
@coderabbitai

coderabbitai Bot commented Aug 27, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 511ac4a9-05a6-42c4-8d89-2c27865cce9f

📥 Commits

Reviewing files that changed from the base of the PR and between c8e4d97 and f3e4f10.

📒 Files selected for processing (7)
  • api/apps/v1alpha1/vminstance/types.go
  • packages/apps/vm-instance/README.md
  • packages/apps/vm-instance/templates/vm.yaml
  • packages/apps/vm-instance/tests/hotpluggable_disks_test.yaml
  • packages/apps/vm-instance/values.schema.json
  • packages/apps/vm-instance/values.yaml
  • packages/system/vm-instance-rd/cozyrds/vm-instance.yaml

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


📝 Walkthrough

Walkthrough

The VM instance disk API and chart schemas add an optional hotpluggable field. The template validates that hotpluggable disks use scsi or virtio, renders the flag on valid volumes, and adds Helm tests for supported and invalid configurations.

Changes

Hotpluggable disk support

Layer / File(s) Summary
Disk contract and chart documentation
api/apps/v1alpha1/vminstance/types.go, packages/apps/vm-instance/values.schema.json, packages/system/vm-instance-rd/cozyrds/vm-instance.yaml, packages/apps/vm-instance/values.yaml, packages/apps/vm-instance/README.md
The disk API and schemas add the optional hotpluggable boolean. Values documentation describes its feature-gate and bus requirements and adds an example. The README table spacing is reformatted.
Hotpluggable volume rendering
packages/apps/vm-instance/templates/vm.yaml
The template rejects hotpluggable disks without a scsi or virtio bus. Valid disks render hotpluggable: true in their dataVolume entry.
Rendering behavior tests
packages/apps/vm-instance/tests/hotpluggable_disks_test.yaml
Helm tests cover unset hotplugging, scsi, virtio, sata, and a missing bus.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: kvaps, myasnikovdaniil

Sequence Diagram(s)

sequenceDiagram
  participant DiskValues
  participant vm_yaml as vm.yaml
  participant DataVolume
  DiskValues->>vm_yaml: provide bus and hotpluggable values
  vm_yaml->>vm_yaml: validate the bus
  vm_yaml->>DataVolume: render hotpluggable: true for valid disks
Loading

Merge Risk: 🔵 Low · up to f3e4f

The new disk option enables live attachment only when the cluster has DeclarativeHotplugVolumes enabled; the platform’s default configuration does not currently enable that gate, so using the option without platform alignment can fail instead of attaching the disk. The change is mergeable with explicit owner awareness and follow-up on feature-gate support.

🚥 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 1…
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 summarizes the primary change: adding support for attaching a disk to a running VM.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

lf (lfinmauritius) added a commit to lfinmauritius/cozystack that referenced this pull request Sep 2, 2026
Same content as fork branch feat/vm-instance-cozyplane-static-ip (commit 5187e11),
applied on the v1.6.1 bundle: values field, template block, test suite, and the
schema, README and RD regenerated with cozyvalues-gen v1.6.0 + hack/update-crd.sh.
The RD keeps the helm-install-disable-wait annotation (cozystack#3735) and the
hotpluggable schema (cozystack#3978).
lf (lfinmauritius) added a commit to lfinmauritius/cozystack that referenced this pull request Sep 2, 2026
Same content as fork branch feat/vm-instance-cozyplane-static-ip (commit 4ddc400),
applied on the v1.6.1 bundle: values field, template block, test suite, and the
schema, README and RD regenerated with cozyvalues-gen v1.6.0 + hack/update-crd.sh.
The RD keeps the helm-install-disable-wait annotation (cozystack#3735), the
hotpluggable schema (cozystack#3978) and its kubevirt chartRef.
Aleksei Sviridkin (lexfrei) added a commit that referenced this pull request Sep 11, 2026
## What this PR does

Guards the Kube-OVN `IP` lookup behind API discovery. The `vm-instance`
chart now renders on clusters using another CNI instead of trying to
query a CRD that is not installed.

The Helm regression suite mocks the IP object through
`kubernetesProvider` and pins both sides: with `kubeovn.io/v1/IP`
advertised the annotations carry the mocked MAC and IP, without it they
are absent even though the object exists, which is the branch the guard
adds. helm-unittest's fake lookup never errors on an unserved kind, so
only the second case distinguishes the guard; it fails on main and
passes here.

Open PRs #3991, #3978, #3931, and #3900 touch the same template. Their
current hunks do not alter this lookup; #3900 adds secondary-interface
annotations immediately after it, so it is the only nearby merge hunk to
watch.

### Screenshots

Not applicable; there is no 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:

### Release note

```release-note
fix(virtual-machine): render VM instances on clusters where the Kube-OVN IP API is not installed.
```

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

* **Bug Fixes**
* VM instances now render correctly in environments without the Kube-OVN
IP capability.
* OVN IP lookups and MAC/IP annotations are only generated when the
required Kube-OVN API is available.
* MAC/IP annotations are omitted when the capability is unavailable,
even if an IP object exists.

* **Tests**
* Added coverage for both supported and unsupported Kube-OVN IP
discovery scenarios.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

lf (@lfinmauritius) NOT LGTM: on current main this field does nothing, so it has to wait for the platform to switch to declarative hotplug.

Adding a disk to a running VM without a reboot is a real need, and the bus check is right. But the platform still enables the deprecated HotplugVolumes gate (packages/system/kubevirt/templates/kubevirt-cr.yaml). As your own note says, virt-controller skips declarative hotplug while that gate is on. So a tenant who ticks hotpluggable in the dashboard today gets the same RestartRequired as without it. A checkbox that silently has no effect is worse than no checkbox.

The switch is in progress in #4453, which drops HotplugVolumes and handles the kubevirt-csi removevolume path you described (together with #4451). Once it merges, this becomes a small and useful change. Before that:

  1. Rebase after #4453 lands. The branch already conflicts with main in packages/apps/vm-instance/README.md and the generated packages/system/vm-instance-rd/cozyrds/vm-instance.yaml, so re-run make generate.
  2. Drop "Requires the DeclarativeHotplugVolumes feature gate on the cluster" from the field description. A tenant cannot act on it, and after #4453 it is always true.
  3. Replace Co-Authored-By: Claude Opus 5 <[email protected]> with Assisted-by: LLM, see docs/agents/contributing.md.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/uncategorized PR auto-labeler could not map title scope to a known area/*; please review kind/feature Categorizes issue or PR as related to a new feature size/L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants