On 11/06/2017 07:52 AM, Lin Ma wrote: > When we change disk bus to 'scsi' or add scsi disks, virt-manager relies > on libvirt's rules to create addresses. Sometimes it works, sometimes not, > Espectially in multiple scsi controllers case, we can not exactly control > which virtual disks attach to which scsi controller, sometimes libvirt > creates unnecessary scsi controllers for us, attach virtual disks to the > scsi controllers which we don't want. It has confused users for a while. > > This patch added two new option to solve the above issue: > 1. add virtio-scsi bus option. users can decide which kind of scsi bus > they want to use, instead of guessing through libvirt. > 2. add controller index option. when users configure disk bus, it allows > users to exactly choice which existed scsi controller they want to attach > or create a new one. > > The patch changed some mechanism about adding/configuring scsi bus, and > generating device addrStr instead of libvirt did, it avoids some annoying > issues. > There's a lot going on here, this needs to be split into multiple patches. At the very least: addhardware controller change, details controller change, details virtio-scsi change, but probably some smaller patches too Can you describe more what use case you are trying to facilitate better here with the controller indexing changes? Better handling 8+ scsi devices? Or just a simple 'I want to use virtio-scsi' use case. Before adding more UI complexity I want to be sure this isn't something we can mostly fix by having virt-manager be smarter by default One thing this makes me think: not sure how much sense it makes to allow changing a disk's bus value for an existing VM (it makes sense in the 'customize' wizard before install time). I'm struggling to think of a usecase outside of 'virt testing' or 'trying to just get this vm to boot' Some comments inline > Signed-off-by: Lin Ma <lma@xxxxxxxx> > --- > ui/addhardware.ui | 40 +++++++++++++++ > ui/details.ui | 40 +++++++++++++++ > virtManager/addhardware.py | 125 +++++++++++++++++++++++++++++++++++---------- > virtManager/details.py | 80 ++++++++++++++++++++++++++++- > virtManager/domain.py | 7 ++- > virtinst/device.py | 15 +++++- > virtinst/devicedisk.py | 2 + > 7 files changed, 278 insertions(+), 31 deletions(-) > > diff --git a/ui/addhardware.ui b/ui/addhardware.ui The UI works okay but I think I'd rather have the controller field pop up _below_ the bus field. > diff --git a/virtManager/addhardware.py b/virtManager/addhardware.py > index d661602d..9205725f 100644 > --- a/virtManager/addhardware.py > +++ b/virtManager/addhardware.py > @@ -97,6 +97,7 @@ class vmmAddHardware(vmmGObjectUI): > "on_create_finish_clicked": self._finish, > "on_hw_list_changed": self._hw_selected, > > + "on_storage_bustype_changed": self._change_storage_bustype, > "on_storage_devtype_changed": self._change_storage_devtype, > > "on_mac_address_clicked": self._change_macaddr_use, > @@ -196,6 +197,10 @@ class vmmAddHardware(vmmGObjectUI): > netmodel_list = self.widget("net-model") > self.build_network_model_combo(self.vm, netmodel_list) > > + # Scsi index combo > + self.build_controller_index_combo(self.vm, > + self.widget("storage-scsi-index")) > + > # Disk bus type > self.build_disk_bus_combo(self.vm, > self.widget("storage-bustype")) > @@ -678,6 +683,41 @@ class vmmAddHardware(vmmGObjectUI): > model.append([None, _("Hypervisor default")]) > combo.set_active(0) > > + @staticmethod > + def build_controller_index_combo(vm, combo): > + ignore = vm > + model = Gtk.ListStore(str, str) > + combo.set_model(model) > + uiutil.init_combo_text_column(combo, 1) > + model.set_sort_column_id(1, Gtk.SortType.ASCENDING) > + combo.set_active(-1) > + > + @staticmethod > + def has_available_scsi_unit(vm, controller_index): > + used_unit = 0 > + for disk in vm.get_disk_devices(inactive=True): > + if disk.bus == "scsi": > + if disk.address.controller == controller_index: > + used_unit += 1 > + if used_unit < 7: > + return True > + else: > + return False > + > + @staticmethod > + def find_available_scsi_unit(vm, controller_index): > + max_unit = 7 > + avail_unit = 0 > + used_list = [] > + for disk in vm.get_disk_devices(inactive=True): > + if disk.bus == "scsi": > + if str(disk.address.controller) == controller_index: > + used_list.append(disk.address.unit) > + while avail_unit < max_unit: > + if avail_unit not in used_list: > + return avail_unit > + avail_unit += 1 > + These two could be static methods in devicecontroller.py. You'd need to pass in vm.get_xmlobj() output rather than a disk list though. Then it would be easier to unit test > @staticmethod > def build_disk_bus_combo(vm, combo): > ignore = vm > @@ -718,6 +758,9 @@ class vmmAddHardware(vmmGObjectUI): > if vm.conn.is_xen() or vm.conn.is_test(): > disk_bus_types.append("xen") > > + if vm.get_hv_type() in ["qemu", "kvm", "test"]: > + disk_bus_types.append("virtio-scsi") > + > rows = [] > for bus in disk_bus_types: > rows.append([bus, virtinst.VirtualDisk.pretty_disk_bus(bus)]) > @@ -725,10 +768,11 @@ class vmmAddHardware(vmmGObjectUI): > model.clear() > > bus_map = { > - "disk": ["ide", "sata", "scsi", "sd", "usb", "virtio", "xen"], > + "disk": ["ide", "sata", "scsi", "sd", "usb", "virtio", "xen", > + "virtio-scsi"], > "floppy": ["fdc"], > - "cdrom": ["ide", "sata", "scsi"], > - "lun": ["scsi"], > + "cdrom": ["ide", "sata", "scsi", "virtio-scsi"], > + "lun": ["scsi", "virtio-scsi"], > } > for row in rows: > if row[0] in bus_map[devtype]: > @@ -1132,6 +1176,30 @@ class vmmAddHardware(vmmGObjectUI): > # Device page listeners # > ######################### > > + def _change_storage_bustype(self, ignore): > + bustype = uiutil.get_list_selection( > + self.widget("storage-bustype")) > + > + controller_model = bustype > + if controller_model in ["scsi", "virtio-scsi"]: > + model = self.widget("storage-scsi-index").get_model() > + model.clear() > + if controller_model == "scsi": > + controller_model = None > + for controller in self.vm.get_controller_devices(): > + if (controller.type == "scsi" and > + controller.model == controller_model): > + if not vmmAddHardware.has_available_scsi_unit( > + self.vm, controller.index): > + continue > + model.append([str(controller.index), > + str(controller.index)]) > + model.append(["new", "New"]) > + self.widget("storage-scsi-index").set_active(0) > + self.widget("storage-scsi-idx").set_visible(True) > + else: > + self.widget("storage-scsi-idx").set_visible(False) > + > def _change_storage_devtype(self, ignore): > devtype = uiutil.get_list_selection( > self.widget("storage-devtype")) > @@ -1429,10 +1497,9 @@ class vmmAddHardware(vmmGObjectUI): > self._dev.validate() > return ret > > - def _set_disk_controller(self, disk, controller_model, used_disks): > - # Add a SCSI controller with model virtio-scsi if needed > + def _set_disk_controller(self, disk, controller_model): > disk.vmm_controller = None > - if controller_model != "virtio-scsi": > + if disk.bus != "scsi": > return None > > # Get SCSI controllers > @@ -1440,7 +1507,7 @@ class vmmAddHardware(vmmGObjectUI): > ctrls_scsi = [x for x in controllers if > (x.type == VirtualController.TYPE_SCSI)] > > - # Create possible new controller > + # Create new controller > controller = VirtualController(self.conn.get_backend()) > controller.type = "scsi" > controller.model = controller_model > @@ -1450,23 +1517,7 @@ class vmmAddHardware(vmmGObjectUI): > if ctrls_scsi: > controller.index = max([x.index for x in ctrls_scsi]) + 1 > > - # Take only virtio-scsi ones > - ctrls_scsi = [x for x in ctrls_scsi > - if x.model == controller_model] > - > - # Save occupied places per controller > - occupied = collections.defaultdict(int) > - for d in used_disks: > - if (d.get_target_prefix() == disk.get_target_prefix() and > - d.bus == "scsi"): > - num = virtinst.VirtualDisk.target_to_num(d.target) > - occupied[num / 7] += 1 > - for c in ctrls_scsi: > - if occupied[c.index] < 7: > - controller = c > - break > - else: > - disk.vmm_controller = controller > + disk.vmm_controller = controller > > return controller.index > > @@ -1479,9 +1530,12 @@ class vmmAddHardware(vmmGObjectUI): > self.widget("storage-cache")) > > controller_model = None > - if (bus == "scsi" and > + controller_index = None > + scsi_unit = None > + if (bus == "virtio-scsi" and > self.vm.get_hv_type() in ["qemu", "kvm", "test"] and > not self.vm.xmlobj.os.is_pseries()): > + bus = "scsi" > controller_model = "virtio-scsi" > > collidelist = [d.path for d in self.vm.get_disk_devices()] > @@ -1507,10 +1561,25 @@ class vmmAddHardware(vmmGObjectUI): > if d.target not in used: > used.append(d.target) > > - prefer_ctrl = self._set_disk_controller( > - disk, controller_model, self.vm.get_disk_devices(inactive=True)) > + new_ctrl_idx = self._set_disk_controller(disk, controller_model) > + disk.generate_target(used, new_ctrl_idx) > + > + if bus == "scsi": > + controller_index = uiutil.get_list_selection( > + self.widget("storage-scsi-index")) > + if controller_index != "new": > + scsi_unit = vmmAddHardware.find_available_scsi_unit( > + self.vm, controller_index) > + disk.address.type = "drive" > + disk.address.controller = controller_index > + disk.address.bus = "0" > + disk.address.target = "0" > + disk.address.unit = scsi_unit > + disk.vmm_controller = None > + else: > + disk.address.type = "drive" > + disk.address.controller = new_ctrl_idx > > - disk.generate_target(used, prefer_ctrl) > except Exception as e: > return self.err.val_err(_("Storage parameter error."), e) > > diff --git a/virtManager/details.py b/virtManager/details.py > index a9e0cb79..3c116787 100644 > --- a/virtManager/details.py > +++ b/virtManager/details.py > @@ -30,6 +30,7 @@ import libvirt > import virtinst > from virtinst import util > from virtinst import VirtualRNGDevice > +from virtinst import VirtualController > > from . import vmmenu > from . import uiutil > @@ -538,7 +539,8 @@ class vmmDetails(vmmGObjectUI): > "on_disk_removable_changed": lambda *x: self.enable_apply(x, EDIT_DISK_REMOVABLE), > "on_disk_cache_combo_changed": lambda *x: self.enable_apply(x, EDIT_DISK_CACHE), > "on_disk_io_combo_changed": lambda *x: self.enable_apply(x, EDIT_DISK_IO), > - "on_disk_bus_combo_changed": lambda *x: self.enable_apply(x, EDIT_DISK_BUS), > + "on_controller_index_combo_changed": lambda *x: self.enable_apply(x, EDIT_DISK_BUS), > + "on_disk_bus_combo_changed": self.disk_bus_combo_changed, > "on_disk_format_changed": self.disk_format_changed, > "on_disk_serial_changed": lambda *x: self.enable_apply(x, EDIT_DISK_SERIAL), > "on_disk_sgio_entry_changed": lambda *x: self.enable_apply(x, EDIT_DISK_SGIO), > @@ -1006,6 +1008,10 @@ class vmmDetails(vmmGObjectUI): > disk_bus = self.widget("disk-bus") > vmmAddHardware.build_disk_bus_combo(self.vm, disk_bus) > > + # Controller index combo > + controller_index = self.widget("controller-index") > + vmmAddHardware.build_controller_index_combo(self.vm, controller_index) > + > # Network model > net_model = self.widget("network-model") > vmmAddHardware.build_network_model_combo(self.vm, net_model) > @@ -1822,6 +1828,41 @@ class vmmDetails(vmmGObjectUI): > boot_list.get_selection().emit("changed") > self.enable_apply(EDIT_BOOTORDER) > > + def disk_bus_combo_changed(self, ignore): > + controller_model = uiutil.get_list_selection(self.widget("disk-bus")) > + > + if controller_model in ["scsi", "virtio-scsi"]: > + disk = self.get_hw_selection(HW_LIST_COL_DEVICE) > + model = self.widget("controller-index").get_model() > + model.clear() > + current_controller = -1 > + i = -1 > + if controller_model == "scsi": > + controller_model = None > + for controller in self.vm.get_controller_devices(): > + if (controller.type == "scsi" and > + controller.model == controller_model): > + i += 1 > + if (disk.bus == "scsi" and > + disk.address.controller == controller.index): > + current_controller = i > + if not vmmAddHardware.has_available_scsi_unit( > + self.vm, controller.index): > + if current_controller != i: > + continue > + model.append([str(controller.index), > + str(controller.index)]) > + model.append(["new", "New"]) > + if current_controller >= 0: > + self.widget("controller-index").set_active(current_controller) > + else: > + self.widget("controller-index").set_active(0) > + self.widget("controller-idx").set_visible(True) > + else: > + self.widget("controller-idx").set_visible(False) > + > + self.enable_apply(EDIT_DISK_BUS) > + > def disk_format_changed(self, ignore): > self.widget("disk-format-warn").show() > self.enable_apply(EDIT_DISK_FORMAT) > @@ -2127,6 +2168,35 @@ class vmmDetails(vmmGObjectUI): > if self.edited(EDIT_DISK_BUS): > bus = uiutil.get_list_selection(self.widget("disk-bus")) > addr = None > + controller_model = None > + if bus in ["scsi", "virtio-scsi"]: > + controller_index = uiutil.get_list_selection( > + self.widget("controller-index")) > + if bus == "virtio-scsi": > + bus = "scsi" > + controller_model = "virtio-scsi" > + if controller_index != "new": > + scsi_bus = "0" > + scsi_target = "0" > + scsi_unit = vmmAddHardware.find_available_scsi_unit( > + self.vm, controller_index) > + addr = (controller_index + "/" + scsi_bus + "/" + > + scsi_target + "/" + str(scsi_unit)) > + else: > + controllers = self.vm.get_controller_devices() > + ctrls_scsi = [x for x in controllers if (x.type == "scsi")] > + > + new_ctrl = VirtualController(self.conn.get_backend()) > + new_ctrl.type = "scsi" > + new_ctrl.model = controller_model > + new_ctrl.index = 0 > + > + if ctrls_scsi: > + new_ctrl.index = max([x.index for x in ctrls_scsi]) + 1 > + > + self.vm.add_device(new_ctrl) > + > + addr = str(new_ctrl.index) + "/" > This is too much open coded logic, especially since it seems to match much of what we are doing in addhardware already... we need to find a way to share this stuff more > kwargs["bus"] = bus > kwargs["addrstr"] = addr > @@ -2681,6 +2751,14 @@ class vmmDetails(vmmGObjectUI): > vmmAddHardware.populate_disk_bus_combo(self.vm, devtype, > self.widget("disk-bus").get_model()) > uiutil.set_list_selection(self.widget("disk-bus"), bus) > + if bus == virtinst.VirtualController.TYPE_SCSI: > + for controller in self.vm.get_controller_devices(): > + if (controller.type == "scsi" and > + controller.index == disk.address.controller): > + if controller.model == "virtio-scsi": > + uiutil.set_list_selection(self.widget("disk-bus"), > + "virtio-scsi") > + > self.widget("disk-serial").set_text(serial or "") > > button = self.widget("disk-cdrom-connect") > diff --git a/virtManager/domain.py b/virtManager/domain.py > index a1f59e38..fb658d59 100644 > --- a/virtManager/domain.py > +++ b/virtManager/domain.py > @@ -791,7 +791,12 @@ class vmmDomain(vmmLibvirtObject): > editdev.bus = bus > > if oldbus == bus: > - return > + if oldbus == "scsi": > + controller_index = addrstr.split("/")[0] > + if editdev.address.controller == controller_index: > + return > + else: > + return > > editdev.address.clear() > editdev.address.set_addrstr(addrstr) > diff --git a/virtinst/device.py b/virtinst/device.py > index 4f9dcb2c..a8436177 100644 > --- a/virtinst/device.py > +++ b/virtinst/device.py > @@ -36,7 +36,7 @@ class VirtualDeviceAddress(XMLBuilder): > """ > Examples: > <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/> > - <address type='drive' controller='0' bus='0' unit='0'/> > + <address type='drive' controller='0' bus='0' target='0' unit='0'/> > <address type='ccid' controller='0' slot='0'/> > <address type='virtio-serial' controller='1' bus='0' port='4'/> > """ > @@ -68,6 +68,19 @@ class VirtualDeviceAddress(XMLBuilder): > self.domain, self.bus = addrstr.split(":", 1) > elif addrstr == "spapr-vio": > self.type = self.ADDRESS_TYPE_SPAPR_VIO > + elif addrstr.count("/") in [1, 2, 3]: > + self.type = self.ADDRESS_TYPE_DRIVE > + self.controller = addrstr.split("/")[0] > + if addrstr.count("/") == 1: > + if addrstr.split("/")[1] != "": > + self.bus = addrstr.split("/")[1] > + elif addrstr.count("/") == 2: > + self.bus = addrstr.split("/")[1] > + self.target = addrstr.split("/")[2] > + elif addrstr.count("/") == 3: > + self.bus = addrstr.split("/")[1] > + self.target = addrstr.split("/")[2] > + self.unit = addrstr.split("/")[3] > else: > raise ValueError(_("Could not determine or unsupported " > "format of '%s'") % addrstr) It's hard for me to follow in the code exactly where this address parsing comes into play and the domain.py It should probably be a separate though with unit testing - Cole _______________________________________________ virt-tools-list mailing list virt-tools-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/virt-tools-list