From ff5bbfd309de657cb282e5d4fb9864c24d524b58 Mon Sep 17 00:00:00 2001 From: Cole Robinson Date: Tue, 24 Sep 2013 11:42:18 -0400 Subject: [PATCH] Fix parsing rawhide .treeinfo (bz #989162) Fix spice with TLS (bz #904295) Reduce impact of memory leak (bz #972371) --- 0001-Fix-rawhide-URL-installs.patch | 24 ++ ...console-Fix-spice-with-TLS-bz-904295.patch | 94 ++++++ ...graph-data-func-if-graph-isn-t-visib.patch | 165 +++++++++ ...-can_set_row_none-before-setting-ico.patch | 46 +++ ...ore-VIR_ERR_NO_DOMAIN-when-a-domain-.patch | 147 ++++++++ ...me-row-creation-drop-unneeded-row-ke.patch | 316 ++++++++++++++++++ ...ate-stats-and-state-update-callbacks.patch | 204 +++++++++++ virt-manager.spec | 29 +- 8 files changed, 1024 insertions(+), 1 deletion(-) create mode 100644 0001-Fix-rawhide-URL-installs.patch create mode 100644 0002-console-Fix-spice-with-TLS-bz-904295.patch create mode 100644 0003-manager-Disable-graph-data-func-if-graph-isn-t-visib.patch create mode 100644 0004-inspection-Check-can_set_row_none-before-setting-ico.patch create mode 100644 0005-virt-manager-ignore-VIR_ERR_NO_DOMAIN-when-a-domain-.patch create mode 100644 0006-manager-Merge-some-row-creation-drop-unneeded-row-ke.patch create mode 100644 0007-manager-Separate-stats-and-state-update-callbacks.patch diff --git a/0001-Fix-rawhide-URL-installs.patch b/0001-Fix-rawhide-URL-installs.patch new file mode 100644 index 0000000..71835c0 --- /dev/null +++ b/0001-Fix-rawhide-URL-installs.patch @@ -0,0 +1,24 @@ +From 17406905d786734e9d30b3a808314f7bb1e28891 Mon Sep 17 00:00:00 2001 +From: Lars Seipel +Date: Mon, 29 Jul 2013 12:28:22 -0400 +Subject: [PATCH] Fix rawhide URL installs + +https://bugzilla.redhat.com/show_bug.cgi?id=989162 +(cherry picked from commit 72648c9185e3ccb27fb9896a4e7a48f1769c8a4e) +--- + virtinst/OSDistro.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/virtinst/OSDistro.py b/virtinst/OSDistro.py +index 5ecfdf6..780823c 100644 +--- a/virtinst/OSDistro.py ++++ b/virtinst/OSDistro.py +@@ -512,7 +512,7 @@ class FedoraDistro(RedHatDistro): + if ret: + lateststr, latestnum = self._latestFedoraVariant() + ver = self.treeinfo.get("general", "version") +- if ver == "development": ++ if ver == "development" or ver == "rawhide": + self.os_variant = self._latestFedoraVariant() + elif ver: + vernum = int(str(ver).split("-")[0]) diff --git a/0002-console-Fix-spice-with-TLS-bz-904295.patch b/0002-console-Fix-spice-with-TLS-bz-904295.patch new file mode 100644 index 0000000..3a12be1 --- /dev/null +++ b/0002-console-Fix-spice-with-TLS-bz-904295.patch @@ -0,0 +1,94 @@ +From 11eb5ff728413ca1b3c6404e4e42550add553355 Mon Sep 17 00:00:00 2001 +From: Cole Robinson +Date: Sun, 1 Sep 2013 16:08:14 -0400 +Subject: [PATCH] console: Fix spice with TLS (bz #904295) + +We now use the same CA trust store as virt-viewer's default, which is: + +~/.spicec/spice_truststore.pem + +(cherry picked from commit cdc82e62a236d0e737851c693d6673f65ca278c1) + +Conflicts: + virtManager/console.py +--- + virtManager/console.py | 26 +++++++++++++++----------- + 1 file changed, 15 insertions(+), 11 deletions(-) + +diff --git a/virtManager/console.py b/virtManager/console.py +index 001318e..90db31c 100644 +--- a/virtManager/console.py ++++ b/virtManager/console.py +@@ -63,6 +63,7 @@ class ConnectionInfo(object): + self.gport = gdev.port and str(gdev.port) or None + self.gsocket = gdev.socket + self.gaddr = gdev.listen or "127.0.0.1" ++ self.gtlsport = gdev.tlsPort or None + + self.transport, self.connuser = conn.get_transport() + self._connhost = conn.get_uri_hostname() or "127.0.0.1" +@@ -80,19 +81,22 @@ class ConnectionInfo(object): + def get_conn_host(self): + host = self._connhost + port = self._connport ++ tlsport = None + + if not self.need_tunnel(): + port = self.gport ++ tlsport = self.gtlsport + if self.gaddr != "0.0.0.0": + host = self.gaddr + +- return host, port ++ return host, port, tlsport + + def logstring(self): + return ("proto=%s trans=%s connhost=%s connuser=%s " +- "connport=%s gaddr=%s gport=%s gsocket=%s" % ++ "connport=%s gaddr=%s gport=%s gtlsport=%s gsocket=%s" % + (self.gtype, self.transport, self._connhost, self.connuser, +- self._connport, self.gaddr, self.gport, self.gsocket)) ++ self._connport, self.gaddr, self.gport, self.gtlsport, ++ self.gsocket)) + def console_active(self): + if self.gsocket: + return True +@@ -111,7 +115,7 @@ class Tunnel(object): + if self.outfd is not None: + return -1 + +- host, port = ginfo.get_conn_host() ++ host, port, ignore = ginfo.get_conn_host() + + # Build SSH cmd + argv = ["ssh", "ssh"] +@@ -406,7 +410,7 @@ class VNCViewer(Viewer): + return self.display.is_open() + + def open_host(self, ginfo, password=None): +- host, port = ginfo.get_conn_host() ++ host, port, ignore = ginfo.get_conn_host() + + if not ginfo.gsocket: + logging.debug("VNC connection to %s:%s", host, port) +@@ -553,14 +557,14 @@ class SpiceViewer(Viewer): + return self.display_channel.get_properties("width", "height") + + def open_host(self, ginfo, password=None): +- host, port = ginfo.get_conn_host() +- +- uri = "spice://" +- uri += str(host) + "?port=" + str(port) +- logging.debug("spice uri: %s", uri) ++ host, port, tlsport = ginfo.get_conn_host() + + self.spice_session = SpiceClientGLib.Session() +- self.spice_session.set_property("uri", uri) ++ SpiceClientGLib.set_session_option(self.spice_session) ++ self.spice_session.set_property("host", str(host)) ++ self.spice_session.set_property("port", str(port)) ++ if tlsport: ++ self.spice_session.set_property("tls-port", str(tlsport)) + if password: + self.spice_session.set_property("password", password) + GObject.GObject.connect(self.spice_session, "channel-new", diff --git a/0003-manager-Disable-graph-data-func-if-graph-isn-t-visib.patch b/0003-manager-Disable-graph-data-func-if-graph-isn-t-visib.patch new file mode 100644 index 0000000..e2fe994 --- /dev/null +++ b/0003-manager-Disable-graph-data-func-if-graph-isn-t-visib.patch @@ -0,0 +1,165 @@ +From 0c452553f19bcaf9cf5ebc52d1d18014ffe10160 Mon Sep 17 00:00:00 2001 +From: Cole Robinson +Date: Sun, 1 Sep 2013 21:35:23 -0400 +Subject: [PATCH] manager: Disable graph data func if graph isn't visible + +Seems like pygobject is a bit leaky with tree row accesses, and +the cell cb hammers on that quite a bit, even if the graph isn't showing. + +(cherry picked from commit db7db9ab47dd00d746cf8a3359c1c26dbfcfa50e) +--- + virtManager/manager.py | 82 ++++++++++++++++++++++---------------------------- + 1 file changed, 36 insertions(+), 46 deletions(-) + +diff --git a/virtManager/manager.py b/virtManager/manager.py +index bdc21cf..ca9d909 100644 +--- a/virtManager/manager.py ++++ b/virtManager/manager.py +@@ -270,15 +270,11 @@ class vmmManager(vmmGObjectUI): + self.config.on_stats_enable_net_poll_changed(self.enable_polling, + COL_NETWORK)) + ++ self.toggle_guest_cpu_usage_visible_widget() ++ self.toggle_host_cpu_usage_visible_widget() ++ self.toggle_disk_io_visible_widget() ++ self.toggle_network_traffic_visible_widget() + +- self.widget("menu_view_stats_guest_cpu").set_active( +- self.config.is_vmlist_guest_cpu_usage_visible()) +- self.widget("menu_view_stats_host_cpu").set_active( +- self.config.is_vmlist_host_cpu_usage_visible()) +- self.widget("menu_view_stats_disk").set_active( +- self.config.is_vmlist_disk_io_visible()) +- self.widget("menu_view_stats_network").set_active( +- self.config.is_vmlist_network_traffic_visible()) + + def init_toolbar(self): + self.widget("vm-new").set_icon_name("vm_new") +@@ -428,7 +424,7 @@ class vmmManager(vmmGObjectUI): + nameCol.add_attribute(name_txt, 'foreground', ROW_COLOR) + nameCol.set_sort_column_id(COL_NAME) + +- def make_stats_column(title, datafunc, is_visible, colnum): ++ def make_stats_column(title, colnum): + col = Gtk.TreeViewColumn(title) + col.set_min_width(140) + +@@ -443,29 +439,15 @@ class vmmManager(vmmGObjectUI): + img.set_property("reversed", True) + col.pack_start(img, True) + col.add_attribute(img, 'visible', ROW_IS_VM) +- col.set_cell_data_func(img, datafunc, None) + +- col.set_visible(is_visible) + col.set_sort_column_id(colnum) + vmlist.append_column(col) + return col + +- self.guestcpucol = make_stats_column(_("CPU usage"), +- self.guest_cpu_usage_img, +- self.config.is_vmlist_guest_cpu_usage_visible(), +- COL_GUEST_CPU) +- self.hostcpucol = make_stats_column(_("Host CPU usage"), +- self.host_cpu_usage_img, +- self.config.is_vmlist_host_cpu_usage_visible(), +- COL_HOST_CPU) +- self.diskcol = make_stats_column(_("Disk I/O"), +- self.disk_io_img, +- self.config.is_vmlist_disk_io_visible(), +- COL_DISK) +- self.netcol = make_stats_column(_("Network I/O"), +- self.network_traffic_img, +- self.config.is_vmlist_network_traffic_visible(), +- COL_NETWORK) ++ self.guestcpucol = make_stats_column(_("CPU usage"), COL_GUEST_CPU) ++ self.hostcpucol = make_stats_column(_("Host CPU usage"), COL_HOST_CPU) ++ self.diskcol = make_stats_column(_("Disk I/O"), COL_DISK) ++ self.netcol = make_stats_column(_("Network I/O"), COL_NETWORK) + + model.set_sort_func(COL_NAME, self.vmlist_name_sorter) + model.set_sort_func(COL_GUEST_CPU, self.vmlist_guest_cpu_usage_sorter) +@@ -1196,25 +1178,33 @@ class vmmManager(vmmGObjectUI): + current_text = current_text + disabled_text + widget.set_label(current_text) + +- def toggle_network_traffic_visible_widget(self): +- val = self.config.is_vmlist_network_traffic_visible() +- self.netcol.set_visible(val) +- self.widget("menu_view_stats_network").set_active(val) ++ def _toggle_graph_helper(self, do_show, col, datafunc, menu): ++ img = -1 ++ for child in col.get_cells(): ++ if isinstance(child, CellRendererSparkline): ++ img = child ++ datafunc = do_show and datafunc or None + +- def toggle_disk_io_visible_widget(self): +- val = self.config.is_vmlist_disk_io_visible() +- self.diskcol.set_visible(val) +- self.widget("menu_view_stats_disk").set_active(val) ++ col.set_cell_data_func(img, datafunc, None) ++ col.set_visible(do_show) ++ self.widget(menu).set_active(do_show) + ++ def toggle_network_traffic_visible_widget(self): ++ self._toggle_graph_helper( ++ self.config.is_vmlist_network_traffic_visible(), self.netcol, ++ self.network_traffic_img, "menu_view_stats_network") ++ def toggle_disk_io_visible_widget(self): ++ self._toggle_graph_helper( ++ self.config.is_vmlist_disk_io_visible(), self.diskcol, ++ self.disk_io_img, "menu_view_stats_disk") + def toggle_guest_cpu_usage_visible_widget(self): +- val = self.config.is_vmlist_guest_cpu_usage_visible() +- self.guestcpucol.set_visible(val) +- self.widget("menu_view_stats_guest_cpu").set_active(val) +- ++ self._toggle_graph_helper( ++ self.config.is_vmlist_guest_cpu_usage_visible(), self.guestcpucol, ++ self.guest_cpu_usage_img, "menu_view_stats_guest_cpu") + def toggle_host_cpu_usage_visible_widget(self): +- val = self.config.is_vmlist_host_cpu_usage_visible() +- self.hostcpucol.set_visible(val) +- self.widget("menu_view_stats_host_cpu").set_active(val) ++ self._toggle_graph_helper( ++ self.config.is_vmlist_host_cpu_usage_visible(), self.hostcpucol, ++ self.host_cpu_usage_img, "menu_view_stats_host_cpu") + + def toggle_stats_visible(self, src, stats_id): + visible = src.get_active() +@@ -1236,7 +1226,7 @@ class vmmManager(vmmGObjectUI): + self.toggle_stats_visible(src, COL_NETWORK) + + def guest_cpu_usage_img(self, column_ignore, cell, model, _iter, data): +- obj = model.get_value(_iter, ROW_HANDLE) ++ obj = model[_iter][ROW_HANDLE] + if obj is None or not hasattr(obj, "conn"): + return + +@@ -1244,7 +1234,7 @@ class vmmManager(vmmGObjectUI): + cell.set_property('data_array', data) + + def host_cpu_usage_img(self, column_ignore, cell, model, _iter, data): +- obj = model.get_value(_iter, ROW_HANDLE) ++ obj = model[_iter][ROW_HANDLE] + if obj is None or not hasattr(obj, "conn"): + return + +@@ -1252,7 +1242,7 @@ class vmmManager(vmmGObjectUI): + cell.set_property('data_array', data) + + def disk_io_img(self, column_ignore, cell, model, _iter, data): +- obj = model.get_value(_iter, ROW_HANDLE) ++ obj = model[_iter][ROW_HANDLE] + if obj is None or not hasattr(obj, "conn"): + return + +@@ -1260,7 +1250,7 @@ class vmmManager(vmmGObjectUI): + cell.set_property('data_array', data) + + def network_traffic_img(self, column_ignore, cell, model, _iter, data): +- obj = model.get_value(_iter, ROW_HANDLE) ++ obj = model[_iter][ROW_HANDLE] + if obj is None or not hasattr(obj, "conn"): + return + diff --git a/0004-inspection-Check-can_set_row_none-before-setting-ico.patch b/0004-inspection-Check-can_set_row_none-before-setting-ico.patch new file mode 100644 index 0000000..b9dc4e4 --- /dev/null +++ b/0004-inspection-Check-can_set_row_none-before-setting-ico.patch @@ -0,0 +1,46 @@ +From 89d78c1af3f84113ea0d18b4229e98d16c12bfef Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Fri, 28 Jun 2013 18:45:53 +0100 +Subject: [PATCH] inspection: Check can_set_row_none before setting icon to + None. + +Previously one could set the icon field of a tree model to None, but +with Gtk3 and older versions of pygobject3 this would fail. + +Use can_set_row_none to test for this condition and work around it. + +The previous traceback was: + +Traceback (most recent call last): + File "/home/rjones/d/virt-manager/virtManager/manager.py", line 981, in vm_inspection_changed + self.get_inspection_icon_pixbuf(vm, 16, 16) + File "/usr/lib64/python2.7/site-packages/gi/overrides/Gtk.py", line 1152, in __setitem__ + self.model.set_value(self.iter, key, value) + File "/usr/lib64/python2.7/site-packages/gi/overrides/Gtk.py", line 1298, in set_value + Gtk.TreeStore.set_value(self, treeiter, column, value) + File "/usr/lib64/python2.7/site-packages/gi/types.py", line 47, in function + return info.invoke(*args, **kwargs) +TypeError: Argument 3 does not allow None as a value + +(cherry picked from commit 9d5c1cdb1c03a8c045ae94ab5324aa7dda68e695) +--- + virtManager/manager.py | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +diff --git a/virtManager/manager.py b/virtManager/manager.py +index ca9d909..3682429 100644 +--- a/virtManager/manager.py ++++ b/virtManager/manager.py +@@ -959,8 +959,10 @@ class vmmManager(vmmGObjectUI): + return + + row = self.rows[self.vm_row_key(vm)] +- row[ROW_INSPECTION_OS_ICON] = \ +- self.get_inspection_icon_pixbuf(vm, 16, 16) ++ new_icon = self.get_inspection_icon_pixbuf(vm, 16, 16) ++ if not can_set_row_none: ++ new_icon = new_icon or "" ++ row[ROW_INSPECTION_OS_ICON] = new_icon + model.row_changed(row.path, row.iter) + + def get_inspection_icon_pixbuf(self, vm, w, h): diff --git a/0005-virt-manager-ignore-VIR_ERR_NO_DOMAIN-when-a-domain-.patch b/0005-virt-manager-ignore-VIR_ERR_NO_DOMAIN-when-a-domain-.patch new file mode 100644 index 0000000..69d4be6 --- /dev/null +++ b/0005-virt-manager-ignore-VIR_ERR_NO_DOMAIN-when-a-domain-.patch @@ -0,0 +1,147 @@ +From 6df408590cf0c4c094bcc5e8700700118e2d71c6 Mon Sep 17 00:00:00 2001 +From: Giuseppe Scrivano +Date: Fri, 16 Aug 2013 15:59:33 +0200 +Subject: [PATCH] virt-manager: ignore VIR_ERR_NO_DOMAIN when a domain was just + deleted + +Some callbacks could try to access a domain that was just deleted and +not accessible anymore. Detect these cases and don't propagate the +exception. + +Signed-off-by: Giuseppe Scrivano +(cherry picked from commit c1fa43ebb90c26c57f72faaf6c0b6a5c19221dd4) + +Conflicts: + virtManager/uihelpers.py +--- + virtManager/details.py | 10 ++++++++-- + virtManager/domain.py | 14 +++++++++----- + virtManager/manager.py | 29 ++++++++++++++++++----------- + virtManager/uihelpers.py | 5 +++++ + 4 files changed, 40 insertions(+), 18 deletions(-) + +diff --git a/virtManager/details.py b/virtManager/details.py +index b2d496e..79db2cf 100644 +--- a/virtManager/details.py ++++ b/virtManager/details.py +@@ -2598,8 +2598,14 @@ class vmmDetails(vmmGObjectUI): + + # If the dialog is visible, we want to make sure the XML is always + # up to date +- if self.is_visible(): +- self.vm.refresh_xml() ++ try: ++ if self.is_visible(): ++ self.vm.refresh_xml() ++ except libvirt.libvirtError, e: ++ if uihelpers.exception_is_libvirt_error(e, "VIR_ERR_NO_DOMAIN"): ++ self.close() ++ return ++ raise + + # Stats page needs to be refreshed every tick + if (page == PAGE_DETAILS and +diff --git a/virtManager/domain.py b/virtManager/domain.py +index bd3d964..486664f 100644 +--- a/virtManager/domain.py ++++ b/virtManager/domain.py +@@ -32,6 +32,7 @@ from virtinst.VirtualCharDevice import VirtualCharSpicevmcDevice + import virtinst.support as support + + from virtManager import util ++from virtManager import uihelpers + from virtManager.libvirtobject import vmmLibvirtObject + + +@@ -406,7 +407,12 @@ class vmmDomain(vmmLibvirtObject): + caps=self.conn.get_capabilities()) + + def _reparse_xml(self, ignore=None): +- self._guest = self._build_guest(self._get_domain_xml()) ++ try: ++ self._guest = self._build_guest(self._get_domain_xml()) ++ except libvirt.libvirtError, e: ++ if uihelpers.exception_is_libvirt_error(e, "VIR_ERR_NO_DOMAIN"): ++ return ++ raise + + + ############################## +@@ -1566,14 +1572,12 @@ class vmmDomain(vmmLibvirtObject): + """ + try: + info = self._backend.info() ++ self._update_status(info[0]) + except libvirt.libvirtError, e: +- if (hasattr(libvirt, "VIR_ERR_NO_DOMAIN") and +- e.get_error_code() == getattr(libvirt, "VIR_ERR_NO_DOMAIN")): +- # Possibly a transient domain that was removed on shutdown ++ if uihelpers.exception_is_libvirt_error(e, "VIR_ERR_NO_DOMAIN"): + return + raise + +- self._update_status(info[0]) + + def _update_status(self, status): + """ +diff --git a/virtManager/manager.py b/virtManager/manager.py +index 3682429..97cb068 100644 +--- a/virtManager/manager.py ++++ b/virtManager/manager.py +@@ -35,6 +35,7 @@ from virtManager.baseclass import vmmGObjectUI + from virtManager.graphwidgets import CellRendererSparkline + from virtManager import util as util + ++import libvirt + + # Number of data points for performance graphs + GRAPH_LEN = 40 +@@ -936,18 +937,24 @@ class vmmManager(vmmGObjectUI): + if self.vm_row_key(vm) not in self.rows: + return + +- row = self.rows[self.vm_row_key(vm)] +- row[ROW_NAME] = vm.get_name() +- row[ROW_STATUS] = vm.run_status() +- row[ROW_STATUS_ICON] = vm.run_status_icon_name() +- row[ROW_IS_VM_RUNNING] = vm.is_active() +- row[ROW_MARKUP] = self._build_vm_markup(row) + +- if config_changed: +- desc = vm.get_description() +- if not can_set_row_none: +- desc = desc or "" +- row[ROW_HINT] = util.xml_escape(desc) ++ try: ++ row = self.rows[self.vm_row_key(vm)] ++ row[ROW_NAME] = vm.get_name() ++ row[ROW_STATUS] = vm.run_status() ++ row[ROW_STATUS_ICON] = vm.run_status_icon_name() ++ row[ROW_IS_VM_RUNNING] = vm.is_active() ++ row[ROW_MARKUP] = self._build_vm_markup(row) ++ ++ if config_changed: ++ desc = vm.get_description() ++ if not can_set_row_none: ++ desc = desc or "" ++ row[ROW_HINT] = util.xml_escape(desc) ++ except libvirt.libvirtError, e: ++ if uihelpers.exception_is_libvirt_error(e, "VIR_ERR_NO_DOMAIN"): ++ return ++ raise + + model.row_changed(row.path, row.iter) + +diff --git a/virtManager/uihelpers.py b/virtManager/uihelpers.py +index ba69ee9..ff254ab 100644 +--- a/virtManager/uihelpers.py ++++ b/virtManager/uihelpers.py +@@ -1027,3 +1027,8 @@ def build_keycombo_menu(cb): + + menu.show_all() + return menu ++ ++def exception_is_libvirt_error(e, error): ++ import libvirt ++ return (hasattr(libvirt, error) and ++ e.get_error_code() == getattr(libvirt, error)) diff --git a/0006-manager-Merge-some-row-creation-drop-unneeded-row-ke.patch b/0006-manager-Merge-some-row-creation-drop-unneeded-row-ke.patch new file mode 100644 index 0000000..c4cc0fd --- /dev/null +++ b/0006-manager-Merge-some-row-creation-drop-unneeded-row-ke.patch @@ -0,0 +1,316 @@ +From c3e576155228cc3f3f05d3953edadbf589b74d8c Mon Sep 17 00:00:00 2001 +From: Cole Robinson +Date: Sun, 1 Sep 2013 18:36:58 -0400 +Subject: [PATCH] manager: Merge some row creation, drop unneeded row keys + +(cherry picked from commit 1bd6026515324430adbdd7a8b666e1b6a64879c3) + +Conflicts: + virtManager/manager.py +--- + virtManager/manager.py | 174 ++++++++++++++++++++++--------------------------- + 1 file changed, 79 insertions(+), 95 deletions(-) + +diff --git a/virtManager/manager.py b/virtManager/manager.py +index 97cb068..2770b2f 100644 +--- a/virtManager/manager.py ++++ b/virtManager/manager.py +@@ -42,18 +42,16 @@ GRAPH_LEN = 40 + + # fields in the tree model data set + (ROW_HANDLE, +-ROW_NAME, ++ROW_SORT_KEY, + ROW_MARKUP, +-ROW_STATUS, + ROW_STATUS_ICON, +-ROW_KEY, + ROW_HINT, + ROW_IS_CONN, + ROW_IS_CONN_CONNECTED, + ROW_IS_VM, + ROW_IS_VM_RUNNING, + ROW_COLOR, +-ROW_INSPECTION_OS_ICON) = range(13) ++ROW_INSPECTION_OS_ICON) = range(11) + + # Columns in the tree view + COL_NAME = 0 +@@ -78,6 +76,24 @@ def _style_get_prop(widget, propname): + return value.get_int() + + ++def _get_inspection_icon_pixbuf(vm, w, h): ++ # libguestfs gives us the PNG data as a string. ++ png_data = vm.inspection.icon ++ if png_data is None: ++ return None ++ ++ try: ++ pb = GdkPixbuf.PixbufLoader() ++ pb.set_size(w, h) ++ pb.write(png_data) ++ pb.close() ++ return pb.get_pixbuf() ++ except: ++ logging.exception("Error loading inspection icon data") ++ vm.inspection.icon = None ++ return None ++ ++ + class vmmManager(vmmGObjectUI): + __gsignals__ = { + "action-show-connect": (GObject.SignalFlags.RUN_FIRST, None, []), +@@ -380,11 +396,9 @@ class vmmManager(vmmGObjectUI): + + rowtypes = [] + rowtypes.insert(ROW_HANDLE, object) # backing object +- rowtypes.insert(ROW_NAME, str) # object name ++ rowtypes.insert(ROW_SORT_KEY, str) # object name + rowtypes.insert(ROW_MARKUP, str) # row markup text +- rowtypes.insert(ROW_STATUS, str) # object status string + rowtypes.insert(ROW_STATUS_ICON, str) # status icon name +- rowtypes.insert(ROW_KEY, str) # key/uuid + rowtypes.insert(ROW_HINT, str) # row tooltip + rowtypes.insert(ROW_IS_CONN, bool) # if object is a connection + rowtypes.insert(ROW_IS_CONN_CONNECTED, bool) # if conn is connected +@@ -764,8 +778,8 @@ class vmmManager(vmmGObjectUI): + hint += " (%s)" % _("Double click to connect") + return hint + +- def _build_conn_markup(self, conn, row): +- name = util.xml_escape(row[ROW_NAME]) ++ def _build_conn_markup(self, conn, name): ++ name = util.xml_escape(name) + text = name + if conn.state == conn.STATE_DISCONNECTED: + text += " - " + _("Not Connected") +@@ -781,31 +795,44 @@ class vmmManager(vmmGObjectUI): + color = "#5b5b5b" + return color + +- def _build_vm_markup(self, row): ++ def _build_vm_markup(self, name, status): + domtext = ("%s" % +- util.xml_escape(row[ROW_NAME])) +- statetext = "%s" % row[ROW_STATUS] ++ util.xml_escape(name)) ++ statetext = "%s" % status + return domtext + "\n" + statetext + +- def _build_vm_row(self, vm): +- row = [] ++ def _build_row(self, conn, vm): ++ if conn: ++ name = conn.get_pretty_desc_inactive(False) ++ markup = self._build_conn_markup(conn, name) ++ status = ("%s" % ++ conn.get_state_text()) ++ status_icon = None ++ hint = self._build_conn_hint(conn) ++ color = self._build_conn_color(conn) ++ os_icon = None ++ else: ++ name = vm.get_name() ++ status = vm.run_status() ++ markup = self._build_vm_markup(name, status) ++ status_icon = vm.run_status_icon_name() ++ hint = vm.get_description() ++ color = None ++ os_icon = _get_inspection_icon_pixbuf(vm, 16, 16) + +- row.insert(ROW_HANDLE, vm) +- row.insert(ROW_NAME, vm.get_name()) +- row.insert(ROW_MARKUP, "") +- row.insert(ROW_STATUS, vm.run_status()) +- row.insert(ROW_STATUS_ICON, vm.run_status_icon_name()) +- row.insert(ROW_KEY, vm.get_uuid()) +- row.insert(ROW_HINT, util.xml_escape(vm.get_description())) +- row.insert(ROW_IS_CONN, False) +- row.insert(ROW_IS_CONN_CONNECTED, True) +- row.insert(ROW_IS_VM, True) +- row.insert(ROW_IS_VM_RUNNING, vm.is_active()) +- row.insert(ROW_COLOR, None) +- row.insert(ROW_INSPECTION_OS_ICON, +- self.get_inspection_icon_pixbuf(vm, 16, 16)) +- +- row[ROW_MARKUP] = self._build_vm_markup(row) ++ row = [] ++ row.insert(ROW_HANDLE, conn or vm) ++ row.insert(ROW_SORT_KEY, name) ++ row.insert(ROW_MARKUP, markup) ++ row.insert(ROW_STATUS_ICON, status_icon) ++ row.insert(ROW_HINT, util.xml_escape(hint)) ++ row.insert(ROW_IS_CONN, bool(conn)) ++ row.insert(ROW_IS_CONN_CONNECTED, ++ bool(conn) and conn.state != conn.STATE_DISCONNECTED) ++ row.insert(ROW_IS_VM, bool(vm)) ++ row.insert(ROW_IS_VM_RUNNING, bool(vm) and vm.is_active()) ++ row.insert(ROW_COLOR, color) ++ row.insert(ROW_INSPECTION_OS_ICON, os_icon) + + return row + +@@ -814,7 +841,7 @@ class vmmManager(vmmGObjectUI): + if row_key in self.rows: + return + +- row = self._build_vm_row(vm) ++ row = self._build_row(None, vm) + parent = self.rows[conn.get_uri()].iter + + _iter = model.append(parent, row) +@@ -824,28 +851,8 @@ class vmmManager(vmmGObjectUI): + # Expand a connection when adding a vm to it + self.widget("vm-list").expand_row(model.get_path(parent), False) + +- def _build_conn_row(self, conn): +- row = [] +- row.insert(ROW_HANDLE, conn) +- row.insert(ROW_NAME, conn.get_pretty_desc_inactive(False)) +- row.insert(ROW_MARKUP, self._build_conn_markup(conn, row)) +- row.insert(ROW_STATUS, ("%s" % +- conn.get_state_text())) +- row.insert(ROW_STATUS_ICON, None) +- row.insert(ROW_KEY, conn.get_uri()) +- row.insert(ROW_HINT, self._build_conn_hint(conn)) +- row.insert(ROW_IS_CONN, True) +- row.insert(ROW_IS_CONN_CONNECTED, +- conn.state != conn.STATE_DISCONNECTED) +- row.insert(ROW_IS_VM, False) +- row.insert(ROW_IS_VM_RUNNING, False) +- row.insert(ROW_COLOR, self._build_conn_color(conn)) +- row.insert(ROW_INSPECTION_OS_ICON, None) +- +- return row +- + def _append_conn(self, model, conn): +- row = self._build_conn_row(conn) ++ row = self._build_row(conn, None) + + _iter = model.append(None, row) + path = model.get_path(_iter) +@@ -877,11 +884,11 @@ class vmmManager(vmmGObjectUI): + if row[ROW_IS_CONN]: + connrows.append(row) + for row in connrows: +- descs.append(row[ROW_NAME]) ++ descs.append(row[ROW_SORT_KEY]) + + for row in connrows: + conn = row[ROW_HANDLE] +- name = row[ROW_NAME] ++ name = row[ROW_SORT_KEY] + if descs.count(name) <= 1: + continue + +@@ -919,7 +926,7 @@ class vmmManager(vmmGObjectUI): + missing = True + for row in range(model.iter_n_children(parent)): + _iter = model.iter_nth_child(parent, row) +- if model.get_value(_iter, ROW_KEY) == vm.get_uuid(): ++ if model.get_value(_iter, ROW_HANDLE) == vm: + missing = False + break + +@@ -931,20 +938,18 @@ class vmmManager(vmmGObjectUI): + self.vm_resources_sampled(vm) + + def vm_resources_sampled(self, vm, config_changed=False): +- vmlist = self.widget("vm-list") +- model = vmlist.get_model() +- +- if self.vm_row_key(vm) not in self.rows: ++ row = self.rows.get(self.vm_row_key(vm), None) ++ if row is None: + return + +- + try: +- row = self.rows[self.vm_row_key(vm)] +- row[ROW_NAME] = vm.get_name() +- row[ROW_STATUS] = vm.run_status() ++ name = vm.get_name() ++ status = vm.run_status() ++ ++ row[ROW_SORT_KEY] = name + row[ROW_STATUS_ICON] = vm.run_status_icon_name() + row[ROW_IS_VM_RUNNING] = vm.is_active() +- row[ROW_MARKUP] = self._build_vm_markup(row) ++ row[ROW_MARKUP] = self._build_vm_markup(name, status) + + if config_changed: + desc = vm.get_description() +@@ -956,53 +961,32 @@ class vmmManager(vmmGObjectUI): + return + raise + +- model.row_changed(row.path, row.iter) ++ self.widget("vm-list").get_model().row_changed(row.path, row.iter) + + def vm_inspection_changed(self, vm): +- vmlist = self.widget("vm-list") +- model = vmlist.get_model() +- +- if self.vm_row_key(vm) not in self.rows: ++ row = self.rows.get(self.vm_row_key(vm), None) ++ if row is None: + return + + row = self.rows[self.vm_row_key(vm)] +- new_icon = self.get_inspection_icon_pixbuf(vm, 16, 16) ++ new_icon = _get_inspection_icon_pixbuf(vm, 16, 16) + if not can_set_row_none: + new_icon = new_icon or "" + row[ROW_INSPECTION_OS_ICON] = new_icon +- model.row_changed(row.path, row.iter) +- +- def get_inspection_icon_pixbuf(self, vm, w, h): +- # libguestfs gives us the PNG data as a string. +- png_data = vm.inspection.icon +- if png_data is None: +- return None + +- try: +- pb = GdkPixbuf.PixbufLoader() +- pb.set_size(w, h) +- pb.write(png_data) +- pb.close() +- return pb.get_pixbuf() +- except: +- logging.exception("Error loading inspection icon data") +- vm.inspection.icon = None +- return None ++ self.widget("vm-list").get_model().row_changed(row.path, row.iter) + + def conn_state_changed(self, conn): + self.conn_resources_sampled(conn) + self.vm_selected() + + def conn_resources_sampled(self, conn, newname=None): +- vmlist = self.widget("vm-list") +- model = vmlist.get_model() ++ model = self.widget("vm-list").get_model() + row = self.rows[conn.get_uri()] + + if newname: +- row[ROW_NAME] = newname +- row[ROW_MARKUP] = self._build_conn_markup(conn, row) +- row[ROW_STATUS] = ("%s" % +- conn.get_state_text()) ++ row[ROW_SORT_KEY] = newname ++ row[ROW_MARKUP] = self._build_conn_markup(conn, row[ROW_SORT_KEY]) + row[ROW_IS_CONN_CONNECTED] = conn.state != conn.STATE_DISCONNECTED + row[ROW_COLOR] = self._build_conn_color(conn) + row[ROW_HINT] = self._build_conn_hint(conn) +@@ -1131,8 +1115,8 @@ class vmmManager(vmmGObjectUI): + ################# + + def vmlist_name_sorter(self, model, iter1, iter2, ignore): +- return cmp(model.get_value(iter1, ROW_NAME), +- model.get_value(iter2, ROW_NAME)) ++ return cmp(model.get_value(iter1, ROW_SORT_KEY), ++ model.get_value(iter2, ROW_SORT_KEY)) + + def vmlist_guest_cpu_usage_sorter(self, model, iter1, iter2, ignore): + obj1 = model.get_value(iter1, ROW_HANDLE) diff --git a/0007-manager-Separate-stats-and-state-update-callbacks.patch b/0007-manager-Separate-stats-and-state-update-callbacks.patch new file mode 100644 index 0000000..2a762e0 --- /dev/null +++ b/0007-manager-Separate-stats-and-state-update-callbacks.patch @@ -0,0 +1,204 @@ +From ed5dc524c54e72ee7f75749f11eb0606dddee8ea Mon Sep 17 00:00:00 2001 +From: Cole Robinson +Date: Mon, 2 Sep 2013 09:09:31 -0400 +Subject: [PATCH] manager: Separate stats and state update callbacks + +There's no need to be resetting row keys like VM name, state, on every +'resources-sampled' signals, since we have had finer grained status-changed +and config-changed signals for a while. This seems to reduce the memory +leak on F19 as well. + +(cherry picked from commit f141c77c452d4807b516bdc40a0e86235bf5243a) + +Conflicts: + virtManager/manager.py +--- + virtManager/manager.py | 100 ++++++++++++++++++++++++++----------------------- + 1 file changed, 53 insertions(+), 47 deletions(-) + +diff --git a/virtManager/manager.py b/virtManager/manager.py +index 2770b2f..6773ff3 100644 +--- a/virtManager/manager.py ++++ b/virtManager/manager.py +@@ -185,9 +185,9 @@ class vmmManager(vmmGObjectUI): + self.init_toolbar() + self.init_context_menus() + +- self.vm_selected() +- self.widget("vm-list").get_selection().connect("changed", +- self.vm_selected) ++ self.update_current_selection() ++ self.widget("vm-list").get_selection().connect( ++ "changed", self.update_current_selection) + + self.max_disk_rate = 10.0 + self.max_net_rate = 10.0 +@@ -750,9 +750,9 @@ class vmmManager(vmmGObjectUI): + + def vm_added(self, conn, vmuuid): + vm = conn.get_vm(vmuuid) ++ vm.connect("config-changed", self.vm_config_changed) + vm.connect("status-changed", self.vm_status_changed) +- vm.connect("resources-sampled", self.vm_resources_sampled) +- vm.connect("config-changed", self.vm_resources_sampled, True) ++ vm.connect("resources-sampled", self.vm_row_updated) + vm.connect("inspection-changed", self.vm_inspection_changed) + + vmlist = self.widget("vm-list") +@@ -868,7 +868,7 @@ class vmmManager(vmmGObjectUI): + + conn.connect("vm-added", self.vm_added) + conn.connect("vm-removed", self.vm_removed) +- conn.connect("resources-sampled", self.conn_resources_sampled) ++ conn.connect("resources-sampled", self.conn_row_updated) + conn.connect("state-changed", self.conn_state_changed) + conn.connect("connect-error", self._connect_error) + +@@ -893,7 +893,7 @@ class vmmManager(vmmGObjectUI): + continue + + newname = conn.get_pretty_desc_inactive(False, True) +- self.conn_resources_sampled(conn, newname) ++ self.conn_state_changed(conn, newname=newname) + + def remove_conn(self, engine_ignore, uri): + model = self.widget("vm-list").get_model() +@@ -916,28 +916,13 @@ class vmmManager(vmmGObjectUI): + # State/UI updating methods # + ############################# + +- def vm_status_changed(self, vm, oldstatus, newstatus): +- ignore = newstatus +- ignore = oldstatus +- parent = self.rows[vm.conn.get_uri()].iter +- vmlist = self.widget("vm-list") +- model = vmlist.get_model() +- +- missing = True +- for row in range(model.iter_n_children(parent)): +- _iter = model.iter_nth_child(parent, row) +- if model.get_value(_iter, ROW_HANDLE) == vm: +- missing = False +- break +- +- if missing: +- self._append_vm(model, vm, vm.conn) +- +- # Update run/shutdown/pause button states +- self.vm_selected() +- self.vm_resources_sampled(vm) ++ def vm_row_updated(self, vm): ++ row = self.rows.get(self.vm_row_key(vm), None) ++ if row is None: ++ return ++ self.widget("vm-list").get_model().row_changed(row.path, row.iter) + +- def vm_resources_sampled(self, vm, config_changed=False): ++ def vm_config_changed(self, vm): + row = self.rows.get(self.vm_row_key(vm), None) + if row is None: + return +@@ -951,17 +936,37 @@ class vmmManager(vmmGObjectUI): + row[ROW_IS_VM_RUNNING] = vm.is_active() + row[ROW_MARKUP] = self._build_vm_markup(name, status) + +- if config_changed: +- desc = vm.get_description() +- if not can_set_row_none: +- desc = desc or "" +- row[ROW_HINT] = util.xml_escape(desc) ++ desc = vm.get_description() ++ if not can_set_row_none: ++ desc = desc or "" ++ row[ROW_HINT] = util.xml_escape(desc) + except libvirt.libvirtError, e: + if uihelpers.exception_is_libvirt_error(e, "VIR_ERR_NO_DOMAIN"): + return + raise + +- self.widget("vm-list").get_model().row_changed(row.path, row.iter) ++ self.vm_row_updated(vm) ++ ++ def vm_status_changed(self, vm, oldstatus, newstatus): ++ ignore = newstatus ++ ignore = oldstatus ++ parent = self.rows[vm.conn.get_uri()].iter ++ vmlist = self.widget("vm-list") ++ model = vmlist.get_model() ++ ++ missing = True ++ for row in range(model.iter_n_children(parent)): ++ _iter = model.iter_nth_child(parent, row) ++ if model.get_value(_iter, ROW_HANDLE) == vm: ++ missing = False ++ break ++ ++ if missing: ++ self._append_vm(model, vm, vm.conn) ++ ++ # Update run/shutdown/pause button states ++ self.update_current_selection() ++ self.vm_config_changed(vm) + + def vm_inspection_changed(self, vm): + row = self.rows.get(self.vm_row_key(vm), None) +@@ -974,16 +979,10 @@ class vmmManager(vmmGObjectUI): + new_icon = new_icon or "" + row[ROW_INSPECTION_OS_ICON] = new_icon + +- self.widget("vm-list").get_model().row_changed(row.path, row.iter) +- +- def conn_state_changed(self, conn): +- self.conn_resources_sampled(conn) +- self.vm_selected() ++ self.vm_row_updated(vm) + +- def conn_resources_sampled(self, conn, newname=None): +- model = self.widget("vm-list").get_model() ++ def conn_state_changed(self, conn, newname=None): + row = self.rows[conn.get_uri()] +- + if newname: + row[ROW_SORT_KEY] = newname + row[ROW_MARKUP] = self._build_conn_markup(conn, row[ROW_SORT_KEY]) +@@ -994,20 +993,27 @@ class vmmManager(vmmGObjectUI): + if conn.get_state() in [vmmConnection.STATE_DISCONNECTED, + vmmConnection.STATE_CONNECTING]: + # Connection went inactive, delete any VM child nodes +- parent = self.rows[conn.get_uri()].iter ++ parent = row.iter + if parent is not None: ++ model = self.widget("vm-list").get_model() + child = model.iter_children(parent) + while child is not None: +- del self.rows[self.vm_row_key(model.get_value(child, +- ROW_HANDLE))] ++ vm = model[child][ROW_HANDLE] ++ del self.rows[self.vm_row_key(vm)] + model.remove(child) + child = model.iter_children(parent) + ++ self.conn_row_updated(conn) ++ self.update_current_selection() ++ ++ def conn_row_updated(self, conn): ++ row = self.rows[conn.get_uri()] ++ + self.max_disk_rate = max(self.max_disk_rate, conn.disk_io_max_rate()) + self.max_net_rate = max(self.max_net_rate, + conn.network_traffic_max_rate()) + +- model.row_changed(row.path, row.iter) ++ self.widget("vm-list").get_model().row_changed(row.path, row.iter) + + def change_run_text(self, can_restore): + if can_restore: +@@ -1019,7 +1025,7 @@ class vmmManager(vmmGObjectUI): + self.vmmenu_items["run"].get_child().set_label(text) + self.widget("vm-run").set_label(strip_text) + +- def vm_selected(self, ignore=None): ++ def update_current_selection(self, ignore=None): + vm = self.current_vm() + + show_open = bool(vm) diff --git a/virt-manager.spec b/virt-manager.spec index f0aedb8..1b5f9f7 100644 --- a/virt-manager.spec +++ b/virt-manager.spec @@ -21,7 +21,7 @@ %define _version 0.10.0 -%define _release 1 +%define _release 2 # This macro is used for the continuous automated builds. It just @@ -40,6 +40,17 @@ Group: Applications/Emulators License: GPLv2+ URL: http://virt-manager.org/ Source0: http://virt-manager.org/download/sources/%{name}/%{name}-%{version}.tar.gz + +# Fix parsing rawhide .treeinfo (bz #989162) +Patch0001: 0001-Fix-rawhide-URL-installs.patch +# Fix spice with TLS (bz #904295) +Patch0002: 0002-console-Fix-spice-with-TLS-bz-904295.patch +# Reduce impact of memory leak (bz #972371) +Patch0003: 0003-manager-Disable-graph-data-func-if-graph-isn-t-visib.patch +Patch0004: 0004-inspection-Check-can_set_row_none-before-setting-ico.patch +Patch0005: 0005-virt-manager-ignore-VIR_ERR_NO_DOMAIN-when-a-domain-.patch +Patch0006: 0006-manager-Merge-some-row-creation-drop-unneeded-row-ke.patch +Patch0007: 0007-manager-Separate-stats-and-state-update-callbacks.patch BuildArch: noarch @@ -104,6 +115,17 @@ machine). %prep %setup -q +# Fix parsing rawhide .treeinfo (bz #989162) +%patch0001 -p1 +# Fix spice with TLS (bz #904295) +%patch0002 -p1 +# Reduce impact of memory leak (bz #972371) +%patch0003 -p1 +%patch0004 -p1 +%patch0005 -p1 +%patch0006 -p1 +%patch0007 -p1 + %build %if %{qemu_user} %define _qemu_user --qemu-user=%{qemu_user} @@ -213,6 +235,11 @@ fi %changelog +* Tue Sep 24 2013 Cole Robinson - 0.10.0-2 +- Fix parsing rawhide .treeinfo (bz #989162) +- Fix spice with TLS (bz #904295) +- Reduce impact of memory leak (bz #972371) + * Wed Jun 19 2013 Cole Robinson - 0.10.0-1 - Rebased to version 0.10.0 - Fix screenshots (bz #969410)