migrate: Storage connection list in the migrate dialog, not migrate menu.

This commit is contained in:
Cole Robinson
2009-11-18 13:51:36 -05:00
parent 64e5998ded
commit 164d7eebd1
6 changed files with 200 additions and 139 deletions

View File

@@ -95,7 +95,7 @@ class vmmDetails(gobject.GObject):
"action-view-manager": (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE, []),
"action-migrate-domain": (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE, (str,str,str)),
gobject.TYPE_NONE, (str,str)),
"action-clone-domain": (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE, (str,str)),
}
@@ -161,8 +161,8 @@ class vmmDetails(gobject.GObject):
"on_details_menu_save_activate": self.control_vm_save_domain,
"on_details_menu_destroy_activate": self.control_vm_destroy,
"on_details_menu_pause_activate": self.control_vm_pause,
"on_details_menu_migrate_activate": self.populate_migrate_menu,
"on_details_menu_clone_activate": self.control_vm_clone,
"on_details_menu_migrate_activate": self.control_vm_migrate,
"on_details_menu_screenshot_activate": self.control_vm_screenshot,
"on_details_menu_graphics_activate": self.control_vm_console,
"on_details_menu_view_toolbar_activate": self.toggle_toolbar,
@@ -513,11 +513,6 @@ class vmmDetails(gobject.GObject):
src.show_all()
def populate_migrate_menu(self, ignore1=None):
menu = self.window.get_widget("details-menu-migrate_menu")
self.engine.populate_migrate_menu(menu, self.control_vm_migrate,
self.vm)
def control_fullscreen(self, src):
menu = self.window.get_widget("details-menu-view-fullscreen")
if src.get_active() != menu.get_active():
@@ -740,9 +735,9 @@ class vmmDetails(gobject.GObject):
self.emit("action-clone-domain", self.vm.get_connection().get_uri(),
self.vm.get_uuid())
def control_vm_migrate(self, src, uri):
def control_vm_migrate(self, src):
self.emit("action-migrate-domain", self.vm.get_connection().get_uri(),
self.vm.get_uuid(), uri)
self.vm.get_uuid())
def control_vm_screenshot(self, src):
# If someone feels kind they could extend this code to allow

View File

@@ -265,8 +265,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_migrate_domain(self, src, uri, uuid):
self.migrate_domain(uri, uuid)
def _do_clone_domain(self, src, uri, uuid):
self.clone_domain(uri, uuid)
def _do_exit_app(self, src):
@@ -647,82 +647,16 @@ class vmmEngine(gobject.GObject):
self.err.show_err(_("Error shutting down domain: %s" % str(e)),
"".join(traceback.format_exc()))
def migrate_domain(self, uri, uuid, desturi):
def migrate_domain(self, uri, uuid):
conn = self._lookup_connection(uri)
vm = conn.get_vm(uuid)
destconn = self._lookup_connection(desturi)
if not self.windowMigrate:
self.windowMigrate = vmmMigrateDialog(self.config, vm, destconn)
self.windowMigrate = vmmMigrateDialog(self.config, vm, self)
self.windowMigrate.set_state(vm, destconn)
self.windowMigrate.set_state(vm)
self.windowMigrate.show()
def populate_migrate_menu(self, menu, migrate_func, vm):
conns = self.get_available_migrate_hostnames(vm)
# Clear menu
for item in menu:
menu.remove(item)
for ignore, val_list in conns.items():
can_migrate, label, tooltip, uri = val_list
mitem = gtk.ImageMenuItem(label)
mitem.set_sensitive(can_migrate)
mitem.connect("activate", migrate_func, uri)
if tooltip:
util.tooltip_wrapper(mitem, tooltip)
mitem.show()
menu.add(mitem)
if len(menu) == 0:
mitem = gtk.ImageMenuItem(_("No connections available."))
mitem.show()
menu.add(mitem)
def get_available_migrate_hostnames(self, vm):
driver = vm.get_connection().get_driver()
origuri = vm.get_connection().get_uri()
available_migrate_hostnames = {}
# Returns list of lists of the form
# [ Can we migrate to this connection?,
# String to use as list entry,
# Tooltip reason,
# Conn URI ]
# 1. connected(ACTIVE, INACTIVE) host
for key, value in self.connections.items():
if not value.has_key("connection"):
continue
conn = value["connection"]
can_migrate = False
desc = conn.get_pretty_desc_inactive()
reason = ""
desturi = conn.get_uri()
if conn.get_driver() != driver:
reason = _("Connection hypervisors do not match.")
elif conn.get_state() == vmmConnection.STATE_DISCONNECTED:
reason = _("Connection is disconnected.")
elif key == origuri:
reason = _("Cannot migrate to same connection.")
# Explicitly don't include this in the list
continue
elif conn.get_state() == vmmConnection.STATE_ACTIVE:
# Assumably we can migrate to this connection
can_migrate = True
reason = desturi
available_migrate_hostnames[key] = [can_migrate, desc, reason,
desturi]
return available_migrate_hostnames
def clone_domain(self, uri, uuid):
con = self._lookup_connection(uri)
orig_vm = con.get_vm(uuid)

