Subject: virtinst: implement NVMe disk target generation From: Pavel Hrdina phrdina@redhat.com Fri Jan 9 08:51:29 2026 +0100 Date: Sun Jan 11 18:36:09 2026 +0100: Git: 97505a9feab1ecfd23d88dad0f90444108496f83 In libvirt NVMe disk targets are based on what linux uses for device names. Signed-off-by: Pavel Hrdina --- a/tests/test_disk.py +++ b/tests/test_disk.py @@ -40,13 +40,30 @@ def test_disk_numtotarget(): assert DeviceDisk.target_to_num("xvdaaa") == 26 * 26 * 1 + 26 * 1 + 0 conn = utils.URIs.open_testdefault_cached() + guest = virtinst.Guest(conn) disk = virtinst.DeviceDisk(conn) disk.bus = "ide" - assert disk.generate_target([]) == "hda" - assert disk.generate_target(["hda"]) == "hdb" - assert disk.generate_target(["hdb", "sda"]) == "hdc" - assert disk.generate_target(["hda", "hdd"]) == "hdb" + assert disk.generate_target([], guest) == "hda" + assert disk.generate_target(["hda"], guest) == "hdb" + assert disk.generate_target(["hdb", "sda"], guest) == "hdc" + assert disk.generate_target(["hda", "hdd"], guest) == "hdb" + + disk.bus = "nvme" + disk.serial = "0" + + assert disk.generate_target([], guest) == "nvme0n1" + + controller = virtinst.DeviceController(conn) + controller.type = "nvme" + controller.serial = "0" + controller.index = 0 + guest.add_device(controller) + + assert disk.generate_target([], guest) == "nvme0n1" + + controller.index = 1 + assert disk.generate_target(["nvme1n1"], guest) == "nvme1n2" def test_disk_dir_searchable(monkeypatch): --- a/virtManager/addhardware.py +++ b/virtManager/addhardware.py @@ -1453,7 +1453,7 @@ class vmmAddHardware(vmmGObjectUI): used.append(d.target) self._set_disk_controller(disk) - disk.generate_target(used) + disk.generate_target(used, self.vm.xmlobj) return disk def _build_network(self): --- a/virtinst/devices/disk.py +++ b/virtinst/devices/disk.py @@ -914,6 +914,19 @@ class DeviceDisk(Device): if path: self._set_xmlpath(path) + def get_nvme_namespace(self, guest): + """ + Returns the NVMe namespace for disk based on it's bus and serial. + + If no NVMe controller is defined return 0 as that is default used by + libvirt and virt-manager. + """ + for c in guest.devices.controller: + if c.type == "nvme" and c.serial == self.serial: + return c.index + + return 0 + def get_target_prefix(self): """ Returns the suggested disk target prefix (hd, xvd, sd ...) for the @@ -930,6 +943,7 @@ class DeviceDisk(Device): "fd": 2, "hd": 4, "sd": 1024, + "nvme": 1024, } return prefix, nummap[prefix] @@ -941,10 +955,12 @@ class DeviceDisk(Device): return _return("fd") elif self.bus == "ide": return _return("hd") + elif self.bus == "nvme": + return _return("nvme") # sata, scsi, usb, sd return _return("sd") - def generate_target(self, skip_targets): + def generate_target(self, skip_targets, guest): """ Generate target device ('hda', 'sdb', etc..) for disk, excluding any targets in 'skip_targets'. @@ -957,11 +973,17 @@ class DeviceDisk(Device): skip_targets = [t for t in skip_targets if t and t.startswith(prefix)] skip_targets.sort() + if self.bus == "nvme": + nvmen = self.get_nvme_namespace(guest) + def get_target(): first_found = None for i in range(maxnode): - gen_t = prefix + self.num_to_target(i + 1) + if self.bus == "nvme": + gen_t = f"{prefix}{nvmen}n{i + 1}" + else: + gen_t = prefix + self.num_to_target(i + 1) if gen_t in skip_targets: skip_targets.remove(gen_t) continue @@ -1008,7 +1030,7 @@ class DeviceDisk(Device): used.remove(self.target) self.target = None - self.generate_target(used) + self.generate_target(used, guest) ######################### # set_defaults handling # @@ -1078,4 +1100,4 @@ class DeviceDisk(Device): if not self.target: used_targets = [d.target for d in guest.devices.disk if d.target] - self.generate_target(used_targets) + self.generate_target(used_targets, guest) --- a/virtinst/guest.py +++ b/virtinst/guest.py @@ -877,7 +877,7 @@ class Guest(XMLBuilder): if dev.DEVICE_TYPE == "disk" and dev.bus == "ide": dev.bus = "sata" used_targets = [d.target for d in self.devices.disk if d.target] - dev.generate_target(used_targets) + dev.generate_target(used_targets, self) dev.address.clear() if dev.address.type == "pci":