%PDF- %PDF-
Direktori : /usr/share/virt-manager/virtManager/ |
Current File : //usr/share/virt-manager/virtManager/migrate.py |
# # Copyright (C) 2009, 2013 Red Hat, Inc. # Copyright (C) 2009 Cole Robinson <crobinso@redhat.com> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301 USA. # import logging import traceback from gi.repository import Gdk from gi.repository import Gtk from gi.repository import Pango from virtinst import util from . import uiutil from .baseclass import vmmGObjectUI from .asyncjob import vmmAsyncJob from .domain import vmmDomain NUM_COLS = 3 (COL_LABEL, COL_URI, COL_CAN_MIGRATE) = range(NUM_COLS) class vmmMigrateDialog(vmmGObjectUI): def __init__(self, engine): vmmGObjectUI.__init__(self, "migrate.ui", "vmm-migrate") self.vm = None self.conn = None self._conns = {} self.builder.connect_signals({ "on_vmm_migrate_delete_event": self._delete_event, "on_migrate_cancel_clicked": self._cancel_clicked, "on_migrate_finish_clicked": self._finish_clicked, "on_migrate_dest_changed": self._destconn_changed, "on_migrate_set_address_toggled": self._set_address_toggled, "on_migrate_set_port_toggled": self._set_port_toggled, "on_migrate_mode_changed": self._mode_changed, }) self.bind_escape_key_close() self._init_state(engine) def _cleanup(self): self.vm = None self.conn = None self._conns = None ############## # Public API # ############## def show(self, parent, vm): logging.debug("Showing migrate wizard") self.vm = vm self.conn = vm.conn self._reset_state() self.topwin.set_transient_for(parent) self.topwin.present() def close(self, ignore1=None, ignore2=None): logging.debug("Closing migrate wizard") self.topwin.hide() return 1 ################ # Init helpers # ################ def _init_state(self, engine): blue = Gdk.color_parse("#0072A8") self.widget("header").modify_bg(Gtk.StateType.NORMAL, blue) # Connection combo cols = [None] * NUM_COLS cols[COL_LABEL] = str cols[COL_URI] = str cols[COL_CAN_MIGRATE] = bool model = Gtk.ListStore(*cols) combo = self.widget("migrate-dest") combo.set_model(model) text = uiutil.init_combo_text_column(combo, COL_LABEL) text.set_property("ellipsize", Pango.EllipsizeMode.MIDDLE) text.set_property("width-chars", 30) combo.add_attribute(text, 'sensitive', COL_CAN_MIGRATE) model.set_sort_column_id(COL_LABEL, Gtk.SortType.ASCENDING) def _sorter(model, iter1, iter2, ignore): def _cmp(a, b): return ((a > b) - (a < b)) row1 = model[iter1] row2 = model[iter2] if row1[COL_URI] is None: return -1 if row2[COL_URI] is None: return 1 return _cmp(row1[COL_LABEL], row2[COL_LABEL]) model.set_sort_func(COL_LABEL, _sorter) # Mode combo combo = self.widget("migrate-mode") # label, is_tunnel model = Gtk.ListStore(str, bool) model.append([_("Direct"), False]) model.append([_("Tunnelled"), True]) combo.set_model(model) uiutil.init_combo_text_column(combo, 0) # Hook up signals to get connection listing engine.connect("conn-added", self._conn_added_cb) engine.connect("conn-removed", self._conn_removed_cb) self.widget("migrate-dest").emit("changed") self.widget("migrate-mode").set_tooltip_text( self.widget("migrate-mode-label").get_tooltip_text()) self.widget("migrate-unsafe").set_tooltip_text( self.widget("migrate-unsafe-label").get_tooltip_text()) self.widget("migrate-temporary").set_tooltip_text( self.widget("migrate-temporary-label").get_tooltip_text()) def _reset_state(self): title_str = ("<span size='large' color='white'>%s '%s'</span>" % (_("Migrate"), util.xml_escape(self.vm.get_name()))) self.widget("header-label").set_markup(title_str) self.widget("migrate-advanced-expander").set_expanded(False) self.widget("migrate-cancel").grab_focus() self.widget("config-box").set_visible(True) hostname = self.conn.libvirt_gethostname() srctext = "%s (%s)" % (hostname, self.conn.get_pretty_desc()) self.widget("migrate-label-name").set_text(self.vm.get_name_or_title()) self.widget("migrate-label-src").set_text(srctext) self.widget("migrate-label-src").set_tooltip_text(self.conn.get_uri()) self.widget("migrate-set-address").set_active(True) self.widget("migrate-set-address").emit("toggled") self.widget("migrate-set-port").set_active(True) self.widget("migrate-mode").set_active(0) self.widget("migrate-unsafe").set_active(False) self.widget("migrate-temporary").set_active(False) if self.conn.is_xen(): # Default xen port is 8002 self.widget("migrate-port").set_value(8002) else: # QEMU migrate port range is 49152+64 self.widget("migrate-port").set_value(49152) self._populate_destconn() ############# # Listeners # ############# def _delete_event(self, ignore1, ignore2): self.close() return 1 def _cancel_clicked(self, src): ignore = src self.close() def _finish_clicked(self, src): ignore = src self._finish() def _destconn_changed(self, src): row = uiutil.get_list_selected_row(src) if not row: return can_migrate = row and row[COL_CAN_MIGRATE] or False uri = row[COL_URI] tooltip = "" if not can_migrate: tooltip = _("A valid destination connection must be selected.") self.widget("config-box").set_visible(can_migrate) self.widget("migrate-finish").set_sensitive(can_migrate) self.widget("migrate-finish").set_tooltip_text(tooltip) address = "" address_warning = "" tunnel_warning = "" tunnel_uri = "" if can_migrate and uri in self._conns: destconn = self._conns[uri] tunnel_uri = destconn.get_uri() if not destconn.is_remote(): tunnel_warning = _("A remotely accessible libvirt URI " "is required for tunneled migration, but the " "selected connection is a local URI. Libvirt will " "reject this unless you add a transport.") tunnel_warning = ("<span size='small'>%s</span>" % tunnel_warning) address = destconn.libvirt_gethostname() if self._is_localhost(address): address_warning = _("The destination's hostname is " "'localhost', which will be rejected by libvirt. " "You must configure the destination to have a valid " "publicly accessible hostname.") address_warning = ("<span size='small'>%s</span>" % address_warning) self.widget("migrate-address").set_text(address) uiutil.set_grid_row_visible( self.widget("migrate-address-warning-box"), bool(address_warning)) self.widget("migrate-address-warning-label").set_markup(address_warning) self.widget("migrate-tunnel-uri").set_text(tunnel_uri) uiutil.set_grid_row_visible( self.widget("migrate-tunnel-warning-box"), bool(tunnel_warning)) self.widget("migrate-tunnel-warning-label").set_markup(tunnel_warning) def _set_address_toggled(self, src): enable = src.get_active() self.widget("migrate-address").set_visible(enable) self.widget("migrate-address-label").set_visible(not enable) port_enable = self.widget("migrate-set-port").get_active() self.widget("migrate-set-port").set_active(enable and port_enable) self.widget("migrate-set-port").emit("toggled") def _set_port_toggled(self, src): enable = src.get_active() self.widget("migrate-port").set_visible(enable) self.widget("migrate-port-label").set_visible(not enable) def _is_tunnel_selected(self): return uiutil.get_list_selection(self.widget("migrate-mode"), column=1) def _mode_changed(self, src): ignore = src is_tunnel = self._is_tunnel_selected() self.widget("migrate-direct-box").set_visible(not is_tunnel) self.widget("migrate-tunnel-box").set_visible(is_tunnel) def _conn_added_cb(self, engine, conn): ignore = engine self._conns[conn.get_uri()] = conn def _conn_removed_cb(self, engine, uri): ignore = engine del(self._conns[uri]) ########################### # destconn combo handling # ########################### def _is_localhost(self, addr): return not addr or addr.startswith("localhost") def _build_dest_row(self, destconn): driver = self.conn.get_driver() origuri = self.conn.get_uri() can_migrate = False desc = destconn.get_pretty_desc() reason = "" desturi = destconn.get_uri() if destconn.get_driver() != driver: reason = _("Hypervisors do not match") elif destconn.is_disconnected(): reason = _("Disconnected") elif destconn.get_uri() == origuri: reason = _("Same connection") elif destconn.is_active(): can_migrate = True if reason: desc = "%s (%s)" % (desc, reason) return [desc, desturi, can_migrate] def _populate_destconn(self): combo = self.widget("migrate-dest") model = combo.get_model() model.clear() rows = [] for conn in self._conns.values(): rows.append(self._build_dest_row(conn)) if not any([row[COL_CAN_MIGRATE] for row in rows]): rows.insert(0, [_("No usable connections available."), None, False]) for row in rows: model.append(row) combo.set_active(0) for idx, row in enumerate(model): if row[COL_CAN_MIGRATE]: combo.set_active(idx) break #################### # migrate handling # #################### def _build_regular_migrate_uri(self): address = None if self.widget("migrate-address").get_visible(): address = self.widget("migrate-address").get_text() port = None if self.widget("migrate-port").get_visible(): port = int(self.widget("migrate-port").get_value()) if not address: return if self.conn.is_xen(): uri = "%s" % address else: uri = "tcp:%s" % address if port: uri += ":%s" % port return uri def _finish_cb(self, error, details, destconn): self.reset_finish_cursor() if error: error = _("Unable to migrate guest: %s") % error self.err.show_err(error, details=details) else: self.conn.schedule_priority_tick(pollvm=True) destconn.schedule_priority_tick(pollvm=True) self.close() def _finish(self): try: row = uiutil.get_list_selected_row(self.widget("migrate-dest")) destlabel = row[COL_LABEL] destconn = self._conns.get(row[COL_URI]) tunnel = self._is_tunnel_selected() unsafe = self.widget("migrate-unsafe").get_active() temporary = self.widget("migrate-temporary").get_active() if tunnel: uri = self.widget("migrate-tunnel-uri").get_text() else: uri = self._build_regular_migrate_uri() except Exception as e: details = "".join(traceback.format_exc()) self.err.show_err((_("Uncaught error validating input: %s") % str(e)), details=details) return self.set_finish_cursor() cancel_cb = None if self.vm.getjobinfo_supported: cancel_cb = (self._cancel_migration, self.vm) if uri: destlabel += " " + uri progWin = vmmAsyncJob( self._async_migrate, [self.vm, destconn, uri, tunnel, unsafe, temporary], self._finish_cb, [destconn], _("Migrating VM '%s'") % self.vm.get_name(), (_("Migrating VM '%s' to %s. This may take a while.") % (self.vm.get_name(), destlabel)), self.topwin, cancel_cb=cancel_cb) progWin.run() def _cancel_migration(self, asyncjob, vm): logging.debug("Cancelling migrate job") if not vm: return try: vm.abort_job() except Exception as e: logging.exception("Error cancelling migrate job") asyncjob.show_warning(_("Error cancelling migrate job: %s") % e) return asyncjob.job_canceled = True return def _async_migrate(self, asyncjob, origvm, origdconn, migrate_uri, tunnel, unsafe, temporary): meter = asyncjob.get_meter() srcconn = origvm.conn dstconn = origdconn vminst = srcconn.get_backend().lookupByName(origvm.get_name()) vm = vmmDomain(srcconn, vminst, vminst.UUID()) logging.debug("Migrating vm=%s from %s to %s", vm.get_name(), srcconn.get_uri(), dstconn.get_uri()) vm.migrate(dstconn, migrate_uri, tunnel, unsafe, temporary, meter=meter)