diff --git a/src/virtManager/details.py b/src/virtManager/details.py index 9444b0925..e582e1122 100644 --- a/src/virtManager/details.py +++ b/src/virtManager/details.py @@ -95,6 +95,8 @@ class vmmDetails(gobject.GObject): gobject.TYPE_NONE, []), "action-view-manager": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, []), + "action-migrate-domain": (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, (str,str,str)), } @@ -131,6 +133,13 @@ class vmmDetails(gobject.GObject): menu = gtk.Menu() self.window.get_widget("control-shutdown").set_menu(menu) + self.migrate_menu_items = {} + + self.migrate_menu_items["(None)"] = gtk.ImageMenuItem(_("(None)")) + self.migrate_menu_items["(None)"].show() + self.migrate_menu_items["(None)"].set_sensitive(False) + self.window.get_widget("details-menu-migrate_menu").add(self.migrate_menu_items["(None)"]) + rebootimg = gtk.Image() rebootimg.set_from_pixbuf(gtk.gdk.pixbuf_new_from_file_at_size(self.config.get_icon_dir() + "/icon_shutdown.png", 18, 18)) shutdownimg = gtk.Image() @@ -661,6 +670,32 @@ class vmmDetails(gobject.GObject): def control_vm_destroy(self, src): self.emit("action-destroy-domain", self.vm.get_connection().get_uri(), self.vm.get_uuid()) + def control_vm_migrate(self, src): + # get selected submenu(destination hostname) + hostname = self.window.get_widget("details-menu-migrate_menu").get_active().get_image().get_stock()[0] + for key in self.engine.connections.keys(): + if self.engine.get_connection(key).get_hostname() == hostname: + host_uri = key + break + self.emit("action-migrate-domain", self.vm.get_connection().get_uri(), self.vm.get_uuid(), host_uri) + + def set_migrate_menu(self): + menu = self.window.get_widget("details-menu-migrate_menu") + # clear migrate-submenu + for submenu_item in menu.get_children(): + submenu_item_name = submenu_item.get_image().get_stock()[0] + menu.remove(self.migrate_menu_items[submenu_item_name]) + + available_migrate_hostnames = self.engine.get_available_migrate_hostnames() + if len(available_migrate_hostnames) == 0: + menu.add(self.migrate_menu_items["(None)"]) + else: + for hostname in available_migrate_hostnames.values(): + self.migrate_menu_items[hostname] = gtk.ImageMenuItem(hostname) + self.migrate_menu_items[hostname].show() + self.migrate_menu_items[hostname].connect("activate", self.control_vm_migrate) + menu.add(self.migrate_menu_items[hostname]) + def set_pause_widget_states(self, state): try: self.ignorePause = True @@ -707,6 +742,13 @@ class vmmDetails(gobject.GObject): self.window.get_widget("details-menu-shutdown").set_sensitive(True) self.window.get_widget("details-menu-save").set_sensitive(True) + # Currently, the condition that "Migrate" become insensitive is only "readonly". + if vm.is_read_only(): + self.window.get_widget("details-menu-migrate").set_sensitive(False) + else: + self.window.get_widget("details-menu-migrate").set_sensitive(True) + self.set_migrate_menu() + if status in [ libvirt.VIR_DOMAIN_SHUTOFF ,libvirt.VIR_DOMAIN_CRASHED ]: if self.window.get_widget("console-pages").get_current_page() != PAGE_UNAVAILABLE: self.vncViewer.close() diff --git a/src/virtManager/domain.py b/src/virtManager/domain.py index 98b998613..de9f9a8dc 100644 --- a/src/virtManager/domain.py +++ b/src/virtManager/domain.py @@ -1103,4 +1103,10 @@ class vmmDomain(gobject.GObject): self._disk_io = self._sample_disk_io_dummy + def migrate(self, dictcon): + flags = 0 + if self.lastStatus == libvirt.VIR_DOMAIN_RUNNING: + flags = libvirt.VIR_MIGRATE_LIVE + self.vm.migrate(self.connection.vmm, flags, None, dictcon.get_short_hostname(), 0) + gobject.type_register(vmmDomain) diff --git a/src/virtManager/engine.py b/src/virtManager/engine.py index 7a3254e6c..24d628da2 100644 --- a/src/virtManager/engine.py +++ b/src/virtManager/engine.py @@ -202,6 +202,8 @@ class vmmEngine(gobject.GObject): self.shutdown_domain(src, uri, uuid) def _do_reboot_domain(self, src, uri, uuid): self.reboot_domain(src, uri, uuid) + def _do_migrate_domain(self, src, uri, uuid, desturi): + self.migrate_domain(uri, uuid, desturi) def _do_exit_app(self, src): self.exit_app() @@ -279,6 +281,7 @@ class vmmEngine(gobject.GObject): details.connect("action-reboot-domain", self._do_reboot_domain) details.connect("action-exit-app", self._do_exit_app) details.connect("action-view-manager", self._do_show_manager) + details.connect("action-migrate-domain", self._do_migrate_domain) except Exception, e: self.err.show_err(_("Error bringing up domain details: %s") % str(e), @@ -296,6 +299,7 @@ class vmmEngine(gobject.GObject): self.windowManager.connect("action-shutdown-domain", self._do_shutdown_domain) self.windowManager.connect("action-reboot-domain", self._do_reboot_domain) self.windowManager.connect("action-destroy-domain", self._do_destroy_domain) + self.windowManager.connect("action-migrate-domain", self._do_migrate_domain) self.windowManager.connect("action-show-console", self._do_show_console) self.windowManager.connect("action-show-details", self._do_show_details) self.windowManager.connect("action-show-preferences", self._do_show_preferences) @@ -529,6 +533,64 @@ class vmmEngine(gobject.GObject): else: logging.warning("Reboot requested, but machine is already shutting down / shutoff") + def migrate_domain(self, uri, uuid, desturi): + conn = self.get_connection(uri, False) + vm = conn.get_vm(uuid) + destconn = self.get_connection(desturi, False) + resp = self.err.yes_no(_("%s will be migrated from %s to %s, are you sure?") % \ + (vm.get_name(), conn.get_hostname(), destconn.get_hostname())) + if resp: + migrate_progress = None + try: + # show progress dialog + migrate_progress = self.get_migrate_progress(vm.get_name(), conn.get_short_hostname(), destconn.get_short_hostname()) + migrate_progress.show() + while gtk.events_pending(): + gtk.main_iteration() + # call virDomainMigrate + vm.migrate(destconn) + # close progress dialog + migrate_progress.destroy() + except Exception, e: + migrate_progress.destroy() + self.err.show_err(_("Error migrating domain: %s") % str(e), + "".join(traceback.format_exc())) + self.windowManager.conn_refresh_resources(conn) + self.windowManager.conn_refresh_resources(destconn) + + def get_migrate_progress(self, vmname, hostname, desthostname): + migrate_progress = None + migrate_progress = gtk.MessageDialog(None, \ + gtk.DIALOG_DESTROY_WITH_PARENT, \ + gtk.MESSAGE_INFO, \ + gtk.BUTTONS_NONE, \ + _("%s will be migrated from %s to %s." % \ + (vmname, hostname, desthostname))) + migrate_progress.set_title(" ") + return migrate_progress + + def get_available_migrate_hostnames(self): + hostname = self.windowManager.current_connection().get_hostname() + driver = self.windowManager.current_connection().get_driver() + available_migrate_hostnames = {} + + # 1. connected(ACTIVE, INACTIVE) host + for key in self.connections.keys(): + if self.connections[key].has_key("connection") is True \ + and (self.get_connection(key).get_state() == vmmConnection.STATE_ACTIVE or self.get_connection(key).get_state() == vmmConnection.STATE_INACTIVE): + available_migrate_hostnames[key] = self.get_connection(key).get_hostname() + + # 2. remove source host + for key in available_migrate_hostnames.keys(): + if available_migrate_hostnames[key] == hostname: + del available_migrate_hostnames[key] + + # 3. remove a different host of hypervisor + for key in available_migrate_hostnames.keys(): + if self.get_connection(key).get_driver() != driver: + del available_migrate_hostnames[key] + + return available_migrate_hostnames gobject.type_register(vmmEngine) diff --git a/src/virtManager/manager.py b/src/virtManager/manager.py index 86bb1bfee..d9f39de7d 100644 --- a/src/virtManager/manager.py +++ b/src/virtManager/manager.py @@ -105,6 +105,8 @@ class vmmManager(gobject.GObject): gobject.TYPE_NONE, [str]), "action-show-help": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, [str]), + "action-migrate-domain": (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, (str,str,str)), "action-exit-app": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, []),} @@ -156,6 +158,8 @@ class vmmManager(gobject.GObject): self.vmmenushutdown = gtk.Menu() self.vmmenu_items = {} self.vmmenushutdown_items = {} + self.vmmenumigrate = gtk.Menu() + self.vmmenumigrate_items = {} self.vmmenu_items["run"] = gtk.ImageMenuItem("_Run") self.vmmenu_items["run"].set_image(self.vmmenu_icons["run"]) @@ -200,9 +204,23 @@ class vmmManager(gobject.GObject): self.vmmenushutdown_items["forcepoweroff"].connect("activate", self.destroy_vm) self.vmmenushutdown.add(self.vmmenushutdown_items["forcepoweroff"]) - self.vmmenu_items["hsep"] = gtk.SeparatorMenuItem() - self.vmmenu_items["hsep"].show(); - self.vmmenu.add(self.vmmenu_items["hsep"]) + self.vmmenu_items["hsep1"] = gtk.SeparatorMenuItem() + self.vmmenu_items["hsep1"].show(); + self.vmmenu.add(self.vmmenu_items["hsep1"]) + + self.vmmenu_items["migrate"] = gtk.ImageMenuItem("_Migrate") + self.vmmenu_items["migrate"].set_submenu(self.vmmenumigrate) + self.vmmenu_items["migrate"].show() + self.vmmenu.add(self.vmmenu_items["migrate"]) + + self.vmmenumigrate_items["(None)"] = gtk.ImageMenuItem(_("(None)")) + self.vmmenumigrate_items["(None)"].show() + self.vmmenumigrate_items["(None)"].set_sensitive(False) + self.vmmenumigrate.add(self.vmmenumigrate_items["(None)"]) + + self.vmmenu_items["hsep2"] = gtk.SeparatorMenuItem() + self.vmmenu_items["hsep2"].show(); + self.vmmenu.add(self.vmmenu_items["hsep2"]) self.vmmenu_items["open"] = gtk.ImageMenuItem(gtk.STOCK_OPEN) self.vmmenu_items["open"].connect("activate", self.open_vm_console) @@ -718,6 +736,7 @@ class vmmManager(gobject.GObject): self.vmmenu_items["resume"].hide() self.vmmenu_items["resume"].set_sensitive(False) self.vmmenu_items["shutdown"].set_sensitive(False) + self.vmmenu_items["migrate"].set_sensitive(False) else: if vm.status() == libvirt.VIR_DOMAIN_SHUTOFF: self.vmmenu_items["run"].set_sensitive(True) @@ -726,6 +745,8 @@ class vmmManager(gobject.GObject): self.vmmenu_items["resume"].hide() self.vmmenu_items["resume"].set_sensitive(False) self.vmmenu_items["shutdown"].set_sensitive(False) + self.vmmenu_items["migrate"].set_sensitive(True) + self.set_migrate_submenu() elif vm.status() == libvirt.VIR_DOMAIN_RUNNING: self.vmmenu_items["run"].set_sensitive(False) self.vmmenu_items["pause"].set_sensitive(True) @@ -733,6 +754,8 @@ class vmmManager(gobject.GObject): self.vmmenu_items["resume"].hide() self.vmmenu_items["resume"].set_sensitive(False) self.vmmenu_items["shutdown"].set_sensitive(True) + self.vmmenu_items["migrate"].set_sensitive(True) + self.set_migrate_submenu() elif vm.status() == libvirt.VIR_DOMAIN_PAUSED: self.vmmenu_items["run"].set_sensitive(False) self.vmmenu_items["pause"].hide() @@ -740,6 +763,8 @@ class vmmManager(gobject.GObject): self.vmmenu_items["resume"].show() self.vmmenu_items["resume"].set_sensitive(True) self.vmmenu_items["shutdown"].set_sensitive(True) + self.vmmenu_items["migrate"].set_sensitive(True) + self.set_migrate_submenu() self.vmmenu.popup(None, None, None, 0, event.time) return False else: @@ -1052,6 +1077,33 @@ class vmmManager(gobject.GObject): if vm is not None: self.emit("action-resume-domain", vm.get_connection().get_uri(), vm.get_uuid()) + def migrate(self, ignore): + vm = self.current_vm() + # get selected submenu(destination hostname) + hostname = self.vmmenumigrate.get_active().get_image().get_stock()[0] + for key in self.engine.connections.keys(): + if self.engine.get_connection(key).get_hostname() == hostname: + host_uri = key + break + if vm is not None: + self.emit("action-migrate-domain", vm.get_connection().get_uri(), vm.get_uuid(), host_uri) + + def set_migrate_submenu(self): + # clear migrate-submenu + for submenu_item in self.vmmenumigrate.get_children(): + submenu_item_name = submenu_item.get_image().get_stock()[0] + self.vmmenumigrate.remove(self.vmmenumigrate_items[submenu_item_name]) + + available_migrate_hostnames = self.engine.get_available_migrate_hostnames() + if len(available_migrate_hostnames) == 0: + self.vmmenumigrate.add(self.vmmenumigrate_items["(None)"]) + else: + for hostname in available_migrate_hostnames.values(): + self.vmmenumigrate_items[hostname] = gtk.ImageMenuItem(hostname) + self.vmmenumigrate_items[hostname].show() + self.vmmenumigrate_items[hostname].connect("activate", self.migrate) + self.vmmenumigrate.add(self.vmmenumigrate_items[hostname]) + def _add_connection(self, engine, conn): conn.connect("vm-added", self.vm_added) conn.connect("vm-removed", self.vm_removed) diff --git a/src/vmm-details.glade b/src/vmm-details.glade index bdaa5aa9b..7bb0ac4a8 100644 --- a/src/vmm-details.glade +++ b/src/vmm-details.glade @@ -166,6 +166,27 @@ + + + True + _Migrate + True + + + + + + + + + + + + + True + + + True