diff --git a/src/virtManager/baseclass.py b/src/virtManager/baseclass.py index cd3e7e7c9..feef559be 100644 --- a/src/virtManager/baseclass.py +++ b/src/virtManager/baseclass.py @@ -41,8 +41,8 @@ class vmmGObject(gobject.GObject): self._gobject_timeouts = [] self._gconf_handles = [] - self._object_key = str(self) - self.config.add_object(self._object_key) + self.object_key = str(self) + self.config.add_object(self.object_key) def cleanup(self): # Do any cleanup required to drop reference counts so object is @@ -78,6 +78,11 @@ class vmmGObject(gobject.GObject): gobject.source_remove(handle) self._gobject_timeouts.remove(handle) + def _printtrace(self, msg): + import traceback + print "%s (%s %s)\n:%s" % (msg, self.object_key, self.refcount(), + "".join(traceback.format_stack())) + def refcount(self): # Function generates 2 temporary refs, so adjust total accordingly return (sys.getrefcount(self) - 2) @@ -91,9 +96,9 @@ class vmmGObject(gobject.GObject): getattr(gobject.GObject, "__del__")(self) try: - self.config.remove_object(self._object_key) + self.config.remove_object(self.object_key) except: - logging.exception("Error removing %s" % self._object_key) + logging.exception("Error removing %s" % self.object_key) class vmmGObjectUI(vmmGObject): def __init__(self, filename, windowname): diff --git a/src/virtManager/clone.py b/src/virtManager/clone.py index 9000e21a3..d9a629591 100644 --- a/src/virtManager/clone.py +++ b/src/virtManager/clone.py @@ -131,8 +131,33 @@ class vmmCloneVM(vmmGObjectUI): self.change_mac_close() self.change_storage_close() self.topwin.hide() + + self.orig_vm = None + self.clone_design = None + self.storage_list = {} + self.target_list = [] + self.net_list = {} + self.mac_list = [] + return 1 + def cleanup(self): + self.close() + + self.conn = None + + self.change_mac.destroy() + self.change_mac = None + + self.change_storage.destroy() + self.change_storage = None + + if self.storage_browser: + self.storage_browser.cleanup() + self.storage_browser = None + + vmmGObjectUI.cleanup(self) + def change_mac_close(self, ignore1=None, ignore2=None): self.change_mac.hide() return 1 diff --git a/src/virtManager/connection.py b/src/virtManager/connection.py index c2688f966..40a415ce6 100644 --- a/src/virtManager/connection.py +++ b/src/virtManager/connection.py @@ -146,6 +146,7 @@ class vmmConnection(vmmGObject): self.hostinfo = None self.hal_helper_remove_sig = None + self.hal_handles = [] self.netdev_initialized = False self.netdev_error = "" @@ -164,6 +165,7 @@ class vmmConnection(vmmGObject): sig = hal_helper.connect("device-removed", self._haldev_removed) self.hal_helper_remove_sig = sig + self.hal_handles.append(sig) def _init_netdev(self): """ @@ -186,7 +188,8 @@ class vmmConnection(vmmGObject): else: error = hal_helper.get_init_error() if not error: - hal_helper.connect("netdev-added", self._netdev_added) + self.hal_handles.append( + hal_helper.connect("netdev-added", self._netdev_added)) self._set_hal_remove_sig(hal_helper) else: @@ -225,7 +228,8 @@ class vmmConnection(vmmGObject): else: error = hal_helper.get_init_error() if not error: - hal_helper.connect("optical-added", self._optical_added) + self.hal_handles.append( + hal_helper.connect("optical-added", self._optical_added)) self._set_hal_remove_sig(hal_helper) else: @@ -922,6 +926,16 @@ class vmmConnection(vmmGObject): self._change_state(self.STATE_DISCONNECTED) + def cleanup(self): + # Do this first, so signals are unregistered before we change state + vmmGObject.cleanup(self) + self.close() + + hal_helper = self.get_hal_helper() + if hal_helper: + for h in self.hal_handles: + hal_helper.disconnect(h) + def _open_dev_conn(self, uri): """ Allow using virtinsts connection hacking to fake capabilities diff --git a/src/virtManager/create.py b/src/virtManager/create.py index c4306b977..3f930abed 100644 --- a/src/virtManager/create.py +++ b/src/virtManager/create.py @@ -155,9 +155,33 @@ class vmmCreate(vmmGObjectUI): if self.config_window: self.config_window.close() + if self.storage_browser: + self.storage_browser.close() return 1 + def cleanup(self): + self.close() + self.remove_conn() + + self.conn = None + self.caps = None + self.capsguest = None + self.capsdomain = None + + self.guest = None + self.disk = None + self.nic = None + + try: + if self.storage_browser: + self.storage_browser.cleanup() + self.storage_browser = None + except: + logging.exception("Error cleaning up create") + + vmmGObjectUI.cleanup(self) + def remove_timers(self): try: if self.host_storage_timer: @@ -166,15 +190,20 @@ class vmmCreate(vmmGObjectUI): except: pass + def remove_conn(self): + if not self.conn: + return + + for signal in self.conn_signals: + self.conn.disconnect(signal) + self.conn_signals = [] + self.conn = None + def set_conn(self, newconn, force_validate=False): if self.conn == newconn and not force_validate: return - if self.conn: - for signal in self.conn_signals: - self.conn.disconnect(signal) - self.conn_signals = [] - + self.remove_conn() self.conn = newconn if self.conn: self.set_conn_state() diff --git a/src/virtManager/createinterface.py b/src/virtManager/createinterface.py index 77640b4cb..37fadd75b 100644 --- a/src/virtManager/createinterface.py +++ b/src/virtManager/createinterface.py @@ -153,6 +153,26 @@ class vmmCreateInterface(vmmGObjectUI): return 1 + def cleanup(self): + self.close() + + try: + self.conn = None + self.interface = None + + self.ip_config.destroy() + self.ip_config = None + + self.bridge_config.destroy() + self.bridge_config = None + + self.bond_config.destroy() + self.bond_config = None + except: + logging.exception("Error cleaning up addiface") + + vmmGObjectUI.cleanup(self) + ########################### # Initialization routines # ########################### diff --git a/src/virtManager/createnet.py b/src/virtManager/createnet.py index 8e79fb462..143f77fbc 100644 --- a/src/virtManager/createnet.py +++ b/src/virtManager/createnet.py @@ -75,6 +75,21 @@ class vmmCreateNetwork(vmmGObjectUI): self.reset_state() self.topwin.present() + def is_visible(self): + if self.topwin.flags() & gtk.VISIBLE: + return 1 + return 0 + + def close(self, ignore1=None, ignore2=None): + self.topwin.hide() + return 1 + + def cleanup(self): + self.close() + self.conn = None + + vmmGObjectUI.cleanup(self) + def set_initial_state(self): notebook = self.window.get_widget("create-pages") notebook.set_show_tabs(False) @@ -310,14 +325,6 @@ class vmmCreateNetwork(vmmGObjectUI): self.window.get_widget("create-finish").show() self.window.get_widget("create-finish").grab_focus() - def close(self, ignore1=None, ignore2=None): - self.topwin.hide() - return 1 - - def is_visible(self): - if self.topwin.flags() & gtk.VISIBLE: - return 1 - return 0 def finish(self, ignore=None): name = self.get_config_name() diff --git a/src/virtManager/createpool.py b/src/virtManager/createpool.py index f4aefae00..d03b6f610 100644 --- a/src/virtManager/createpool.py +++ b/src/virtManager/createpool.py @@ -111,6 +111,14 @@ class vmmCreatePool(vmmGObjectUI): self.topwin.hide() return 1 + def cleanup(self): + self.close() + + self.conn = None + self._pool = None + + vmmGObjectUI.cleanup(self) + def set_initial_state(self): self.window.get_widget("pool-pages").set_show_tabs(False) diff --git a/src/virtManager/delete.py b/src/virtManager/delete.py index d8d60d9b9..af26d80ce 100644 --- a/src/virtManager/delete.py +++ b/src/virtManager/delete.py @@ -83,6 +83,14 @@ class vmmDeleteDialog(vmmGObjectUI): self.conn = None return 1 + def cleanup(self): + self.close() + + self.vm = None + self.conn = None + + vmmGObjectUI.cleanup(self) + def reset_state(self): # Set VM name in title' diff --git a/src/virtManager/engine.py b/src/virtManager/engine.py index 50cbb0fbb..903156b58 100644 --- a/src/virtManager/engine.py +++ b/src/virtManager/engine.py @@ -45,10 +45,11 @@ from virtManager.create import vmmCreate from virtManager.host import vmmHost from virtManager.error import vmmErrorDialog from virtManager.systray import vmmSystray +import virtManager.uihelpers as uihelpers import virtManager.util as util # Enable this to get a report of leaked objects on app shutdown -debug_ref_leaks = False +debug_ref_leaks = True def default_uri(): tryuri = None @@ -246,8 +247,10 @@ class vmmEngine(vmmGObject): self.init_systray() - self.config.on_stats_update_interval_changed(self.reschedule_timer) - self.config.on_view_system_tray_changed(self.system_tray_changed) + self.add_gconf_handle( + self.config.on_stats_update_interval_changed(self.reschedule_timer)) + self.add_gconf_handle( + self.config.on_view_system_tray_changed(self.system_tray_changed)) self.schedule_timer() self.load_stored_uris() @@ -349,8 +352,6 @@ class vmmEngine(vmmGObject): def connect_to_uri(self, uri, readOnly=None, autoconnect=False, do_start=True): - self.windowConnect = None - try: conn = self._check_connection(uri) if not conn: @@ -368,10 +369,9 @@ class vmmEngine(vmmGObject): def _do_connect(self, src_ignore, uri): return self.connect_to_uri(uri) - def _connect_cancelled(self, connect_ignore): - self.windowConnect = None + def _connect_cancelled(self, src): if len(self.connections.keys()) == 0: - self.exit_app() + self.exit_app(src) def _do_vm_removed(self, connection, hvuri, vmuuid): @@ -393,10 +393,6 @@ class vmmEngine(vmmGObject): self.connections[hvuri]["windowDetails"][vmuuid].cleanup() del(self.connections[hvuri]["windowDetails"][vmuuid]) - if self.connections[hvuri]["windowHost"] is not None: - self.connections[hvuri]["windowHost"].close() - self.connections[hvuri]["windowHost"] = None - if (self.windowCreate and self.windowCreate.conn and self.windowCreate.conn.get_uri() == hvuri): @@ -461,18 +457,79 @@ class vmmEngine(vmmGObject): def decrement_window_counter(self): self.windows -= 1 logging.debug("window counter decremented to %s" % self.windows) + # Don't exit if system tray is enabled - if self.windows <= 0 and not self.systray.is_visible(): + if (self.windows <= 0 and + self.systray and + not self.systray.is_visible()): self.exit_app() - def exit_app(self, ignore_src=None): - conns = self.connections.values() - for conn in conns: - conn["connection"].close() - self.connections = {} + def cleanup(self): + try: + vmmGObject.cleanup(self) + uihelpers.cleanup() + self.err = None + + if self.timer != None: + gobject.source_remove(self.timer) + + if self.systray: + self.systray.cleanup() + self.systray = None + + self.get_manager() + if self.windowManager: + self.windowManager.cleanup() + self.windowManager = None + + if self.windowPreferences: + self.windowPreferences.cleanup() + self.windowPreferences = None + + if self.windowAbout: + self.windowAbout.cleanup() + self.windowAbout = None + + if self.windowConnect: + self.windowConnect.cleanup() + self.windowConnect = None + + if self.windowCreate: + self.windowCreate.cleanup() + self.windowCreate = None + + if self.windowMigrate: + self.windowMigrate.cleanup() + self.windowMigrate = None + + # Do this last, so any manually 'disconnected' signals + # take precedence over cleanup signal removal + for uri in self.connections: + self.cleanup_connection(uri) + self.connections = {} + except: + logging.exception("Error cleaning up engine") + + def exit_app(self, src=None): + if self.err is None: + # Already in cleanup + return + + self.cleanup() if debug_ref_leaks: - for name in self.config.get_objects(): + objs = self.config.get_objects() + if src and src.object_key in objs: + # Whatever UI initiates the app exit will always appear + # to leak + logging.debug("Exitting app from %s, skipping leak check" % + src.object_key) + objs.remove(src.object_key) + + # Engine will always appear to leak + objs.remove(self.object_key) + + for name in objs: logging.debug("Leaked %s" % name) logging.debug("Exiting app normally.") @@ -502,13 +559,28 @@ class vmmEngine(vmmGObject): return conn + def cleanup_connection(self, uri): + try: + if self.connections[uri]["windowHost"]: + self.connections[uri]["windowHost"].cleanup() + if self.connections[uri]["windowClone"]: + self.connections[uri]["windowClone"].cleanup() + + details = self.connections[uri]["windowDetails"] + for win in details.values(): + win.cleanup() + + self.connections[uri]["connection"].cleanup() + except: + logging.exception("Error cleaning up conn in engine") + + def remove_connection(self, uri): - conn = self.connections[uri]["connection"] - conn.close() - del self.connections[uri] + self.cleanup_connection(uri) + del(self.connections[uri]) self.emit("connection-removed", uri) - self.config.remove_connection(conn.get_uri()) + self.config.remove_connection(uri) def connect(self, name, callback, *args): handle_id = vmmGObject.connect(self, name, callback, *args) @@ -683,7 +755,8 @@ class vmmEngine(vmmGObject): def _do_show_manager(self, src): try: - self.get_manager().show() + manager = self.get_manager() + manager.show() except Exception, e: if not src: raise diff --git a/src/virtManager/host.py b/src/virtManager/host.py index 3efaa43f8..78270c95d 100644 --- a/src/virtManager/host.py +++ b/src/virtManager/host.py @@ -312,6 +312,43 @@ class vmmHost(vmmGObjectUI): self.engine.decrement_window_counter() return 1 + def cleanup(self): + self.close() + + try: + self.conn = None + self.engine = None + + + if self.addnet: + self.addnet.cleanup() + self.addnet = None + + if self.addpool: + self.addpool.cleanup() + self.addpool = None + + if self.addvol: + self.addvol.cleanup() + self.addvol = None + + if self.addinterface: + self.addinterface.cleanup() + self.addinterface = None + + self.volmenu.destroy() + self.volmenu = None + + self.cpu_usage_graph.destroy() + self.cpu_usage_graph = None + + self.memory_usage_graph.destroy() + self.memory_usage_graph = None + except: + logging.exception("Error cleaning up host dialog") + + vmmGObjectUI.cleanup(self) + def show_help(self, src_ignore): self.emit("action-show-help", "virt-manager-host-window") diff --git a/src/virtManager/manager.py b/src/virtManager/manager.py index 4055e3374..021d156b8 100644 --- a/src/virtManager/manager.py +++ b/src/virtManager/manager.py @@ -129,17 +129,18 @@ class vmmManager(vmmGObjectUI): self.topwin.set_default_size(w or 550, h or 550) self.prev_position = None - self.init_vmlist() - self.init_stats() - self.init_toolbar() - self.vmmenu = gtk.Menu() self.vmmenushutdown = gtk.Menu() self.vmmenu_items = {} self.vmmenushutdown_items = {} self.connmenu = gtk.Menu() self.connmenu_items = {} - self.init_context_menus() + + # There seem to be ref counting issues with calling + # list.get_column, so avoid it + self.diskcol = None + self.netcol = None + self.cpucol = None self.window.signal_autoconnect({ "on_menu_view_cpu_usage_activate": (self.toggle_stats_visible, @@ -171,7 +172,12 @@ class vmmManager(vmmGObjectUI): "on_menu_edit_preferences_activate": self.show_preferences, "on_menu_help_about_activate": self.show_about, "on_menu_help_activate": self.show_help, - }) + }) + + self.init_vmlist() + self.init_stats() + self.init_toolbar() + self.init_context_menus() # XXX: Help docs useless/out of date self.window.get_widget("menu_help").hide() @@ -223,6 +229,38 @@ class vmmManager(vmmGObjectUI): self.engine.decrement_window_counter() return 1 + + def cleanup(self): + self.close() + + try: + self.engine = None + self.rows = None + + self.diskcol = None + self.cpucol = None + self.netcol = None + + if self.delete_dialog: + self.delete_dialog.cleanup() + self.delete_dialog = None + + self.vmmenu.destroy() + self.vmmenu = None + self.vmmenu_items = None + self.vmmenushutdown.destroy() + self.vmmenushutdown = None + self.vmmenushutdown_items = None + self.connmenu.destroy() + self.connmenu = None + self.connmenu_items = None + + except: + logging.exception("Error cleaning up manager") + + vmmGObjectUI.cleanup(self) + + def is_visible(self): return bool(self.topwin.flags() & gtk.VISIBLE) @@ -235,19 +273,25 @@ class vmmManager(vmmGObjectUI): ################ def init_stats(self): - self.config.on_vmlist_cpu_usage_visible_changed( - self.toggle_cpu_usage_visible_widget) - self.config.on_vmlist_disk_io_visible_changed( - self.toggle_disk_io_visible_widget) - self.config.on_vmlist_network_traffic_visible_changed( - self.toggle_network_traffic_visible_widget) + self.add_gconf_handle( + self.config.on_vmlist_cpu_usage_visible_changed( + self.toggle_cpu_usage_visible_widget)) + self.add_gconf_handle( + self.config.on_vmlist_disk_io_visible_changed( + self.toggle_disk_io_visible_widget)) + self.add_gconf_handle( + self.config.on_vmlist_network_traffic_visible_changed( + self.toggle_network_traffic_visible_widget)) # Register callbacks with the global stats enable/disable values # that disable the associated vmlist widgets if reporting is disabled - self.config.on_stats_enable_disk_poll_changed(self.enable_polling, - cfg.STATS_DISK) - self.config.on_stats_enable_net_poll_changed(self.enable_polling, - cfg.STATS_NETWORK) + self.add_gconf_handle( + self.config.on_stats_enable_disk_poll_changed(self.enable_polling, + cfg.STATS_DISK)) + self.add_gconf_handle( + self.config.on_stats_enable_net_poll_changed(self.enable_polling, + cfg.STATS_NETWORK)) + self.window.get_widget("menu_view_stats_cpu").set_active( self.config.is_vmlist_cpu_usage_visible()) @@ -441,6 +485,9 @@ class vmmManager(vmmGObjectUI): model.set_sort_column_id(COL_NAME, gtk.SORT_ASCENDING) + self.diskcol = diskIOCol + self.netcol = networkTrafficCol + self.cpucol = cpuUsageCol ################## # Helper methods # @@ -1090,19 +1137,14 @@ class vmmManager(vmmGObjectUI): widget.set_label(current_text) def toggle_network_traffic_visible_widget(self, *ignore): - vmlist = self.window.get_widget("vm-list") - col = vmlist.get_column(COL_NETWORK) - col.set_visible(self.config.is_vmlist_network_traffic_visible()) + self.netcol.set_visible( + self.config.is_vmlist_network_traffic_visible()) def toggle_disk_io_visible_widget(self, *ignore): - vmlist = self.window.get_widget("vm-list") - col = vmlist.get_column(COL_DISK) - col.set_visible(self.config.is_vmlist_disk_io_visible()) + self.diskcol.set_visible(self.config.is_vmlist_disk_io_visible()) def toggle_cpu_usage_visible_widget(self, *ignore): - vmlist = self.window.get_widget("vm-list") - col = vmlist.get_column(COL_CPU) - col.set_visible(self.config.is_vmlist_cpu_usage_visible()) + self.cpucol.set_visible(self.config.is_vmlist_cpu_usage_visible()) def toggle_stats_visible(self, src, stats_id): visible = src.get_active() diff --git a/src/virtManager/migrate.py b/src/virtManager/migrate.py index 9894e169f..fabd7c822 100644 --- a/src/virtManager/migrate.py +++ b/src/virtManager/migrate.py @@ -91,6 +91,19 @@ class vmmMigrateDialog(vmmGObjectUI): self.topwin.hide() return 1 + def cleanup(self): + self.close() + + self.vm = None + self.conn = None + self.engine = None + self.destconn_rows = None + + # Not sure why we need to do this manually, but it matters + self.window.get_widget("migrate-dest").get_model().clear() + + vmmGObjectUI.cleanup(self) + def init_state(self): # [hostname, conn, can_migrate, tooltip] dest_model = gtk.ListStore(str, object, bool, str) diff --git a/src/virtManager/preferences.py b/src/virtManager/preferences.py index be3bc66ee..4a8dd995d 100644 --- a/src/virtManager/preferences.py +++ b/src/virtManager/preferences.py @@ -35,23 +35,23 @@ class vmmPreferences(vmmGObjectUI): def __init__(self): vmmGObjectUI.__init__(self, "vmm-preferences.glade", "vmm-preferences") - self.config.on_view_system_tray_changed(self.refresh_view_system_tray) - self.config.on_console_popup_changed(self.refresh_console_popup) - self.config.on_console_accels_changed(self.refresh_console_accels) - self.config.on_console_scaling_changed(self.refresh_console_scaling) - self.config.on_stats_update_interval_changed(self.refresh_update_interval) - self.config.on_stats_history_length_changed(self.refresh_history_length) - self.config.on_sound_local_changed(self.refresh_sound_local) - self.config.on_sound_remote_changed(self.refresh_sound_remote) - self.config.on_graphics_type_changed(self.refresh_graphics_type) - self.config.on_stats_enable_disk_poll_changed(self.refresh_disk_poll) - self.config.on_stats_enable_net_poll_changed(self.refresh_net_poll) + self.add_gconf_handle(self.config.on_view_system_tray_changed(self.refresh_view_system_tray)) + self.add_gconf_handle(self.config.on_console_popup_changed(self.refresh_console_popup)) + self.add_gconf_handle(self.config.on_console_accels_changed(self.refresh_console_accels)) + self.add_gconf_handle(self.config.on_console_scaling_changed(self.refresh_console_scaling)) + self.add_gconf_handle(self.config.on_stats_update_interval_changed(self.refresh_update_interval)) + self.add_gconf_handle(self.config.on_stats_history_length_changed(self.refresh_history_length)) + self.add_gconf_handle(self.config.on_sound_local_changed(self.refresh_sound_local)) + self.add_gconf_handle(self.config.on_sound_remote_changed(self.refresh_sound_remote)) + self.add_gconf_handle(self.config.on_graphics_type_changed(self.refresh_graphics_type)) + self.add_gconf_handle(self.config.on_stats_enable_disk_poll_changed(self.refresh_disk_poll)) + self.add_gconf_handle(self.config.on_stats_enable_net_poll_changed(self.refresh_net_poll)) - self.config.on_confirm_forcepoweroff_changed(self.refresh_confirm_forcepoweroff) - self.config.on_confirm_poweroff_changed(self.refresh_confirm_poweroff) - self.config.on_confirm_pause_changed(self.refresh_confirm_pause) - self.config.on_confirm_removedev_changed(self.refresh_confirm_removedev) - self.config.on_confirm_interface_changed(self.refresh_confirm_interface) + self.add_gconf_handle(self.config.on_confirm_forcepoweroff_changed(self.refresh_confirm_forcepoweroff)) + self.add_gconf_handle(self.config.on_confirm_poweroff_changed(self.refresh_confirm_poweroff)) + self.add_gconf_handle(self.config.on_confirm_pause_changed(self.refresh_confirm_pause)) + self.add_gconf_handle(self.config.on_confirm_removedev_changed(self.refresh_confirm_removedev)) + self.add_gconf_handle(self.config.on_confirm_interface_changed(self.refresh_confirm_interface)) self.refresh_view_system_tray() self.refresh_update_interval() diff --git a/src/virtManager/systray.py b/src/virtManager/systray.py index 27948aac5..56e3d334b 100644 --- a/src/virtManager/systray.py +++ b/src/virtManager/systray.py @@ -18,6 +18,8 @@ # MA 02110-1301 USA. # +import logging + import gobject import gtk @@ -90,7 +92,9 @@ class vmmSystray(vmmGObject): self.init_systray_menu() - self.config.on_view_system_tray_changed(self.show_systray) + self.add_gconf_handle( + self.config.on_view_system_tray_changed(self.show_systray)) + self.show_systray() def is_visible(self): @@ -102,6 +106,20 @@ class vmmSystray(vmmGObject): self.systray_icon and self.systray_icon.is_embedded()) + def cleanup(self): + vmmGObject.cleanup(self) + + try: + self.err = None + + if self.systray_menu: + self.systray_menu.destroy() + self.systray_menu = None + + self.systray_icon = None + except: + logging.exception("Error cleaning up systray") + # Initialization routines def init_systray_menu(self): diff --git a/src/virtManager/uihelpers.py b/src/virtManager/uihelpers.py index 70cb5fc08..b6ff5b68a 100644 --- a/src/virtManager/uihelpers.py +++ b/src/virtManager/uihelpers.py @@ -50,6 +50,10 @@ def set_error_parent(parent): err_dial.set_parent(parent) err_dial = err_dial +def cleanup(): + global err_dial + err_dial = None + ############################################################ # Helpers for shared storage UI between create/addhardware # ############################################################