Skip to content

feat: add CD-ROM hotplug volumes#9937

Merged
wheatdog merged 42 commits intoharvester:masterfrom
wheatdog:9261-hp-cdrom
Feb 13, 2026
Merged

feat: add CD-ROM hotplug volumes#9937
wheatdog merged 42 commits intoharvester:masterfrom
wheatdog:9261-hp-cdrom

Conversation

@wheatdog
Copy link
Member

@wheatdog wheatdog commented Jan 23, 2026

Problem:

To support inserting and ejecting CD-ROM volumes without a restart.

Solution:

According to the announcement, a new feature gate "DeclarativeHotplugVolumes" is introduced at KubeVirt 1.6 and the existing "HotplugVolumes" is going to be deprecated.

  1. Migrate the feature gate from "HotplugVolumes" to "DeclarativeHotplugVolumes"
  2. Add 2 new VM actions:
    1. injectCdRomVolume: add a new entry to volumes from VM spec and volumeClaimTemplates from VM annotation
    2. ejectCdRomVolume: remove the corresponding element from volumes and volumeClaimTemplates from VM. Then, delete the corresponding volume (pvc).
  3. Remove the existing VM action ejectCdRom and related checks -> prefer the new action ejectCdRomVolume that doesn't require a restart.
  4. VM mutator webhook would patch hotpluggable to true for volumes of SATA CD-ROM devices to enable hotplug volume automatically. -> For flexibility, this is handled from FE.
  5. For existing VMs with CD-ROM, the hotpluggable field would be patched when the VM is stopped / restarted. This is handled by vmControllerSetSataCdRomHotpluggable. -> Old VMs would act as is: see docs(hep): CD-ROM Hotplug Volumes [skip ci] #9840 (comment)
  6. Relax the live migration constrain for VM with CD-ROM devices and container disks.
  7. Use kubernetes.Interface instead of kubernetes.Clientset for type annotations since it's an interface, which is easier to be mocked in unit tests.

Related Issue(s):

#9261
#9779

Test plan:

  1. Feature Gate Verification: After enabling the DeclarativeHotplugVolumes feature gate, perform basic checks to ensure existing volume hot-plug functionalities are not regressed.
  2. Volume Insertion: For a running VM with an empty CD-ROM device, verify that a new image can be successfully inserted and that the content is accessible from within the guest OS.
  3. Volume Ejection: For a running VM with an occupied CD-ROM device, verify that the volume can be successfully ejected and is no longer accessible from within the guest OS. Additionally, the corresponding volume is deleted.
  4. Live Migration with CD-ROM: Verify that a running VM with a CD-ROM can be successfully live-migrated.
  5. Existing VM Compatibility: For a VM created before this enhancement, verify that after a restart, the CD-ROM volume becomes ejectable.

Additionally, please refer to the test plan in HEP: #9840

Additional documentation or context

https://kubevirt.io/user-guide/storage/hotplug_volumes/#inject-cd-rom

@wheatdog wheatdog changed the title feat: add cdrom hotplug volumes feat: add CD-ROM hotplug volumes Jan 23, 2026
@wheatdog wheatdog marked this pull request as ready for review January 23, 2026 10:19
@wheatdog wheatdog force-pushed the 9261-hp-cdrom branch 3 times, most recently from cea8ae0 to e30a0ec Compare January 26, 2026 06:16
@mergify
Copy link

mergify bot commented Jan 27, 2026

This pull request is now in conflict. Could you fix it @wheatdog? 🙏

Signed-off-by: Tim Liou <tim.liou@suse.com>
Signed-off-by: Tim Liou <tim.liou@suse.com>
Signed-off-by: Tim Liou <tim.liou@suse.com>
Signed-off-by: Tim Liou <tim.liou@suse.com>
Signed-off-by: Tim Liou <tim.liou@suse.com>
Signed-off-by: Tim Liou <tim.liou@suse.com>
Signed-off-by: Tim Liou <tim.liou@suse.com>
Signed-off-by: Tim Liou <tim.liou@suse.com>
Signed-off-by: Tim Liou <tim.liou@suse.com>
Signed-off-by: Tim Liou <tim.liou@suse.com>
Signed-off-by: Tim Liou <tim.liou@suse.com>
Signed-off-by: Tim Liou <tim.liou@suse.com>
Signed-off-by: Tim Liou <tim.liou@suse.com>
@ibrokethecloud ibrokethecloud self-requested a review February 5, 2026 22:51
ibrokethecloud
ibrokethecloud previously approved these changes Feb 5, 2026
Copy link
Contributor