View File

@@ -137,7 +137,7 @@ class vmmManager(gobject.GObject):
"action-show-help": (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE, [str]),
"action-migrate-domain": (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE, (str,str,str)),
gobject.TYPE_NONE, (str,str)),
"action-clone-domain": (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE, (str,str)),
"action-exit-app": (gobject.SIGNAL_RUN_FIRST,
@@ -215,7 +215,6 @@ class vmmManager(gobject.GObject):
self.vmmenushutdown = gtk.Menu()
self.vmmenu_items = {}
self.vmmenushutdown_items = {}
self.vmmenumigrate = gtk.Menu()
self.vmmenu_items["run"] = gtk.ImageMenuItem(_("_Run"))
self.vmmenu_items["run"].set_image(run_icon)
@@ -272,11 +271,9 @@ class vmmManager(gobject.GObject):
self.vmmenu_items["clone"].connect("activate", self.open_clone_window)
self.vmmenu.add(self.vmmenu_items["clone"])
self.vmmenu_items["migrate"] = gtk.ImageMenuItem(_("_Migrate"))
self.vmmenu_items["migrate"].set_submenu(self.vmmenumigrate)
self.vmmenu_items["migrate"] = gtk.ImageMenuItem(_("_Migrate..."))
self.vmmenu_items["migrate"].show()
self.vmmenu_items["migrate"].connect("activate",
self.populate_migrate_submenu)
self.vmmenu_items["migrate"].connect("activate", self.migrate_vm)
self.vmmenu.add(self.vmmenu_items["migrate"])
self.vmmenu_items["delete"] = gtk.ImageMenuItem("_Delete")
@@ -539,6 +536,10 @@ class vmmManager(gobject.GObject):
return markup
def _append_vm(self, model, vm, conn):
row_key = self.vm_row_key(vm)
if self.rows.has_key(row_key):
return
parent = self.rows[conn.get_uri()].iter
row = []
row.insert(ROW_HANDLE, vm)
@@ -558,7 +559,7 @@ class vmmManager(gobject.GObject):
_iter = model.append(parent, row)
path = model.get_path(_iter)
self.rows[self.vm_row_key(vm)] = model[path]
self.rows[row_key] = model[path]
# Expand a connection when adding a vm to it
self.window.get_widget("vm-list").expand_row(model.get_path(parent), False)
@@ -1087,32 +1088,27 @@ 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, uri):
def migrate_vm(self, ignore):
vm = self.current_vm()
if vm is not None:
self.emit("action-migrate-domain", vm.get_connection().get_uri(),
vm.get_uuid(), uri)
def populate_migrate_submenu(self, src):
vm = self.current_vm()
if not vm:
return
self.engine.populate_migrate_menu(self.vmmenumigrate, self.migrate,
vm)
vm.get_uuid())
def _add_connection(self, engine, conn):
if self.rows.has_key(conn.get_uri()):
return
conn.connect("vm-added", self.vm_added)
conn.connect("vm-removed", self.vm_removed)
conn.connect("resources-sampled", self.conn_refresh_resources)
conn.connect("state-changed", self.conn_state_changed)
conn.connect("connect-error", self._connect_error)
conn.connect("vm-started", self.vm_started)
# add the connection to the treeModel
vmlist = self.window.get_widget("vm-list")
if not self.rows.has_key(conn.get_uri()):
row = self._append_connection(vmlist.get_model(), conn)
vmlist.get_selection().select_iter(row)
row = self._append_connection(vmlist.get_model(), conn)
vmlist.get_selection().select_iter(row)
def _remove_connection(self, engine, conn):
model = self.window.get_widget("vm-list").get_model()

