diff --git a/src/virtManager/choosecd.py b/src/virtManager/choosecd.py index d084b6909..856de8182 100644 --- a/src/virtManager/choosecd.py +++ b/src/virtManager/choosecd.py @@ -29,7 +29,7 @@ class vmmChooseCD(gobject.GObject): gobject.TYPE_NONE, (str, str, str)), # type, source, target } - def __init__(self, config, target, connection): + def __init__(self, config, dev_id_info, connection): self.__gobject_init__() self.window = gtk.glade.XML(config.get_glade_dir() + "/vmm-choose-cd.glade", "vmm-choose-cd", domain="virt-manager") self.err = vmmErrorDialog(self.window.get_widget("vmm-choose-cd"), @@ -38,7 +38,7 @@ class vmmChooseCD(gobject.GObject): _("An unexpected error occurred")) self.config = config self.window.get_widget("vmm-choose-cd").hide() - self.target = target + self.dev_id_info = dev_id_info self.conn = connection self.window.signal_autoconnect({ @@ -65,9 +65,6 @@ class vmmChooseCD(gobject.GObject): self.reset_state() - def set_target(self, target): - self.target=target - def close(self,ignore1=None,ignore2=None): self.window.get_widget("vmm-choose-cd").hide() return 1 @@ -111,7 +108,7 @@ class vmmChooseCD(gobject.GObject): conn=self.conn.vmm) except Exception, e: return self.err.val_err(_("Invalid Media Path"), str(e)) - self.emit("cdrom-chosen", disk.type, disk.path, self.target) + self.emit("cdrom-chosen", disk.type, disk.path, self.dev_id_info) self.cancel() def media_toggled(self, ignore1=None, ignore2=None): diff --git a/src/virtManager/details.py b/src/virtManager/details.py index cea4a2bc8..9c37cff88 100644 --- a/src/virtManager/details.py +++ b/src/virtManager/details.py @@ -287,12 +287,12 @@ class vmmDetails(gobject.GObject): "on_details_help_activate": self.show_help, "on_config_cdrom_connect_clicked": self.toggle_cdrom, - "on_config_disk_remove_clicked": self.remove_disk, - "on_config_network_remove_clicked": self.remove_network, - "on_config_input_remove_clicked": self.remove_input, - "on_config_graphics_remove_clicked": self.remove_graphics, - "on_config_sound_remove_clicked": self.remove_sound, - "on_config_char_remove_clicked": self.remove_char, + "on_config_disk_remove_clicked": self.remove_xml_dev, + "on_config_network_remove_clicked": self.remove_xml_dev, + "on_config_input_remove_clicked": self.remove_xml_dev, + "on_config_graphics_remove_clicked": self.remove_xml_dev, + "on_config_sound_remove_clicked": self.remove_xml_dev, + "on_config_char_remove_clicked": self.remove_xml_dev, "on_add_hardware_button_clicked": self.add_hardware, "on_details_menu_view_fullscreen_activate": self.toggle_fullscreen, @@ -1454,78 +1454,12 @@ class vmmDetails(gobject.GObject): "".join(traceback.format_exc())) return - - def remove_disk(self, src): - diskinfo = self.get_hw_selection(HW_LIST_COL_DEVICE) - if not diskinfo: + def remove_xml_dev(self, src): + info = self.get_hw_selection(HW_LIST_COL_DEVICE) + if not info: return - self.remove_device(self.vm.get_disk_xml(diskinfo[2])) - self.refresh_resources() - - def remove_network(self, src): - netinfo = self.get_hw_selection(HW_LIST_COL_DEVICE) - if not netinfo: - return - - vnic = None - try: - if netinfo[5] == "bridge": - vnic = virtinst.VirtualNetworkInterface(type=netinfo[5], - bridge=netinfo[3], - macaddr=netinfo[2]) - elif netinfo[5] == "network": - vnic = virtinst.VirtualNetworkInterface(type=netinfo[5], - network=netinfo[3], - macaddr=netinfo[2]) - else: - vnic = virtinst.VirtualNetworkInterface(type=netinfo[5], - macaddr=netinfo[2]) - except ValueError, e: - self.err.show_err(_("Error Removing Network: %s" % str(e)), - "".join(traceback.format_exc())) - return False - - xml = vnic.get_xml_config() - self.remove_device(xml) - self.refresh_resources() - - def remove_input(self, src): - inputinfo = self.get_hw_selection(HW_LIST_COL_DEVICE) - if not inputinfo: - return - - xml = "" % (inputinfo[4], inputinfo[3]) - self.remove_device(xml) - self.refresh_resources() - - def remove_graphics(self, src): - gfxinfo = self.get_hw_selection(HW_LIST_COL_DEVICE) - if not gfxinfo: - return - - xml = "" % gfxinfo[2] - self.remove_device(xml) - self.refresh_resources() - - def remove_sound(self, src): - soundinfo = self.get_hw_selection(HW_LIST_COL_DEVICE) - if not soundinfo: - return - - xml = "" % soundinfo[3] - self.remove_device(xml) - self.refresh_resources() - - def remove_char(self, src): - charinfo = self.get_hw_selection(HW_LIST_COL_DEVICE) - if not charinfo: - return - - xml = "<%s>\n" % charinfo[0] + \ - " \n" % charinfo[3] + \ - "" % charinfo[0] - self.remove_device(xml) + self.remove_device(info[0], info[1]) self.refresh_resources() def prepare_hw_list(self): @@ -1604,7 +1538,7 @@ class vmmDetails(gobject.GObject): currentNICs[netinfo[2]] = 1 for row in hw_list_model: if row[HW_LIST_COL_TYPE] == HW_LIST_TYPE_NIC and \ - row[HW_LIST_COL_DEVICE][3] == netinfo[3]: + row[HW_LIST_COL_DEVICE][2] == netinfo[2]: row[HW_LIST_COL_DEVICE] = netinfo missing = False @@ -1750,16 +1684,16 @@ class vmmDetails(gobject.GObject): found_dev = {} for row in hw_list_model: if row[4] == HW_LIST_TYPE_DISK: - disk = row[5] - if disk[2] == virtinst.VirtualDisk.DEVICE_DISK and not \ + diskinfo = row[5] + if diskinfo[4] == virtinst.VirtualDisk.DEVICE_DISK and not \ found_dev.get(virtinst.VirtualDisk.DEVICE_DISK, False): boot_model.append(["Hard Disk", gtk.STOCK_HARDDISK, "hd"]) found_dev[virtinst.VirtualDisk.DEVICE_DISK] = True - elif disk[2] == virtinst.VirtualDisk.DEVICE_CDROM and not \ + elif diskinfo[4] == virtinst.VirtualDisk.DEVICE_CDROM and not \ found_dev.get(virtinst.VirtualDisk.DEVICE_CDROM, False): boot_model.append(["CDROM", gtk.STOCK_CDROM, "cdrom"]) found_dev[virtinst.VirtualDisk.DEVICE_CDROM] = True - elif disk[2] == virtinst.VirtualDisk.DEVICE_FLOPPY and not \ + elif diskinfo[4] == virtinst.VirtualDisk.DEVICE_FLOPPY and not \ found_dev.get(virtinst.VirtualDisk.DEVICE_FLOPPY, False): boot_model.append(["Floppy", gtk.STOCK_FLOPPY, "fd"]) found_dev[virtinst.VirtualDisk.DEVICE_FLOPPY] = True @@ -1784,10 +1718,14 @@ class vmmDetails(gobject.GObject): self.refresh_resources() def toggle_cdrom(self, src): + info = self.get_hw_selection(HW_LIST_COL_DEVICE) + if not info: + return + if src.get_label() == gtk.STOCK_DISCONNECT: #disconnect the cdrom try: - self.vm.disconnect_cdrom_device(self.window.get_widget("disk-target-device").get_text()) + self.vm.disconnect_cdrom_device(info[1]) except Exception, e: self.err.show_err(_("Error Removing CDROM: %s" % str(e)), "".join(traceback.format_exc())) @@ -1799,22 +1737,25 @@ class vmmDetails(gobject.GObject): self.choose_cd = vmmChooseCD(self.config, self.window.get_widget("disk-target-device").get_text(), self.vm.get_connection()) self.choose_cd.connect("cdrom-chosen", self.connect_cdrom) else: - self.choose_cd.set_target(self.window.get_widget("disk-target-device").get_text()) + self.choose_cd.dev_id_info = info[1] self.choose_cd.show() - def connect_cdrom(self, src, typ, source, target): + def connect_cdrom(self, src, typ, source, dev_id_info): try: - self.vm.connect_cdrom_device(typ, source, target) + self.vm.connect_cdrom_device(typ, source, dev_id_info) except Exception, e: self.err.show_err(_("Error Connecting CDROM: %s" % str(e)), "".join(traceback.format_exc())) - def remove_device(self, xml): - logging.debug("Removing device:\n%s" % xml) + def remove_device(self, dev_type, dev_id_info): + logging.debug("Removing device: %s %s" % (dev_type, dev_id_info)) detach_err = False + devxml = self.vm.get_device_xml(dev_type, dev_id_info) try: - self.vm.detach_device(xml) + if self.vm.is_active(): + self.vm.detach_device(devxml) + return except Exception, e: logging.debug("Device could not be hotUNplugged: %s" % str(e)) detach_err = True @@ -1831,11 +1772,8 @@ class vmmDetails(gobject.GObject): "reboot.")): return - if self.vm.is_active() and not detach_err: - return - try: - self.vm.remove_device(xml) + self.vm.remove_device(dev_type, dev_id_info) except Exception, e: self.err.show_err(_("Error Removing Device: %s" % str(e)), "".join(traceback.format_exc())) diff --git a/src/virtManager/domain.py b/src/virtManager/domain.py index 72ec8eac7..08555e677 100644 --- a/src/virtManager/domain.py +++ b/src/virtManager/domain.py @@ -661,27 +661,6 @@ class vmmDomain(gobject.GObject): return self._parse_device_xml(_parse_disk_devs) - def get_disk_xml(self, target): - """Returns device xml in string form for passed disk target""" - xml = self.get_xml() - doc = None - ctx = None - try: - doc = libxml2.parseDoc(xml) - ctx = doc.xpathNewContext() - disk_fragment = ctx.xpathEval("/domain/devices/disk[target/@dev='%s']" % target) - if len(disk_fragment) == 0: - raise RuntimeError("Attmpted to parse disk device %s, but %s does not exist" % (target,target)) - if len(disk_fragment) > 1: - raise RuntimeError("Found multiple disk devices named %s. This domain's XML is malformed." % target) - result = disk_fragment[0].serialize() - finally: - if ctx != None: - ctx.xpathFreeContext() - if doc != None: - doc.freeDoc() - return result - def get_network_devices(self): def _parse_network_devs(ctx): nics = [] @@ -803,7 +782,7 @@ class vmmDomain(gobject.GObject): # [device type, unique, display string, target_port, # char device type, source_path, is_console_dup_of_serial? - dev = [char_type, (char_type, target_port), + dev = [char_type, target_port, "%s:%s" % (char_type, target_port), target_port, dev_type, source_path, False] @@ -849,100 +828,96 @@ class vmmDomain(gobject.GObject): index = xml.find("") return xml[0:index] + devxml + xml[index:] - def _remove_xml_device(self, xml, devxml): - """Remove device 'devxml' from devices section of 'xml, return - result""" + def get_device_xml(self, dev_type, dev_id_info): + vmxml = self.vm.XMLDesc(0) doc = None - try: - doc = libxml2.parseDoc(xml) - except: - return - ctx = doc.xpathNewContext() - try: - dev_doc = libxml2.parseDoc(devxml) - except: - raise RuntimeError("Device XML would not parse") - dev_ctx = dev_doc.xpathNewContext() - ret = None + ctx = None try: - dev = dev_ctx.xpathEval("//*") - dev_type = dev[0].name - if dev_type=="interface": - address = dev_ctx.xpathEval("/interface/mac/@address") - if len(address) > 0 and address[0].content != None: - logging.debug("The mac address appears to be %s" % address[0].content) - ret = ctx.xpathEval("/domain/devices/interface[mac/@address='%s']" % address[0].content) + doc = libxml2.parseDoc(vmxml) + ctx = doc.xpathNewContext() + nodes = self._get_device_xml_helper(ctx, dev_type, dev_id_info) - elif dev_type=="disk": - path = dev_ctx.xpathEval("/disk/target/@dev") - if len(path) > 0 and path[0].content != None: - logging.debug("Looking for path %s" % path[0].content) - ret = ctx.xpathEval("/domain/devices/disk[target/@dev='%s']" % path[0].content) + if nodes: + return nodes[0].serialize() - elif dev_type=="input": - typ = dev_ctx.xpathEval("/input/@type") - bus = dev_ctx.xpathEval("/input/@bus") - if (len(typ) > 0 and typ[0].content != None and - len(bus) > 0 and bus[0].content != None): - logging.debug("Looking for type %s bus %s" % (typ[0].content, bus[0].content)) - ret = ctx.xpathEval("/domain/devices/input[@type='%s' and @bus='%s']" % (typ[0].content, bus[0].content)) - - elif dev_type=="graphics": - typ = dev_ctx.xpathEval("/graphics/@type") - if len(typ) > 0 and typ[0].content != None: - logging.debug("Looking for type %s" % typ[0].content) - ret = ctx.xpathEval("/domain/devices/graphics[@type='%s']" % typ[0].content) - - elif dev_type == "sound": - model = dev_ctx.xpathEval("/sound/@model") - if len(model) > 0 and model[0].content != None: - logging.debug("Looking for type %s" % model[0].content) - ret = ctx.xpathEval("/domain/devices/sound[@model='%s']" % model[0].content) - - elif dev_type == "parallel" or dev_type == "console" or \ - dev_type == "serial": - port = dev_ctx.xpathEval("/%s/target/@port" % dev_type) - if port and len(port) > 0 and port[0].content != None: - logging.debug("Looking for %s w/ port %s" % (dev_type, - port)) - ret = ctx.xpathEval("/domain/devices/%s[target/@port='%s']" % (dev_type, port[0].content)) - - # If serial and console are both present, console is - # probably (always?) just a dup of the 'primary' serial - # device. Try and find an associated console device with - # the same port and remove that as well, otherwise the - # removal doesn't go through on libvirt <= 0.4.4 - if dev_type == "serial": - cons_ret = ctx.xpathEval("/domain/devices/console[target/@port='%s']" % port[0].content) - if cons_ret and len(cons_ret) > 0: - logging.debug("Also removing console device " - "associated with serial dev.") - cons_ret[0].unlinkNode() - cons_ret[0].freeNode() - else: - logging.debug("No console device found associated " - "with passed serial devices") - - else: - raise RuntimeError, _("Unknown device type '%s'" % dev_type) - - # Take variable 'ret', unlink it, and define the altered xml - if ret and len(ret) > 0: - ret[0].unlinkNode() - ret[0].freeNode() - newxml = doc.serialize() - return newxml - else: - logging.debug("Didn't find the specified device to remove. " - "Passed xml was: %s" % devxml) finally: if ctx != None: ctx.xpathFreeContext() if doc != None: doc.freeDoc() - if dev_doc != None: - dev_doc.freeDoc() + + + def _get_device_xml_helper(self, ctx, dev_type, dev_id_info): + """Does all the work of looking up the device in the VM xml""" + + if dev_type=="interface": + ret = ctx.xpathEval("/domain/devices/interface[mac/@address='%s']" % dev_id_info) + + elif dev_type=="disk": + ret = ctx.xpathEval("/domain/devices/disk[target/@dev='%s']" % \ + dev_id_info) + + elif dev_type=="input": + typ, bus = dev_id_info + ret = ctx.xpathEval("/domain/devices/input[@type='%s' and @bus='%s']" % (typ, bus)) + + elif dev_type=="graphics": + ret = ctx.xpathEval("/domain/devices/graphics[@type='%s']" % \ + dev_id_info) + + elif dev_type == "sound": + ret = ctx.xpathEval("/domain/devices/sound[@model='%s']" % \ + dev_id_info) + + elif dev_type == "parallel" or dev_type == "console" or \ + dev_type == "serial": + ret = ctx.xpathEval("/domain/devices/%s[target/@port='%s']" % (dev_type, dev_id_info)) + + # If serial and console are both present, console is + # probably (always?) just a dup of the 'primary' serial + # device. Try and find an associated console device with + # the same port and remove that as well, otherwise the + # removal doesn't go through on libvirt <= 0.4.4 + if dev_type == "serial": + cons_ret = ctx.xpathEval("/domain/devices/console[target/@port='%s']" % dev_id_info) + if cons_ret and len(cons_ret) > 0: + ret.append(cons_ret[0]) + else: + raise RuntimeError, _("Unknown device type '%s'" % dev_type) + + return ret + + def _remove_xml_device(self, dev_type, dev_id_info): + """Remove device 'devxml' from devices section of 'xml, return + result""" + vmxml = self.vm.XMLDesc(0) + doc = libxml2.parseDoc(vmxml) + ctx = None + + try: + ctx = doc.xpathNewContext() + ret = self._get_device_xml_helper(ctx, dev_type, dev_id_info) + + if ret and len(ret) > 0: + if len(ret) > 1 and ret[0].name == "serial" and \ + ret[1].name == "console": + ret[1].unlinkNode() + ret[1].freeNode() + + ret[0].unlinkNode() + ret[0].freeNode() + newxml = doc.serialize() + return newxml + else: + raise ValueError(_("Didn't find the specified device to " + "remove. Device was: %s %s" % \ + (dev_type, str(dev_id_info)))) + finally: + if ctx != None: + ctx.xpathFreeContext() + if doc != None: + doc.freeDoc() def attach_device(self, xml): """Hotplug device to running guest""" @@ -966,10 +941,8 @@ class vmmDomain(gobject.GObject): # Invalidate cached XML self.xml = None - def remove_device(self, devxml): - xml = self.vm.XMLDesc(0) - - newxml = self._remove_xml_device(xml, devxml) + def remove_device(self, dev_type, dev_id_info): + newxml = self._remove_xml_device(dev_type, dev_id_info) logging.debug("Redefine with " + newxml) self.get_connection().define_domain(newxml) @@ -977,19 +950,19 @@ class vmmDomain(gobject.GObject): # Invalidate cached XML self.xml = None - def _change_cdrom(self, newdev, origdev): + def _change_cdrom(self, newdev, dev_id_info): # If vm is shutoff, remove device, and redefine with media - vmxml = self.vm.XMLDesc(0) if not self.is_active(): - tmpxml = self._remove_xml_device(vmxml, origdev) + tmpxml = self._remove_xml_device("disk", dev_id_info) finalxml = self._add_xml_device(tmpxml, newdev) + logging.debug("change cdrom: redefining xml with:\n%s" % finalxml) self.get_connection().define_domain(finalxml) else: self.attach_device(newdev) - def connect_cdrom_device(self, _type, source, target): - xml = self.get_disk_xml(target) + def connect_cdrom_device(self, _type, source, dev_id_info): + xml = self.get_device_xml("disk", dev_id_info) doc = None ctx = None try: @@ -997,7 +970,6 @@ class vmmDomain(gobject.GObject): ctx = doc.xpathNewContext() disk_fragment = ctx.xpathEval("/disk") driver_fragment = ctx.xpathEval("/disk/driver") - origdisk = disk_fragment[0].serialize() disk_fragment[0].setProp("type", _type) elem = disk_fragment[0].newChild(None, "source", None) if _type == "file": @@ -1009,23 +981,22 @@ class vmmDomain(gobject.GObject): if driver_fragment: driver_fragment[0].setProp("name", "phy") result = disk_fragment[0].serialize() - logging.debug("connect_cdrom_device produced the following XML: %s" % result) + logging.debug("connect_cdrom produced: %s" % result) finally: if ctx != None: ctx.xpathFreeContext() if doc != None: doc.freeDoc() - self._change_cdrom(result, origdisk) + self._change_cdrom(result, dev_id_info) - def disconnect_cdrom_device(self, target): - xml = self.get_disk_xml(target) + def disconnect_cdrom_device(self, dev_id_info): + xml = self.get_device_xml("disk", dev_id_info) doc = None ctx = None try: doc = libxml2.parseDoc(xml) ctx = doc.xpathNewContext() disk_fragment = ctx.xpathEval("/disk") - origdisk = disk_fragment[0].serialize() sourcenode = None for child in disk_fragment[0].children: if child.name == "source": @@ -1036,13 +1007,13 @@ class vmmDomain(gobject.GObject): sourcenode.unlinkNode() sourcenode.freeNode() result = disk_fragment[0].serialize() - logging.debug("disconnect_cdrom_device produced the following XML: %s" % result) + logging.debug("eject_cdrom produced: %s" % result) finally: if ctx != None: ctx.xpathFreeContext() if doc != None: doc.freeDoc() - self._change_cdrom(result, origdisk) + self._change_cdrom(result, dev_id_info) def set_vcpu_count(self, vcpus): vcpus = int(vcpus)