@ibrokethecloud ibrokethecloud left a comment

Choose a reason for hiding this comment

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

lgtm. thanks.

}

func SupportEjectCdRomVolume(vm *kubevirtv1.VirtualMachine) (bool, error) {
volumeMaps := map[string]struct{}{}
Copy link
Contributor

Choose a reason for hiding this comment

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

same as above

Suggested change
volumeMaps := map[string]struct{}{}
if runtime.GOARCH == "arm64" {
return false, nil
}
volumeMaps := map[string]struct{}{}

}

func (h *vmActionHandler) ejectCdRom(ctx context.Context, name, namespace string, diskNames []string) error {
func getSataCdRomDiskPos(vm *kubevirtv1.VirtualMachine, deviceName string) (int, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I am a bit confused by this check and why we need to return device position

based on my reading https://kubevirt.io/user-guide/storage/hotplug_volumes/ as long as there is a cdrom available in the vm.spec.template.spec.devices[*].disk of type cdrom with a sata bus, we just need to add a hot pluggable volume in vm.spec.template.volumes with the same name as disk found from vm.spec.template.spec.devices[*].disk.

If there is no volume already attached to the cdrom disk, then we can hot plug a disk or if there is one present we can unplug the disk.

Looking and using the position to insert the volume is redundant.

Copy link
Member Author

@wheatdog wheatdog Feb 5, 2026

Choose a reason for hiding this comment

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

thanks, sorry I missed this comment. And yes, this turns out not just redundant but also problematic for cases like error out when inserting image to the last one of multiple empty cdrom. I've removed this and simplified the logic.

Copy link
Member Author

Choose a reason for hiding this comment

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

already addressed in 3528e33


// addVolume add a hotplug volume with given volume source and disk name.
// name -> VM name, namespace -> VM namespace, input.VolumeSourceName -> PVC name
func (h *vmActionHandler) addVolume(ctx context.Context, namespace, name string, input AddVolumeInput) error {
Copy link
Member Author

Choose a reason for hiding this comment

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

I found a regression to "Add Volume". The API return errors but the volume is actually attached successfully.

failed to update vm default/hp, error: Operation cannot be fulfilled on virtualmachines.kubevirt.io "hp": the object has been modified; please apply your changes to the latest version and try again

Screen.Recording.2026-02-06.at.3.51.23.PM.mov

It seems that "Detach Volume" is fine.

I'll take a look.

Copy link
Member Author

@wheatdog wheatdog Feb 6, 2026

Choose a reason for hiding this comment

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

This error comes from

func (h *vmActionHandler) updateVMVolumeClaimTemplate(vm *kubevirtv1.VirtualMachine, updateVolumeClaimTemplate func([]corev1.PersistentVolumeClaim) ([]corev1.PersistentVolumeClaim, bool)) error {
var volumeClaimTemplates []corev1.PersistentVolumeClaim
vmCopy := vm.DeepCopy()
anno := vmCopy.GetAnnotations()
if volumeClaimTemplatesJSON, ok := anno[util.AnnotationVolumeClaimTemplates]; ok {
if err := json.Unmarshal([]byte(volumeClaimTemplatesJSON), &volumeClaimTemplates); err != nil {
return fmt.Errorf("failed to unserialize %s, error: %v", util.AnnotationVolumeClaimTemplates, err)
}
}

Copy link
Member Author

@wheatdog wheatdog Feb 6, 2026

Choose a reason for hiding this comment

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

@w13915984028 @ibrokethecloud in 1d28d54, I've migrated the existing addVolume/removeVolume functions to the new declarative way such that update is called once to prevent the error.

Screen.Recording.2026-02-06.at.6.22.34.PM.mov

Signed-off-by: Tim Liou <tim.liou@suse.com>
Signed-off-by: Tim Liou <tim.liou@suse.com>
@mergify
Copy link

mergify bot commented Feb 10, 2026

This pull request is now in conflict. Could you fix it @wheatdog? 🙏

Copy link
Member

@w13915984028 w13915984028 left a comment

Choose a reason for hiding this comment

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

A few last questions, thanks.

}

func canInsertCdRomVolume(vm *kubevirtv1.VirtualMachine) bool {
if runtime.GOARCH == "arm64" {
Copy link
Member

Choose a reason for hiding this comment

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

it is better if the arm64 is defined as a const for public usage

Copy link
Member Author

Choose a reason for hiding this comment

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

sure.

Copy link
Member Author

Choose a reason for hiding this comment

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

fixed by 2fdd817

volumeMaps[volume.Name] = struct{}{}
}
}

Copy link
Member

Choose a reason for hiding this comment

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

L222 check len(volumeMaps) == 0 to return early

Copy link
Member Author

Choose a reason for hiding this comment

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

fixed by 5762271

continue
}
useSata := isDiskSataCdRom(&disk)
if !useSata {
Copy link
Member

Choose a reason for hiding this comment

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

this function is used for both UI menu render and webhook check, there are some differences:

UI render does not care error, as long as the first target is found, it could return directly, e.g. L236 could trigger return directly; and L233 could also cause UI render failure

webhook/controller check all devices to ensure no error

could we split it into two functions for each purpose?

Copy link
Member Author

Choose a reason for hiding this comment

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

The idea is to separate 2 intentions:

  1. Capability of insertion/ejection
  2. Correctness of VM spec

Though, as offline discussed, I would like to stick with the current implementation for now. Thanks for the suggestion still!

I still kinda prefer having one function to handle both intentions since

  1. They are quite related. To determine the capability of insertion/ejection, we also need to validate the correctness of the corresponding part of VM spec.
  2. Their implementations are quite similar. They iterate volumes and disks in VM spec.

As for UI, it has its own logic canDoVolumeHotplugAction in UI PR instead of relying on VM action APIs.

if !reflect.DeepEqual(vm, vmCopy) {
if _, err := h.vms.Update(vmCopy); err != nil {
return err
imgSize := max(vmImage.Status.VirtualSize, vmImage.Status.Size)
Copy link
Member

Choose a reason for hiding this comment

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

could we put L329~L366 to a new function and make insertCdRomVolume() shorter

Copy link
Member Author

Choose a reason for hiding this comment

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

I prefer keeping it as is. The most of the space it took is related to initialization of pvc and kubevirt volume, which is the main point of this function.

Copy link
Member

Choose a reason for hiding this comment

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

I have no strong options here, as long as CI does not complain.

Signed-off-by: Tim Liou <tim.liou@suse.com>
Signed-off-by: Tim Liou <tim.liou@suse.com>
@mergify
Copy link

mergify bot commented Feb 12, 2026

This pull request is now in conflict. Could you fix it @wheatdog? 🙏

w13915984028
w13915984028 previously approved these changes Feb 12, 2026
Copy link
Member

@w13915984028 w13915984028 left a comment

Choose a reason for hiding this comment

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

LGTM, thanks.

BTW, please squash the commits manually or utlize the gihtub merge & cut off some commit messages to make the final commit message shorter.

@mergify
Copy link

mergify bot commented Feb 13, 2026

This pull request is now in conflict. Could you fix it @wheatdog? 🙏

ibrokethecloud
ibrokethecloud previously approved these changes Feb 13, 2026
@wheatdog wheatdog dismissed stale reviews from ibrokethecloud and w13915984028 via 79f317c February 13, 2026 07:23
Copy link
Member

@w13915984028 w13915984028 left a comment

Choose a reason for hiding this comment

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

LGTM, thanks.

@wheatdog wheatdog merged commit ef1bcad into harvester:master Feb 13, 2026
12 checks passed
@wheatdog wheatdog deleted the 9261-hp-cdrom branch February 13, 2026 08:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

Comments