View File

@@ -49,7 +49,7 @@ class vmmMigrateDialog(gobject.GObject):
__gsignals__ = {
}
def __init__(self, config, vm, destconn):
def __init__(self, config, vm, engine):
self.__gobject_init__()
self.window = gtk.glade.XML(config.get_glade_dir() + \
"/vmm-migrate.glade",
@@ -57,7 +57,7 @@ class vmmMigrateDialog(gobject.GObject):
self.config = config
self.vm = vm
self.conn = vm.connection
self.destconn = destconn
self.engine = engine
self.topwin = self.window.get_widget("vmm-migrate")
self.err = vmmErrorDialog(self.topwin,
@@ -66,12 +66,15 @@ class vmmMigrateDialog(gobject.GObject):
_("An unexpected error occurred"))
self.topwin.hide()
self.destconn_rows = []
self.window.signal_autoconnect({
"on_vmm_migrate_delete_event" : self.close,
"on_migrate_cancel_clicked" : self.close,
"on_migrate_finish_clicked" : self.finish,
"on_migrate_dest_changed" : self.destconn_changed,
"on_migrate_set_rate_toggled" : self.toggle_set_rate,
"on_migrate_set_interface_toggled" : self.toggle_set_interface,
"on_migrate_set_port_toggled" : self.toggle_set_port,
@@ -85,6 +88,8 @@ class vmmMigrateDialog(gobject.GObject):
image.show()
self.window.get_widget("migrate-vm-icon-box").pack_end(image, False)
self.init_state()
def show(self):
self.reset_state()
self.topwin.show()
@@ -94,19 +99,33 @@ class vmmMigrateDialog(gobject.GObject):
self.topwin.hide()
return 1
def reset_state(self):
def init_state(self):
# [hostname, conn, can_migrate, tooltip]
dest_model = gtk.ListStore(str, object, bool, str)
dest_combo = self.window.get_widget("migrate-dest")
dest_combo.set_model(dest_model)
text = gtk.CellRendererText()
dest_combo.pack_start(text, True)
dest_combo.add_attribute(text, 'text', 0)
dest_combo.add_attribute(text, 'sensitive', 2)
dest_model.set_sort_column_id(0, gtk.SORT_ASCENDING)
# XXX no way to set tooltips here, kind of annoying
# Hook up signals to get connection listing
self.engine.connect("connection-added", self.dest_add_connection)
self.engine.connect("connection-removed", self.dest_remove_connection)
self.destconn_changed(dest_combo)
def reset_state(self):
title_str = ("<span size='large' color='white'>%s '%s'</span>" %
(_("Migrate"), self.vm.get_name()))
self.window.get_widget("migrate-main-label").set_markup(title_str)
name = self.vm.get_name()
srchost = self.conn.get_hostname()
dsthost = self.destconn.get_qualified_hostname()
self.window.get_widget("migrate-label-name").set_text(name)
self.window.get_widget("migrate-label-src").set_text(srchost)
self.window.get_widget("migrate-label-dest").set_text(dsthost)
self.window.get_widget("migrate-advanced-expander").set_expanded(False)
self.window.get_widget("migrate-set-interface").set_active(False)
@@ -117,7 +136,6 @@ class vmmMigrateDialog(gobject.GObject):
self.window.get_widget("migrate-offline").set_active(not running)
self.window.get_widget("migrate-offline").set_sensitive(running)
self.window.get_widget("migrate-interface").set_text(dsthost)
self.window.get_widget("migrate-rate").set_value(0)
self.window.get_widget("migrate-secure").set_active(False)
@@ -138,12 +156,22 @@ class vmmMigrateDialog(gobject.GObject):
secure_box.set_sensitive(support_secure)
util.tooltip_wrapper(secure_box, secure_tooltip)
def set_state(self, vm, destconn):
self.rebuild_dest_rows()
def set_state(self, vm):
self.vm = vm
self.conn = vm.connection
self.destconn = destconn
self.reset_state()
def destconn_changed(self, src):
active = src.get_active()
tooltip = None
if active == -1:
tooltip = _("A valid destination connection must be selected.")
self.window.get_widget("migrate-finish").set_sensitive(active != -1)
util.tooltip_wrapper(self.window.get_widget("migrate-finish"), tooltip)
def toggle_set_rate(self, src):
enable = src.get_active()
self.window.get_widget("migrate-rate").set_sensitive(enable)
@@ -160,6 +188,20 @@ class vmmMigrateDialog(gobject.GObject):
enable = src.get_active()
self.window.get_widget("migrate-port").set_sensitive(enable)
def get_config_destconn(self):
combo = self.window.get_widget("migrate-dest")
model = combo.get_model()
idx = combo.get_active()
if idx == -1:
return None
row = model[idx]
if not row[2]:
return None
return row[1]
def get_config_offline(self):
return self.window.get_widget("migrate-offline").get_active()
def get_config_secure(self):
@@ -186,21 +228,21 @@ class vmmMigrateDialog(gobject.GObject):
return 0
return int(self.window.get_widget("migrate-port").get_value())
def build_localhost_uri(self):
def build_localhost_uri(self, destconn):
# Try to build a remotely accessible URI for the local connection
desthost = self.destconn.get_qualified_hostname()
desthost = destconn.get_qualified_hostname()
if desthost == "localhost":
raise RuntimeError(_("Could not determine remotely accessible "
"hostname for destination connection."))
desturi_tuple = virtinst.util.uri_split(self.destconn.get_uri())
desturi_tuple = virtinst.util.uri_split(destconn.get_uri())
# Replace dest hostname with src hostname
desturi_tuple = list(desturi_tuple)
desturi_tuple[2] = desthost
return uri_join(desturi_tuple)
def build_migrate_uri(self):
def build_migrate_uri(self, destconn):
conn = self.conn
interface = self.get_config_interface()
@@ -214,8 +256,8 @@ class vmmMigrateDialog(gobject.GObject):
# For secure migration, we need to make sure we aren't migrating
# to the local connection, because libvirt will pull try to use
# 'qemu:///system' as the migrate URI which will deadlock
if self.destconn.is_local():
return self.build_localhost_uri()
if destconn.is_local():
return self.build_localhost_uri(destconn)
uri = ""
if conn.is_xen():
@@ -229,6 +271,105 @@ class vmmMigrateDialog(gobject.GObject):
return uri
def rebuild_dest_rows(self):
newrows = []
for row in self.destconn_rows:
newrows.append(self.build_dest_row(row[1]))
self.destconn_rows = newrows
self.populate_dest_combo()
def populate_dest_combo(self):
combo = self.window.get_widget("migrate-dest")
model = combo.get_model()
idx = combo.get_active()
idxconn = None
if idx != -1:
idxconn = model[idx][1]
rows = [[_("No connections available."), None, False, None]]
if self.destconn_rows:
rows = self.destconn_rows
model.clear()
for r in rows:
# Don't list the current connection
if r[1] == self.conn:
continue
model.append(r)
# Find old index
idx = -1
for i in range(len(model)):
row = model[i]
conn = row[1]
if idxconn:
if conn == idxconn and row[2]:
idx = i
break
else:
if row[2]:
idx = i
break
combo.set_active(idx)
def dest_add_connection(self, engine_ignore, conn):
combo = self.window.get_widget("migrate-dest")
model = combo.get_model()
newrow = self.build_dest_row(conn)
# Make sure connection isn't already present
for row in model:
if row[1] and row[1].get_uri() == newrow[1].get_uri():
return
conn.connect("state-changed", self.destconn_state_changed)
self.destconn_rows.append(newrow)
self.populate_dest_combo()
def dest_remove_connection(self, engine_ignore, conn):
# Make sure connection isn't already present
for row in self.destconn_rows:
if row[1] and row[1].get_uri() == conn.get_uri():
self.destconn_rows.remove(row)
self.populate_dest_combo()
def destconn_state_changed(self, conn):
for row in self.destconn_rows:
if row[1] == conn:
self.destconn_rows.remove(row)
self.destconn_rows.append(self.build_dest_row(conn))
self.populate_dest_combo()
def build_dest_row(self, destconn):
driver = self.conn.get_driver()
origuri = self.conn.get_uri()
can_migrate = False
desc = destconn.get_pretty_desc_inactive()
reason = ""
desturi = destconn.get_uri()
if destconn.get_driver() != driver:
reason = _("Connection hypervisors do not match.")
elif destconn.get_state() == destconn.STATE_DISCONNECTED:
reason = _("Connection is disconnected.")
elif destconn.get_uri() == origuri:
# Same connection
pass
elif destconn.get_state() == destconn.STATE_ACTIVE:
# Assumably we can migrate to this connection
can_migrate = True
reason = desturi
return [desc, destconn, can_migrate, reason]
def validate(self):
interface = self.get_config_interface()
@@ -251,11 +392,12 @@ class vmmMigrateDialog(gobject.GObject):
if not self.validate():
return
destconn = self.get_config_destconn()
srchost = self.vm.get_connection().get_hostname()
dsthost = self.destconn.get_qualified_hostname()
dsthost = destconn.get_qualified_hostname()
live = not self.get_config_offline()
secure = self.get_config_secure()
uri = self.build_migrate_uri()
uri = self.build_migrate_uri(destconn)
rate = self.get_config_rate()
if rate:
rate = int(rate)
@@ -269,7 +411,7 @@ class vmmMigrateDialog(gobject.GObject):
self.topwin.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
progWin = vmmAsyncJob(self.config, self._async_migrate,
[self.vm, self.destconn, uri, rate, live, secure],
[self.vm, destconn, uri, rate, live, secure],
title=_("Migrating VM '%s'" % self.vm.get_name()),
text=(_("Migrating VM '%s' from %s to %s. "
"This may take awhile.") %
@@ -285,7 +427,7 @@ class vmmMigrateDialog(gobject.GObject):
if error is None:
self.conn.tick(noStatsUpdate=True)
self.destconn.tick(noStatsUpdate=True)
destconn.tick(noStatsUpdate=True)
self.close()
def _async_migrate(self, origvm, origdconn, migrate_uri, rate, live,

View File

@@ -167,7 +167,7 @@
<child>
<widget class="GtkMenuItem" id="details-menu-clone">
<property name="visible">True</property>
<property name="label" translatable="yes">_Clone...</property>
<property name="label" translatable="yes">_Clone</property>
<property name="use_underline">True</property>
<signal name="activate" handler="on_details_menu_clone_activate"/>
</widget>
@@ -175,12 +175,9 @@
<child>
<widget class="GtkMenuItem" id="details-menu-migrate">
<property name="visible">True</property>
<property name="label" translatable="yes">_Migrate</property>
<property name="label" translatable="yes">_Migrate...</property>
<property name="use_underline">True</property>
<signal name="activate" handler="on_details_menu_migrate_activate"/>
<child>
<widget class="GtkMenu" id="details-menu-migrate_menu"/>
</child>
</widget>
</child>
<child>

View File

@@ -64,7 +64,7 @@
<widget class="GtkVBox" id="vbox3">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<property name="spacing">18</property>
<child>
<widget class="GtkTable" id="table2">
<property name="visible">True</property>
@@ -121,21 +121,6 @@
<property name="x_options">GTK_FILL</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="migrate-label-dest">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="ypad">2</property>
<property name="label">label</property>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
<property name="x_options">GTK_FILL</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="migrate-label-src">
<property name="visible">True</property>
@@ -197,6 +182,18 @@
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
<widget class="GtkComboBox" id="migrate-dest">
<property name="visible">True</property>
<signal name="changed" handler="on_migrate_dest_changed"/>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
</widget>
<packing>
<property name="expand">False</property>