Introduction:

CloudStack currently supports at most one ISO per VM. This forces users into a manual "ISO swap" workflow for common tasks such as installing Windows on KVM — which requires both a Windows installer ISO and a VirtIO drivers ISO simultaneously — and pushes template creation to external tools like Proxmox or virt-manager. This feature adds native support for attaching multiple ISOs per VM, each surfaced as a discrete CD-ROM drive inside the guest. The Windows + VirtIO use case is a natural consequence of the generic multi-ISO capability.

Note: KVM only for 4.23.

Scope:

API Changes:

attachIso

No new parameters. Behavior changes:

detachIso

New optional parameter:

Rules:

listVirtualMachines

Response gains a new isos[] array. Each entry contains:

The legacy isoid / isoname / isodisplaytext fields are unchanged and continue to reflect the slot-3 (bootable/primary) ISO. Existing API consumers require no modification.

UserVmResponse now also exposes cdrommaxcount (since 4.23.0) — the server-computed effective cap for the VM. The UI reads this field directly from the response instead of making a separate listConfigurations call.

Configuration / Cap Derivation:

ParameterScopeTypeDefaultDescription
vm.cdrom.max.countClusterInteger1Maximum number of CD-ROM drives (ISOs) that may be attached to a single VM. Enforced at attach and deploy time. Per-cluster overrides are supported.

The effective max for a given VM is: min(vm.cdrom.max.count.valueIn(clusterId), host-advertised hypervisor cap). The hardcoded "2 for KVM, 1 for others" logic has been removed from the management server; the hypervisor cap is now discovered at runtime (see Hypervisor Cap Discovery below).

CloudMonkey (cmk) Examples:

1. attachIso — attach two ISOs back-to-back to a running VM

cmk attach iso id=<windows-iso-uuid> virtualmachineid=<vm-uuid>
cmk attach iso id=<virtio-iso-uuid>  virtualmachineid=<vm-uuid>

2. detachIso — single ISO attached (id omitted)

cmk detach iso virtualmachineid=<vm-uuid>

3. detachIso — multiple ISOs attached (id required)

cmk detach iso id=<virtio-iso-uuid> virtualmachineid=<vm-uuid>

4. listVirtualMachines — isos[] in response

{
  "id": "a1b2c3d4-...",
  "name": "my-vm",
  "isoid": "<windows-iso-uuid>",
  "isoname": "Windows Server 2022",
  "isos": [
    {
      "id": "<windows-iso-uuid>",
      "name": "Windows Server 2022",
      "displaytext": "Windows Server 2022",
      "deviceseq": 3
    },
    {
      "id": "<virtio-iso-uuid>",
      "name": "virtio-win-0.9.x",
      "displaytext": "VirtIO Drivers",
      "deviceseq": 4
    }
  ],
  ...
}

Behavior Notes:

Hypervisor Cap Discovery:

Each hypervisor agent advertises its own CD-ROM cap as a host detail named host.cdrom.max.count (Host.HOST_CDROM_MAX_COUNT) on StartupRoutingCommand. The management server reads this via HostDetailsDao and falls back to TemplateManager.DEFAULT_CDROM_MAX_PER_VM (= 1) if the detail is absent (older agents, never-deployed VMs).

Misconfiguration Handling:

If vm.cdrom.max.count is set above the host-advertised cap, attach and deploy operations now log an error and throw InvalidParameterValueException with the recommended maximum in the message. The old behavior of silently clamping to the hypervisor cap has been removed.

The listVirtualMachines endpoint silently clamps the displayed cdrommaxcount value for robustness; the loud failure is reserved for action paths (attach, deploy).

Primary Slot Preservation:

The first attached ISO always occupies user_vm.iso_id (slot 3, hdc). This field is never reassigned by a subsequent attach or detach of a non-primary ISO. Detaching the primary ISO when additional ISOs remain attached is rejected; the primary slot must be the last one cleared.

Stop/Start Persistence:

All attached ISOs and their slot assignments are re-applied on VM start using data from vm_iso_map. No guest-side state is required.

Validation:

ISO Deletion Gating:

templateIsDeleteable now also consults the vm_iso_map table (via VmIsoMapDao.listByIsoId), filtering out VMs in Error or Expunging states. Previously only user_vm.iso_id was checked, which meant an ISO attached to a non-primary slot (i.e. in vm_iso_map only) could be wrongly considered deletable even while still in use.

Constants:

ConstantLocationValue / Role
Host.HOST_CDROM_MAX_COUNTManagement serverKey for the host detail ("host.cdrom.max.count") that agents write on startup.
LibvirtVMDef.MAX_CDROMS_PER_VMKVM agentValue advertised by the KVM agent (= 2). Co-located with the getDevLabel slot-allocation logic.
TemplateManager.CDROM_PRIMARY_DEVICE_SEQManagement serverDevice sequence index of the primary (bootable) CD-ROM slot (= 3, i.e. hdc).
TemplateManager.DEFAULT_CDROM_MAX_PER_VMManagement serverFallback cap (= 1) used when the host has not advertised host.cdrom.max.count (older agents, never-deployed VMs).

Database Changes:

A new mapping table tracks secondary ISO attachments:

CREATE TABLE vm_iso_map (
  id               bigint unsigned NOT NULL AUTO_INCREMENT,
  vm_id            bigint unsigned NOT NULL COMMENT 'virtual machine id',
  iso_id           bigint unsigned NOT NULL COMMENT 'iso (volume) id',
  device_seq       int unsigned    NOT NULL COMMENT 'cdrom slot index (3=hdc, 4=hdd, ...)',
  PRIMARY KEY (id),
  CONSTRAINT fk_vm_iso_map__vm_id
    FOREIGN KEY (vm_id)  REFERENCES vm_instance(id) ON DELETE CASCADE,
  CONSTRAINT fk_vm_iso_map__iso_id
    FOREIGN KEY (iso_id) REFERENCES volumes(id)      ON DELETE CASCADE,
  UNIQUE KEY uk_vm_iso_map__vm_device (vm_id, device_seq),
  INDEX i_vm_iso_map__vm_id (vm_id),
  INDEX i_vm_iso_map__iso_id (iso_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

UI Changes: