summaryrefslogtreecommitdiff
path: root/virtManager/createvm.py
diff options
context:
space:
mode:
Diffstat (limited to 'virtManager/createvm.py')
-rw-r--r--virtManager/createvm.py2213
1 files changed, 2213 insertions, 0 deletions
diff --git a/virtManager/createvm.py b/virtManager/createvm.py
new file mode 100644
index 00000000..a9497dd0
--- /dev/null
+++ b/virtManager/createvm.py
@@ -0,0 +1,2213 @@
+# Copyright (C) 2008, 2013, 2014, 2015 Red Hat, Inc.
+# Copyright (C) 2008 Cole Robinson <crobinso@redhat.com>
+#
+# This work is licensed under the GNU GPLv2 or later.
+# See the COPYING file in the top-level directory.
+
+import io
+import pkgutil
+import os
+import threading
+import time
+
+from gi.repository import Gdk
+from gi.repository import Gtk
+from gi.repository import Pango
+
+import virtinst
+import virtinst.generatename
+from virtinst import log
+
+from . import uiutil
+from .asyncjob import vmmAsyncJob
+from .baseclass import vmmGObjectUI
+from .connmanager import vmmConnectionManager
+from .device.addstorage import vmmAddStorage
+from .device.mediacombo import vmmMediaCombo
+from .device.netlist import vmmNetworkList
+from .engine import vmmEngine
+from .object.domain import vmmDomainVirtinst
+from .oslist import vmmOSList
+from .storagebrowse import vmmStorageBrowser
+from .vmwindow import vmmVMWindow
+
+# Number of seconds to wait for media detection
+DETECT_TIMEOUT = 20
+
+DEFAULT_MEM = 1024
+
+(PAGE_NAME,
+ PAGE_INSTALL,
+ PAGE_MEM,
+ PAGE_STORAGE,
+ PAGE_FINISH) = range(5)
+
+(INSTALL_PAGE_ISO,
+ INSTALL_PAGE_URL,
+ INSTALL_PAGE_PXE,
+ INSTALL_PAGE_IMPORT,
+ INSTALL_PAGE_CONTAINER_APP,
+ INSTALL_PAGE_CONTAINER_OS,
+ INSTALL_PAGE_VZ_TEMPLATE) = range(7)
+
+# Column numbers for os type/version list models
+(OS_COL_ID,
+ OS_COL_LABEL,
+ OS_COL_IS_SEP,
+ OS_COL_IS_SHOW_ALL) = range(4)
+
+
+#####################
+# Pretty UI helpers #
+#####################
+
+def _pretty_arch(_a):
+ if _a == "armv7l":
+ return "arm"
+ return _a
+
+
+def _pretty_storage(size):
+ return _("%.1f GiB") % float(size)
+
+
+def _pretty_memory(mem):
+ return _("%d MiB") % (mem / 1024.0)
+
+
+###########################################################
+# Helpers for tracking devices we create from this wizard #
+###########################################################
+
+def _mark_vmm_device(dev):
+ setattr(dev, "vmm_create_wizard_device", True)
+
+
+def _get_vmm_device(guest, devkey):
+ for dev in getattr(guest.devices, devkey):
+ if hasattr(dev, "vmm_create_wizard_device"):
+ return dev
+
+
+def _remove_vmm_device(guest, devkey):
+ dev = _get_vmm_device(guest, devkey)
+ if dev:
+ guest.remove_device(dev)
+
+
+def is_virt_bootstrap_installed():
+ return pkgutil.find_loader('virtBootstrap') is not None
+
+
+##############
+# Main class #
+##############
+
+class vmmCreateVM(vmmGObjectUI):
+ @classmethod
+ def show_instance(cls, parentobj, uri=None):
+ try:
+ if not cls._instance:
+ cls._instance = vmmCreateVM()
+ cls._instance.show(parentobj and parentobj.topwin or None, uri=uri)
+ except Exception as e:
+ if not parentobj:
+ raise
+ parentobj.err.show_err(
+ _("Error launching create dialog: %s") % str(e))
+
+ def __init__(self):
+ vmmGObjectUI.__init__(self, "createvm.ui", "vmm-create")
+ self._cleanup_on_app_close()
+
+ self.conn = None
+ self._capsinfo = None
+
+ self._guest = None
+ self._failed_guest = None
+
+ # Distro detection state variables
+ self._detect_os_in_progress = False
+ self._os_already_detected_for_media = False
+
+ self._customize_window = None
+
+ self._storage_browser = None
+ self._netlist = None
+
+ self._addstorage = vmmAddStorage(self.conn, self.builder, self.topwin)
+ self.widget("storage-align").add(self._addstorage.top_box)
+ def _browse_file_cb(ignore, widget):
+ self._browse_file(widget)
+ self._addstorage.connect("browse-clicked", _browse_file_cb)
+
+ self._mediacombo = vmmMediaCombo(self.conn, self.builder, self.topwin)
+ self._mediacombo.connect("changed", self._iso_changed_cb)
+ self._mediacombo.connect("activate", self._iso_activated_cb)
+ self._mediacombo.set_mnemonic_label(
+ self.widget("install-iso-label"))
+ self.widget("install-iso-align").add(self._mediacombo.top_box)
+
+ self.builder.connect_signals({
+ "on_vmm_newcreate_delete_event": self._close_requested,
+
+ "on_create_cancel_clicked": self._close_requested,
+ "on_create_back_clicked": self._back_clicked,
+ "on_create_forward_clicked": self._forward_clicked,
+ "on_create_finish_clicked": self._finish_clicked,
+ "on_create_pages_switch_page": self._page_changed,
+
+ "on_create_conn_changed": self._conn_changed,
+ "on_method_changed": self._method_changed,
+ "on_xen_type_changed": self._xen_type_changed,
+ "on_arch_changed": self._arch_changed,
+ "on_virt_type_changed": self._virt_type_changed,
+ "on_machine_changed": self._machine_changed,
+ "on_vz_virt_type_changed": self._vz_virt_type_changed,
+
+ "on_install_iso_browse_clicked": self._browse_iso,
+ "on_install_url_entry_changed": self._url_changed,
+ "on_install_url_entry_activate": self._url_activated,
+ "on_install_import_browse_clicked": self._browse_import,
+ "on_install_app_browse_clicked": self._browse_app,
+ "on_install_oscontainer_browse_clicked": self._browse_oscontainer,
+ "on_install_container_source_toggle": self._container_source_toggle,
+
+ "on_install_detect_os_toggled": self._detect_os_toggled_cb,
+
+ "on_kernel_browse_clicked": self._browse_kernel,
+ "on_initrd_browse_clicked": self._browse_initrd,
+ "on_dtb_browse_clicked": self._browse_dtb,
+
+ "on_enable_storage_toggled": self._toggle_enable_storage,
+
+ "on_create_vm_name_changed": self._name_changed,
+ })
+ self.bind_escape_key_close()
+
+ self._init_state()
+
+
+
+ ###########################
+ # Standard window methods #
+ ###########################
+
+ def show(self, parent, uri):
+ log.debug("Showing new vm wizard")
+
+ if not self.is_visible():
+ self._reset_state(uri)
+ self.topwin.set_transient_for(parent)
+ vmmEngine.get_instance().increment_window_counter()
+
+ self.topwin.present()
+
+ def _close(self, ignore1=None, ignore2=None):
+ if self.is_visible():
+ log.debug("Closing new vm wizard")
+ vmmEngine.get_instance().decrement_window_counter()
+
+ self.topwin.hide()
+
+ self._cleanup_customize_window()
+ if self._storage_browser:
+ self._storage_browser.close()
+ self._set_conn(None)
+ self._failed_guest = None
+ self._guest = None
+
+ def _cleanup(self):
+ if self._storage_browser:
+ self._storage_browser.cleanup()
+ self._storage_browser = None
+ if self._netlist:
+ self._netlist.cleanup()
+ self._netlist = None
+ if self._mediacombo:
+ self._mediacombo.cleanup()
+ self._mediacombo = None
+ if self._addstorage:
+ self._addstorage.cleanup()
+ self._addstorage = None
+
+ self.conn = None
+ self._capsinfo = None
+ self._guest = None
+
+
+ ##########################
+ # Initial state handling #
+ ##########################
+
+ def _show_startup_error(self, error, hideinstall=True):
+ self.widget("startup-error-box").show()
+ self.widget("create-forward").set_sensitive(False)
+ if hideinstall:
+ self.widget("install-box").hide()
+ self.widget("arch-expander").hide()
+
+ self.widget("startup-error").set_text("%s: %s" % (_("Error"), error))
+ return False
+
+ def _show_startup_warning(self, error):
+ self.widget("startup-error-box").show()
+ self.widget("startup-error").set_markup(
+ "<span size='small'>%s: %s</span>" % (_("Warning"), error))
+
+ def _show_arch_warning(self, error):
+ self.widget("arch-warning-box").show()
+ self.widget("arch-warning").set_markup(
+ "<span size='small'>%s: %s</span>" % (_("Warning"), error))
+
+
+ def _init_state(self):
+ self.widget("create-pages").set_show_tabs(False)
+ self.widget("install-method-pages").set_show_tabs(False)
+
+ blue = Gdk.Color.parse("#0072A8")[1]
+ self.widget("header").modify_bg(Gtk.StateType.NORMAL, blue)
+
+ # Connection list
+ self.widget("create-conn-label").set_text("")
+ self.widget("startup-error").set_text("")
+ conn_list = self.widget("create-conn")
+ conn_model = Gtk.ListStore(str, str)
+ conn_list.set_model(conn_model)
+ text = uiutil.init_combo_text_column(conn_list, 1)
+ text.set_property("ellipsize", Pango.EllipsizeMode.MIDDLE)
+
+ def set_model_list(widget_id):
+ lst = self.widget(widget_id)
+ model = Gtk.ListStore(str)
+ lst.set_model(model)
+ lst.set_entry_text_column(0)
+
+ # Lists for the install urls
+ set_model_list("install-url-combo")
+
+ # Lists for OS container bootstrap
+ set_model_list("install-oscontainer-source-url-combo")
+
+ # Architecture
+ archList = self.widget("arch")
+ # [label, guest.os.arch value]
+ archModel = Gtk.ListStore(str, str)
+ archList.set_model(archModel)
+ uiutil.init_combo_text_column(archList, 0)
+ archList.set_row_separator_func(
+ lambda m, i, ignore: m[i][0] is None, None)
+
+ # guest.os.type value for xen (hvm vs. xen)
+ hyperList = self.widget("xen-type")
+ # [label, guest.os_type value]
+ hyperModel = Gtk.ListStore(str, str)
+ hyperList.set_model(hyperModel)
+ uiutil.init_combo_text_column(hyperList, 0)
+
+ # guest.os.machine value
+ lst = self.widget("machine")
+ # [machine ID]
+ model = Gtk.ListStore(str)
+ lst.set_model(model)
+ uiutil.init_combo_text_column(lst, 0)
+ lst.set_row_separator_func(lambda m, i, ignore: m[i][0] is None, None)
+
+ # guest.type value for xen (qemu vs kvm)
+ lst = self.widget("virt-type")
+ # [label, guest.type value]
+ model = Gtk.ListStore(str, str)
+ lst.set_model(model)
+ uiutil.init_combo_text_column(lst, 0)
+
+ # OS distro list
+ self._os_list = vmmOSList()
+ self.widget("install-os-align").add(self._os_list.search_entry)
+ self.widget("os-label").set_mnemonic_widget(self._os_list.search_entry)
+
+ def _reset_state(self, urihint=None):
+ """
+ Reset all UI state to default values. Conn specific state is
+ populated in _populate_conn_state
+ """
+ self.reset_finish_cursor()
+
+ self.widget("create-pages").set_current_page(PAGE_NAME)
+ self._page_changed(None, None, PAGE_NAME)
+
+ # Name page state
+ self.widget("create-vm-name").set_text("")
+ self.widget("method-local").set_active(True)
+ self.widget("create-conn").set_active(-1)
+ activeconn = self._populate_conn_list(urihint)
+ self.widget("arch-expander").set_expanded(False)
+ self.widget("vz-virt-type-hvm").set_active(True)
+
+ if self._set_conn(activeconn) is False:
+ return False
+
+
+ # Everything from this point forward should be connection independent
+
+ # Distro/Variant
+ self._os_list.reset_state()
+ self._os_already_detected_for_media = False
+
+ def _populate_media_model(media_model, urls):
+ media_model.clear()
+ if urls is None:
+ return
+ for url in urls:
+ media_model.append([url])
+
+ # Install local
+ self._mediacombo.reset_state()
+
+ # Install URL
+ self.widget("install-urlopts-entry").set_text("")
+ self.widget("install-url-entry").set_text("")
+ self.widget("install-url-options").set_expanded(False)
+ urlmodel = self.widget("install-url-combo").get_model()
+ _populate_media_model(urlmodel, self.config.get_media_urls())
+
+ # Install import
+ self.widget("install-import-entry").set_text("")
+ self.widget("kernel").set_text("")
+ self.widget("initrd").set_text("")
+ self.widget("dtb").set_text("")
+
+ # Install container app
+ self.widget("install-app-entry").set_text("/bin/sh")
+
+ # Install container OS
+ self.widget("install-oscontainer-fs").set_text("")
+ self.widget("install-oscontainer-source-url-entry").set_text("")
+ self.widget("install-oscontainer-source-user").set_text("")
+ self.widget("install-oscontainer-source-passwd").set_text("")
+ self.widget("install-oscontainer-source-insecure").set_active(False)
+ self.widget("install-oscontainer-bootstrap").set_active(False)
+ self.widget("install-oscontainer-auth-options").set_expanded(False)
+ self.widget("install-oscontainer-rootpw").set_text("")
+ src_model = (self.widget("install-oscontainer-source-url-combo")
+ .get_model())
+ _populate_media_model(src_model, self.config.get_container_urls())
+
+ # Install VZ container from template
+ self.widget("install-container-template").set_text("centos-7-x86_64")
+
+ # Storage
+ self.widget("enable-storage").set_active(True)
+ self._addstorage.reset_state()
+ self._addstorage.widget("storage-create").set_active(True)
+ self._addstorage.widget("storage-entry").set_text("")
+
+ # Final page
+ self.widget("summary-customize").set_active(False)
+
+
+ def _set_caps_state(self):
+ """
+ Set state that is dependent on when capsinfo changes
+ """
+ self.widget("arch-warning-box").hide()
+ guest = self._build_guest(None)
+
+ # Helper state
+ is_local = not self.conn.is_remote()
+ is_storage_capable = self.conn.is_storage_capable()
+ can_storage = (is_local or is_storage_capable)
+ is_pv = guest.os.is_xenpv()
+ is_container = self.conn.is_container()
+ is_vz = self.conn.is_vz()
+ is_vz_container = is_vz and guest.os.is_container()
+ can_remote_url = self.conn.get_backend().support_remote_url_install()
+
+ installable_arch = bool(guest.os.is_x86() or
+ guest.os.is_ppc64() or
+ guest.os.is_s390x())
+
+ if guest.prefers_uefi():
+ try:
+ guest.set_uefi_path(guest.get_uefi_path())
+ installable_arch = True
+ log.debug("UEFI found, setting it as default.")
+ except Exception as e:
+ installable_arch = False
+ log.debug("Error checking for UEFI default", exc_info=True)
+ msg = _("Failed to setup UEFI: %s\n"
+ "Install options are limited.") % e
+ self._show_arch_warning(msg)
+
+ # Install Options
+ method_tree = self.widget("method-tree")
+ method_pxe = self.widget("method-pxe")
+ method_local = self.widget("method-local")
+ method_import = self.widget("method-import")
+ method_container_app = self.widget("method-container-app")
+
+ method_tree.set_sensitive((is_local or can_remote_url) and
+ installable_arch)
+ method_local.set_sensitive(not is_pv and can_storage and
+ installable_arch)
+ method_pxe.set_sensitive(not is_pv and installable_arch)
+ method_import.set_sensitive(can_storage)
+ virt_methods = [method_local, method_tree, method_pxe, method_import]
+
+ pxe_tt = None
+ local_tt = None
+ tree_tt = None
+ import_tt = None
+
+ if not is_local:
+ if not can_remote_url:
+ tree_tt = _("Libvirt version does not "
+ "support remote URL installs.")
+ if not is_storage_capable:
+ local_tt = _("Connection does not support storage management.")
+ import_tt = local_tt
+
+ if is_pv:
+ base = _("%s installs not available for paravirt guests.")
+ pxe_tt = base % "PXE"
+ local_tt = base % "CDROM/ISO"
+
+ if not installable_arch:
+ msg = (_("Architecture '%s' is not installable") %
+ guest.os.arch)
+ tree_tt = msg
+ local_tt = msg
+ pxe_tt = msg
+
+ if not any([w.get_active() and w.get_sensitive()
+ for w in virt_methods]):
+ for w in virt_methods:
+ if w.get_sensitive():
+ w.set_active(True)
+ break
+
+ if not (is_container or
+ [w for w in virt_methods if w.get_sensitive()]):
+ return self._show_startup_error(
+ _("No install methods available for this connection."),
+ hideinstall=False)
+
+ method_tree.set_tooltip_text(tree_tt or "")
+ method_local.set_tooltip_text(local_tt or "")
+ method_pxe.set_tooltip_text(pxe_tt or "")
+ method_import.set_tooltip_text(import_tt or "")
+
+ # Container install options
+ method_container_app.set_active(True)
+ self.widget("container-install-box").set_visible(is_container)
+ self.widget("vz-install-box").set_visible(is_vz)
+ self.widget("virt-install-box").set_visible(
+ not is_container and not is_vz_container)
+
+ show_dtb = ("arm" in guest.os.arch or
+ "microblaze" in guest.os.arch or
+ "ppc" in guest.os.arch)
+ self.widget("kernel-box").set_visible(not installable_arch)
+ uiutil.set_grid_row_visible(self.widget("dtb"), show_dtb)
+
+ def _populate_conn_state(self):
+ """
+ Update all state that has some dependency on the current connection
+ """
+ self.conn.schedule_priority_tick(pollnet=True,
+ pollpool=True, polliface=True,
+ pollnodedev=True)
+
+ self.widget("install-box").show()
+ self.widget("create-forward").set_sensitive(True)
+
+ self._capsinfo = None
+ self.conn.invalidate_caps()
+ self._change_caps()
+ is_local = not self.conn.is_remote()
+
+ if not self._capsinfo.guest.has_install_options():
+ error = _("No hypervisor options were found for this "
+ "connection.")
+
+ if self.conn.is_qemu():
+ error += "\n\n"
+ error += _("This usually means that QEMU or KVM is not "
+ "installed on your machine, or the KVM kernel "
+ "modules are not loaded.")
+ return self._show_startup_error(error)
+
+ # A bit out of order, but populate the xen/virt/arch/machine lists
+ # so we can work with a default.
+ self._populate_xen_type()
+ self._populate_arch()
+ self._populate_virt_type()
+
+ show_arch = (self.widget("xen-type").get_visible() or
+ self.widget("virt-type").get_visible() or
+ self.widget("arch").get_visible() or
+ self.widget("machine").get_visible())
+ uiutil.set_grid_row_visible(self.widget("arch-expander"), show_arch)
+
+ if self.conn.is_xen():
+ has_hvm_guests = False
+ for g in self.conn.caps.guests:
+ if g.os_type == "hvm":
+ has_hvm_guests = True
+
+ if not has_hvm_guests:
+ error = _("Host is not advertising support for full "
+ "virtualization. Install options may be limited.")
+ self._show_startup_warning(error)
+
+ elif self.conn.is_qemu():
+ if not self._capsinfo.guest.is_kvm_available():
+ error = _("KVM is not available. This may mean the KVM "
+ "package is not installed, or the KVM kernel modules "
+ "are not loaded. Your virtual machines may perform poorly.")
+ self._show_startup_warning(error)
+
+ elif self.conn.is_vz():
+ has_hvm_guests = False
+ has_exe_guests = False
+ for g in self.conn.caps.guests:
+ if g.os_type == "hvm":
+ has_hvm_guests = True
+ if g.os_type == "exe":
+ has_exe_guests = True
+
+ self.widget("vz-virt-type-hvm").set_sensitive(has_hvm_guests)
+ self.widget("vz-virt-type-exe").set_sensitive(has_exe_guests)
+ self.widget("vz-virt-type-hvm").set_active(has_hvm_guests)
+ self.widget("vz-virt-type-exe").set_active(
+ not has_hvm_guests and has_exe_guests)
+
+ # ISO media
+ # Dependent on connection so we need to do this here
+ self._mediacombo.set_conn(self.conn)
+ self._mediacombo.reset_state()
+
+ # Allow container bootstrap only for local connection and
+ # only if virt-bootstrap is installed. Otherwise, show message.
+ vb_installed = is_virt_bootstrap_installed()
+ vb_enabled = is_local and vb_installed
+
+ oscontainer_widget_conf = {
+ "install-oscontainer-notsupport-conn": not is_local,
+ "install-oscontainer-notsupport": not vb_installed,
+ "install-oscontainer-bootstrap": vb_enabled,
+ "install-oscontainer-source": vb_enabled,
+ "install-oscontainer-rootpw-box": vb_enabled
+ }
+ for w in oscontainer_widget_conf:
+ self.widget(w).set_visible(oscontainer_widget_conf[w])
+
+ # Memory
+ memory = int(self.conn.host_memory_size())
+ mem_label = (_("Up to %(maxmem)s available on the host") %
+ {'maxmem': _pretty_memory(memory)})
+ mem_label = ("<span size='small' color='#484848'>%s</span>" %
+ mem_label)
+ self.widget("mem").set_range(50, memory // 1024)
+ self.widget("phys-mem-label").set_markup(mem_label)
+
+ # CPU
+ phys_cpus = int(self.conn.host_active_processor_count())
+ cmax = phys_cpus
+ if cmax <= 0:
+ cmax = 1
+ cpu_label = (_("Up to %(numcpus)d available") %
+ {'numcpus': int(phys_cpus)})
+ cpu_label = ("<span size='small' color='#484848'>%s</span>" %
+ cpu_label)
+ self.widget("cpus").set_range(1, cmax)
+ self.widget("phys-cpu-label").set_markup(cpu_label)
+
+ # Storage
+ self._addstorage.conn = self.conn
+ self._addstorage.reset_state()
+
+ # Networking
+ self.widget("advanced-expander").set_expanded(False)
+
+ self._netlist = vmmNetworkList(self.conn, self.builder, self.topwin)
+ self.widget("netdev-ui-align").add(self._netlist.top_box)
+ self._netlist.connect("changed", self._netdev_changed)
+ self._netlist.reset_state()
+
+ def _conn_state_changed(self, conn):
+ if conn.is_disconnected():
+ self._close()
+
+ def _set_conn(self, newconn):
+ self.widget("startup-error-box").hide()
+ self.widget("arch-warning-box").hide()
+
+ oldconn = self.conn
+ self.conn = newconn
+ if oldconn:
+ oldconn.disconnect_by_obj(self)
+ if self._netlist:
+ self.widget("netdev-ui-align").remove(self._netlist.top_box)
+ self._netlist.cleanup()
+ self._netlist = None
+
+ if not self.conn:
+ return self._show_startup_error(
+ _("No active connection to install on."))
+ self.conn.connect("state-changed", self._conn_state_changed)
+
+ try:
+ self._populate_conn_state()
+ except Exception as e:
+ log.exception("Error setting create wizard conn state.")
+ return self._show_startup_error(str(e))
+
+
+ def _change_caps(self, gtype=None, arch=None, domtype=None):
+ """
+ Change the cached capsinfo for the passed values, and trigger
+ all needed UI refreshing
+ """
+ if gtype is None:
+ # If none specified, prefer HVM so install options aren't limited
+ # with a default PV choice.
+ for g in self.conn.caps.guests:
+ if g.os_type == "hvm":
+ gtype = "hvm"
+ break
+
+ capsinfo = self.conn.caps.guest_lookup(os_type=gtype,
+ arch=arch,
+ typ=domtype)
+
+ if self._capsinfo:
+ if (self._capsinfo.guest == capsinfo.guest and
+ self._capsinfo.domain == capsinfo.domain):
+ return
+
+ self._capsinfo = capsinfo
+ log.debug("Guest type set to os_type=%s, arch=%s, dom_type=%s",
+ self._capsinfo.os_type,
+ self._capsinfo.arch,
+ self._capsinfo.hypervisor_type)
+ self._populate_machine()
+ self._set_caps_state()
+
+
+ ##################################################
+ # Helpers for populating hv/arch/machine/conn UI #
+ ##################################################
+
+ def _populate_xen_type(self):
+ model = self.widget("xen-type").get_model()
+ model.clear()
+
+ default = 0
+ guests = []
+ if self.conn.is_xen() or self.conn.is_test():
+ guests = self.conn.caps.guests[:]
+
+ for guest in guests:
+ if not guest.domains:
+ continue
+
+ gtype = guest.os_type
+ dom = guest.domains[0]
+ domtype = dom.hypervisor_type
+ label = self.conn.pretty_hv(gtype, domtype)
+
+ # Don't add multiple rows for each arch
+ for m in model:
+ if m[0] == label:
+ label = None
+ break
+ if label is None:
+ continue
+
+ # Determine if this is the default given by guest_lookup
+ if (gtype == self._capsinfo.os_type and
+ domtype == self._capsinfo.hypervisor_type):
+ default = len(model)
+
+ model.append([label, gtype])
+
+ show = bool(len(model))
+ uiutil.set_grid_row_visible(self.widget("xen-type"), show)
+ if show:
+ self.widget("xen-type").set_active(default)
+
+ def _populate_arch(self):
+ model = self.widget("arch").get_model()
+ model.clear()
+
+ default = 0
+ archs = []
+ for guest in self.conn.caps.guests:
+ if guest.os_type == self._capsinfo.os_type:
+ archs.append(guest.arch)
+
+ # Combine x86/i686 to avoid confusion
+ if (self.conn.caps.host.cpu.arch == "x86_64" and
+ "x86_64" in archs and "i686" in archs):
+ archs.remove("i686")
+ archs.sort()
+
+ prios = ["x86_64", "i686", "aarch64", "armv7l", "ppc64", "ppc64le",
+ "s390x"]
+ if self.conn.caps.host.cpu.arch not in prios:
+ prios = []
+ else:
+ for p in prios[:]:
+ if p not in archs:
+ prios.remove(p)
+ else:
+ archs.remove(p)
+ if prios:
+ if archs:
+ prios += [None]
+ archs = prios + archs
+
+ default = 0
+ if self._capsinfo.arch in archs:
+ default = archs.index(self._capsinfo.arch)
+
+ for arch in archs:
+ model.append([_pretty_arch(arch), arch])
+
+ show = not (len(archs) < 2)
+ uiutil.set_grid_row_visible(self.widget("arch"), show)
+ self.widget("arch").set_active(default)
+
+ def _populate_virt_type(self):
+ model = self.widget("virt-type").get_model()
+ model.clear()
+
+ # Allow choosing between qemu and kvm for archs that traditionally
+ # have a decent amount of TCG usage, like armv7l. Also include
+ # aarch64 which can be used for arm32 VMs as well
+ domains = [d.hypervisor_type for d in self._capsinfo.guest.domains[:]]
+ if not self.conn.is_qemu():
+ domains = []
+ elif self._capsinfo.arch in ["i686", "x86_64", "ppc64", "ppc64le"]:
+ domains = []
+
+ default = 0
+ if self._capsinfo.hypervisor_type in domains:
+ default = domains.index(self._capsinfo.hypervisor_type)
+
+ prios = ["kvm"]
+ for domain in prios:
+ if domain not in domains:
+ continue
+ domains.remove(domain)
+ domains.insert(0, domain)
+
+ for domain in domains:
+ label = self.conn.pretty_hv(self._capsinfo.os_type, domain)
+ model.append([label, domain])
+
+ show = bool(len(model) > 1)
+ uiutil.set_grid_row_visible(self.widget("virt-type"), show)
+ self.widget("virt-type").set_active(default)
+
+ def _populate_machine(self):
+ model = self.widget("machine").get_model()
+
+ machines = self._capsinfo.machines[:]
+ if self._capsinfo.arch in ["i686", "x86_64"]:
+ machines = []
+ machines.sort()
+
+ defmachine = None
+ prios = []
+ recommended_machine = virtinst.Guest.get_recommended_machine(
+ self._capsinfo)
+ if recommended_machine:
+ defmachine = recommended_machine
+ prios = [defmachine]
+
+ for p in prios[:]:
+ if p not in machines:
+ prios.remove(p)
+ else:
+ machines.remove(p)
+ if prios:
+ machines = prios + [None] + machines
+
+ default = 0
+ if defmachine and defmachine in machines:
+ default = machines.index(defmachine)
+
+ self.widget("machine").disconnect_by_func(self._machine_changed)
+ try:
+ model.clear()
+ for m in machines:
+ model.append([m])
+
+ show = (len(machines) > 1)
+ uiutil.set_grid_row_visible(self.widget("machine"), show)
+ if show:
+ self.widget("machine").set_active(default)
+ finally:
+ self.widget("machine").connect("changed", self._machine_changed)
+
+ def _populate_conn_list(self, urihint=None):
+ conn_list = self.widget("create-conn")
+ model = conn_list.get_model()
+ model.clear()
+
+ default = -1
+ connmanager = vmmConnectionManager.get_instance()
+ for connobj in connmanager.conns.values():
+ if not connobj.is_active():
+ continue
+
+ if connobj.get_uri() == urihint:
+ default = len(model)
+ elif default < 0 and not connobj.is_remote():
+ # Favor local connections over remote connections
+ default = len(model)
+
+ model.append([connobj.get_uri(), connobj.get_pretty_desc()])
+
+ no_conns = (len(model) == 0)
+
+ if default < 0 and not no_conns:
+ default = 0
+
+ activeuri = ""
+ activedesc = ""
+ activeconn = None
+ if not no_conns:
+ conn_list.set_active(default)
+ activeuri, activedesc = model[default]
+ activeconn = connmanager.conns[activeuri]
+
+ self.widget("create-conn-label").set_text(activedesc)
+ if len(model) <= 1:
+ self.widget("create-conn").hide()
+ self.widget("create-conn-label").show()
+ else:
+ self.widget("create-conn").show()
+ self.widget("create-conn-label").hide()
+
+ return activeconn
+
+
+ ###############################
+ # Misc UI populating routines #
+ ###############################
+
+ def _populate_summary_storage(self, path=None):
+ storagetmpl = "<span size='small' color='#484848'>%s</span>"
+ storagesize = ""
+ storagepath = ""
+
+ disk = _get_vmm_device(self._guest, "disk")
+ if disk:
+ if disk.wants_storage_creation():
+ storagesize = "%s" % _pretty_storage(disk.get_size())
+ if not path:
+ path = disk.path
+ storagepath = (storagetmpl % path)
+ elif len(self._guest.devices.filesystem):
+ fs = self._guest.devices.filesystem[0]
+ storagepath = storagetmpl % fs.source
+ elif self._guest.os.is_container():
+ storagepath = _("Host filesystem")
+ else:
+ storagepath = _("None")
+
+ self.widget("summary-storage").set_markup(storagesize)
+ self.widget("summary-storage").set_visible(bool(storagesize))
+ self.widget("summary-storage-path").set_markup(storagepath)
+
+ def _populate_summary(self):
+ mem = _pretty_memory(int(self._guest.memory))
+ cpu = str(int(self._guest.vcpus))
+
+ instmethod = self._get_config_install_page()
+ install = ""
+ if instmethod == INSTALL_PAGE_ISO:
+ install = _("Local CDROM/ISO")
+ elif instmethod == INSTALL_PAGE_URL:
+ install = _("URL Install Tree")
+ elif instmethod == INSTALL_PAGE_PXE:
+ install = _("PXE Install")
+ elif instmethod == INSTALL_PAGE_IMPORT:
+ install = _("Import existing OS image")
+ elif instmethod == INSTALL_PAGE_CONTAINER_APP:
+ install = _("Application container")
+ elif instmethod == INSTALL_PAGE_CONTAINER_OS:
+ install = _("Operating system container")
+ elif instmethod == INSTALL_PAGE_VZ_TEMPLATE:
+ install = _("Virtuozzo container")
+
+ self.widget("summary-os").set_text(self._guest.osinfo.label)
+ self.widget("summary-install").set_text(install)
+ self.widget("summary-mem").set_text(mem)
+ self.widget("summary-cpu").set_text(cpu)
+ self._populate_summary_storage()
+
+ self._netdev_changed(None)
+
+
+ ################################
+ # UI state getters and helpers #
+ ################################
+
+ def _get_config_name(self):
+ return self.widget("create-vm-name").get_text()
+
+ def _get_config_machine(self):
+ return uiutil.get_list_selection(self.widget("machine"),
+ check_visible=True)
+
+ def _get_config_install_page(self):
+ if self.widget("vz-install-box").get_visible():
+ if self.widget("vz-virt-type-exe").get_active():
+ return INSTALL_PAGE_VZ_TEMPLATE
+ if self.widget("virt-install-box").get_visible():
+ if self.widget("method-local").get_active():
+ return INSTALL_PAGE_ISO
+ elif self.widget("method-tree").get_active():
+ return INSTALL_PAGE_URL
+ elif self.widget("method-pxe").get_active():
+ return INSTALL_PAGE_PXE
+ elif self.widget("method-import").get_active():
+ return INSTALL_PAGE_IMPORT
+ else:
+ if self.widget("method-container-app").get_active():
+ return INSTALL_PAGE_CONTAINER_APP
+ if self.widget("method-container-os").get_active():
+ return INSTALL_PAGE_CONTAINER_OS
+
+ def _is_container_install(self):
+ return self._get_config_install_page() in [INSTALL_PAGE_CONTAINER_APP,
+ INSTALL_PAGE_CONTAINER_OS,
+ INSTALL_PAGE_VZ_TEMPLATE]
+
+
+ def _get_config_oscontainer_bootstrap(self):
+ return self.widget("install-oscontainer-bootstrap").get_active()
+
+
+ def _get_config_oscontainer_source_url(self, store_media=False):
+ src_url = (self.widget("install-oscontainer-source-url-entry")
+ .get_text().strip())
+
+ if src_url and store_media:
+ self.config.add_container_url(src_url)
+
+ return src_url
+
+
+ def _get_config_oscontainer_source_username(self):
+ return (self.widget("install-oscontainer-source-user")
+ .get_text().strip())
+
+
+ def _get_config_oscontainer_source_password(self):
+ return self.widget("install-oscontainer-source-passwd").get_text()
+
+
+ def _get_config_oscontainer_isecure(self):
+ return self.widget("install-oscontainer-source-insecure").get_active()
+
+
+ def _get_config_oscontainer_root_password(self):
+ return self.widget("install-oscontainer-rootpw").get_text()
+
+
+ def _should_skip_disk_page(self):
+ return self._get_config_install_page() in [INSTALL_PAGE_IMPORT,
+ INSTALL_PAGE_CONTAINER_APP,
+ INSTALL_PAGE_CONTAINER_OS,
+ INSTALL_PAGE_VZ_TEMPLATE]
+
+ def _get_config_local_media(self, store_media=False):
+ return self._mediacombo.get_path(store_media=store_media)
+
+ def _get_config_detectable_media(self):
+ instpage = self._get_config_install_page()
+ cdrom = None
+ location = None
+
+ if instpage == INSTALL_PAGE_ISO:
+ cdrom = self._get_config_local_media()
+ elif instpage == INSTALL_PAGE_URL:
+ location = self.widget("install-url-entry").get_text()
+
+ return cdrom, location
+
+ def _get_config_url_info(self, store_media=False):
+ media = self.widget("install-url-entry").get_text().strip()
+ extra = self.widget("install-urlopts-entry").get_text().strip()
+
+ if media and store_media:
+ self.config.add_media_url(media)
+
+ return (media, extra)
+
+ def _get_config_import_path(self):
+ return self.widget("install-import-entry").get_text()
+
+ def _is_default_storage(self):
+ return (self._addstorage.is_default_storage() and
+ not self._should_skip_disk_page())
+
+ def _is_os_detect_active(self):
+ return self.widget("install-detect-os").get_active()
+
+
+ ################
+ # UI Listeners #
+ ################
+
+ def _close_requested(self, *ignore1, **ignore2):
+ """
+ When user tries to close the dialog, check for any disks that
+ we should auto cleanup
+ """
+ if (self._failed_guest and
+ self._failed_guest.installer_instance.get_created_disks(
+ self._failed_guest)):
+
+ def _cleanup_disks(asyncjob):
+ meter = asyncjob.get_meter()
+ self._failed_guest.installer_instance.cleanup_created_disks(
+ self._failed_guest, meter)
+
+ def _cleanup_disks_finished(error, details):
+ if error:
+ log.debug("Error cleaning up disk images:"
+ "\nerror=%s\ndetails=%s", error, details)
+ self.idle_add(self._close)
+
+ progWin = vmmAsyncJob(
+ _cleanup_disks, [],
+ _cleanup_disks_finished, [],
+ _("Removing disk images"),
+ _("Removing disk images we created for this virtual machine."),
+ self.topwin)
+ progWin.run()
+
+ else:
+ self._close()
+
+ return 1
+
+
+ # Intro page listeners
+ def _conn_changed(self, src):
+ uri = uiutil.get_list_selection(src)
+ newconn = None
+ connmanager = vmmConnectionManager.get_instance()
+ if uri:
+ newconn = connmanager.conns[uri]
+
+ # If we aren't visible, let reset_state handle this for us, which
+ # has a better chance of reporting error
+ if not self.is_visible():
+ return
+
+ if self.conn is not newconn:
+ self._set_conn(newconn)
+
+ def _method_changed(self, src):
+ ignore = src
+ # Reset the page number, since the total page numbers depend
+ # on the chosen install method
+ self._set_page_num_text(0)
+
+ def _machine_changed(self, ignore):
+ self._set_caps_state()
+
+ def _xen_type_changed(self, ignore):
+ os_type = uiutil.get_list_selection(self.widget("xen-type"), column=1)
+ if not os_type:
+ return
+
+ self._change_caps(os_type)
+ self._populate_arch()
+
+ def _arch_changed(self, ignore):
+ arch = uiutil.get_list_selection(self.widget("arch"), column=1)
+ if not arch:
+ return
+
+ self._change_caps(self._capsinfo.os_type, arch)
+ self._populate_virt_type()
+
+ def _virt_type_changed(self, ignore):
+ domtype = uiutil.get_list_selection(self.widget("virt-type"), column=1)
+ if not domtype:
+ return
+
+ self._change_caps(self._capsinfo.os_type, self._capsinfo.arch, domtype)
+
+ def _vz_virt_type_changed(self, ignore):
+ is_hvm = self.widget("vz-virt-type-hvm").get_active()
+ if is_hvm:
+ self._change_caps("hvm")
+ else:
+ self._change_caps("exe")
+
+ # Install page listeners
+ def _detectable_media_widget_changed(self, widget, checkfocus=True):
+ self._os_already_detected_for_media = False
+
+ # If the text entry widget has focus, don't fire detect_media_os,
+ # it means the user is probably typing. It will be detected
+ # when the user activates the widget, or we try to switch pages
+ if (checkfocus and
+ hasattr(widget, "get_text") and widget.has_focus()):
+ return
+
+ self._start_detect_os_if_needed()
+
+ def _url_changed(self, src):
+ self._detectable_media_widget_changed(src)
+ def _url_activated(self, src):
+ self._detectable_media_widget_changed(src, checkfocus=False)
+ def _iso_changed_cb(self, mediacombo, entry):
+ self._detectable_media_widget_changed(entry)
+ def _iso_activated_cb(self, mediacombo, entry):
+ self._detectable_media_widget_changed(entry, checkfocus=False)
+
+ def _detect_os_toggled_cb(self, src):
+ if not src.is_visible():
+ return
+
+ # We are only here if the user explicitly changed detection UI
+ dodetect = src.get_active()
+ self._change_os_detect(not dodetect)
+ if dodetect:
+ self._os_already_detected_for_media = False
+ self._start_detect_os_if_needed()
+
+ def _browse_oscontainer(self, ignore):
+ self._browse_file("install-oscontainer-fs", is_dir=True)
+ def _browse_app(self, ignore):
+ self._browse_file("install-app-entry")
+ def _browse_import(self, ignore):
+ self._browse_file("install-import-entry")
+ def _browse_iso(self, ignore):
+ def set_path(ignore, path):
+ self._mediacombo.set_path(path)
+ self._browse_file(None, cb=set_path, is_media=True)
+ def _browse_kernel(self, ignore):
+ self._browse_file("kernel")
+ def _browse_initrd(self, ignore):
+ self._browse_file("initrd")
+ def _browse_dtb(self, ignore):
+ self._browse_file("dtb")
+
+
+ # Storage page listeners
+ def _toggle_enable_storage(self, src):
+ self.widget("storage-align").set_sensitive(src.get_active())
+
+
+ # Summary page listeners
+ def _name_changed(self, src):
+ newname = src.get_text()
+ if not src.is_visible():
+ return
+ if not newname:
+ return
+
+ try:
+ path, ignore = self._get_storage_path(newname, do_log=False)
+ self._populate_summary_storage(path=path)
+ except Exception:
+ log.debug("Error generating storage path on name change "
+ "for name=%s", newname, exc_info=True)
+
+
+ def _netdev_changed(self, ignore):
+ row = self._netlist.get_network_row()
+ pxe_install = (self._get_config_install_page() == INSTALL_PAGE_PXE)
+
+ ntype = row[0]
+ connkey = row[6]
+ expand = (ntype != "network" and ntype != "bridge")
+ no_network = ntype is None
+
+ if (no_network or ntype == virtinst.DeviceInterface.TYPE_USER):
+ can_pxe = False
+ elif ntype != virtinst.DeviceInterface.TYPE_VIRTUAL:
+ can_pxe = True
+ else:
+ can_pxe = self.conn.get_net(connkey).can_pxe()
+
+ if expand:
+ self.widget("advanced-expander").set_expanded(True)
+
+ self.widget("netdev-warn-box").set_visible(False)
+ def _show_netdev_warn(msg):
+ self.widget("advanced-expander").set_expanded(True)
+ self.widget("netdev-warn-box").set_visible(True)
+ self.widget("netdev-warn-label").set_markup(
+ "<small>%s</small>" % msg)
+
+ if no_network:
+ _show_netdev_warn(_("No network selected"))
+ elif not can_pxe and pxe_install:
+ _show_netdev_warn(_("Network selection does not support PXE"))
+
+
+ # Enable/Disable container source URL entry on checkbox click
+ def _container_source_toggle(self, ignore):
+ enable_src = self.widget("install-oscontainer-bootstrap").get_active()
+ self.widget("install-oscontainer-source").set_sensitive(enable_src)
+ self.widget("install-oscontainer-rootpw-box").set_sensitive(enable_src)
+
+ # Auto-generate a path if not specified
+ if enable_src and not self.widget("install-oscontainer-fs").get_text():
+ if os.geteuid() == 0:
+ fs_dir = ['/var/lib/libvirt/filesystems/']
+ else:
+ fs_dir = [os.environ['HOME'],
+ '.local/share/libvirt/filesystems/']
+
+ default_name = virtinst.Guest.generate_name(self._guest)
+ fs = fs_dir + [default_name]
+ self.widget("install-oscontainer-fs").set_text(os.path.join(*fs))
+
+
+ ########################
+ # Misc helper routines #
+ ########################
+
+ def _browse_file(self, cbwidget, cb=None, is_media=False, is_dir=False):
+ if is_media:
+ reason = self.config.CONFIG_DIR_ISO_MEDIA
+ elif is_dir:
+ reason = self.config.CONFIG_DIR_FS
+ else:
+ reason = self.config.CONFIG_DIR_IMAGE
+
+ if cb:
+ callback = cb
+ else:
+ def callback(ignore, text):
+ widget = cbwidget
+ if isinstance(cbwidget, str):
+ widget = self.widget(cbwidget)
+ widget.set_text(text)
+
+ if self._storage_browser and self._storage_browser.conn != self.conn:
+ self._storage_browser.cleanup()
+ self._storage_browser = None
+ if self._storage_browser is None:
+ self._storage_browser = vmmStorageBrowser(self.conn)
+
+ self._storage_browser.set_vm_name(self._get_config_name())
+ self._storage_browser.set_finish_cb(callback)
+ self._storage_browser.set_browse_reason(reason)
+ self._storage_browser.show(self.topwin)
+
+
+ ######################
+ # Navigation methods #
+ ######################
+
+ def _set_page_num_text(self, cur):
+ """
+ Set the 'page 1 of 4' style text in the wizard header
+ """
+ cur += 1
+ final = PAGE_FINISH + 1
+ if self._should_skip_disk_page():
+ final -= 1
+ cur = min(cur, final)
+
+ page_lbl = ("<span color='#59B0E2'>%s</span>" %
+ _("Step %(current_page)d of %(max_page)d") %
+ {'current_page': cur, 'max_page': final})
+
+ self.widget("header-pagenum").set_markup(page_lbl)
+
+ def _change_os_detect(self, sensitive):
+ self._os_list.set_sensitive(sensitive)
+ if not sensitive and not self._os_list.get_selected_os():
+ self._os_list.search_entry.set_text(
+ _("Waiting for install media / source"))
+
+ def _set_install_page(self):
+ instpage = self._get_config_install_page()
+
+ # Setting OS value for container doesn't matter presently
+ self.widget("install-os-distro-box").set_visible(
+ not self._is_container_install())
+
+ enabledetect = False
+ if instpage == INSTALL_PAGE_URL:
+ enabledetect = True
+ elif instpage == INSTALL_PAGE_ISO and not self.conn.is_remote():
+ enabledetect = True
+
+ self.widget("install-detect-os-box").set_visible(enabledetect)
+ dodetect = (enabledetect and
+ self.widget("install-detect-os").get_active())
+ self._change_os_detect(not dodetect)
+
+ # PXE installs have nothing to ask for
+ self.widget("install-method-pages").set_visible(
+ instpage != INSTALL_PAGE_PXE)
+ self.widget("install-method-pages").set_current_page(instpage)
+
+ def _back_clicked(self, src_ignore):
+ notebook = self.widget("create-pages")
+ curpage = notebook.get_current_page()
+ next_page = curpage - 1
+
+ if curpage == PAGE_FINISH and self._should_skip_disk_page():
+ # Skip over storage page
+ next_page -= 1
+
+ notebook.set_current_page(next_page)
+
+ def _get_next_pagenum(self, curpage):
+ next_page = curpage + 1
+
+ if next_page == PAGE_STORAGE and self._should_skip_disk_page():
+ # Skip storage page for import installs
+ next_page += 1
+
+ return next_page
+
+ def _forward_clicked(self, src_ignore=None):
+ notebook = self.widget("create-pages")
+ curpage = notebook.get_current_page()
+
+ if curpage == PAGE_INSTALL:
+ # Make sure we have detected the OS before validating the page
+ did_start = self._start_detect_os_if_needed(
+ forward_after_finish=True)
+ if did_start:
+ return
+
+ if self._validate(curpage) is not True:
+ return
+
+ if curpage == PAGE_NAME:
+ self._set_install_page()
+
+ next_page = self._get_next_pagenum(curpage)
+
+ self.widget("create-forward").grab_focus()
+ notebook.set_current_page(next_page)
+
+
+ def _page_changed(self, ignore1, ignore2, pagenum):
+ if pagenum == PAGE_FINISH:
+ try:
+ self._populate_summary()
+ except Exception as e:
+ self.err.show_err(_("Error populating summary page: %s") %
+ str(e))
+ return
+
+ self.widget("create-finish").grab_focus()
+
+ self._set_page_num_text(pagenum)
+ self.widget("create-back").set_sensitive(pagenum != PAGE_NAME)
+ self.widget("create-forward").set_visible(pagenum != PAGE_FINISH)
+ self.widget("create-finish").set_visible(pagenum == PAGE_FINISH)
+
+ # Hide all other pages, so the dialog isn't all stretched out
+ # because of one large page.
+ for nr in range(self.widget("create-pages").get_n_pages()):
+ page = self.widget("create-pages").get_nth_page(nr)
+ page.set_visible(nr == pagenum)
+
+
+ ############################
+ # Page validation routines #
+ ############################
+
+ def _build_guest(self, variant):
+ guest = virtinst.Guest(self.conn.get_backend())
+ guest.set_capabilities_defaults(self._capsinfo)
+
+ # If no machine was selected don't clear recommended machine
+ machine = self._get_config_machine()
+ if machine:
+ guest.os.machine = machine
+
+ # Validation catches user manually typing an invalid value
+ try:
+ if variant:
+ guest.set_os_name(variant)
+ except ValueError as e:
+ self.err.val_err(_("Error setting OS information."), str(e))
+ return None
+
+ guest.default_graphics_type = self.config.get_graphics_type()
+ guest.skip_default_sound = not self.config.get_new_vm_sound()
+ guest.skip_default_usbredir = (
+ self.config.get_add_spice_usbredir() == "no")
+ guest.x86_cpu_default = self.config.get_default_cpu_setting()
+
+ return guest
+
+ def _validate(self, pagenum):
+ try:
+ if pagenum == PAGE_NAME:
+ return self._validate_intro_page()
+ elif pagenum == PAGE_INSTALL:
+ return self._validate_install_page()
+ elif pagenum == PAGE_MEM:
+ return self._validate_mem_page()
+ elif pagenum == PAGE_STORAGE:
+ return self._validate_storage_page()
+ elif pagenum == PAGE_FINISH:
+ return self._validate_final_page()
+ except Exception as e:
+ self.err.show_err(_("Uncaught error validating install "
+ "parameters: %s") % str(e))
+ return
+
+ def _validate_intro_page(self):
+ # We just set this here because it's needed soon after for distro
+ # detection. But the 'real' self._guest is created in validate_install,
+ # and it just uses _build_guest, so don't ever add any other guest
+ # altering here.
+ self._guest = self._build_guest(None)
+ if not self._guest:
+ return False
+ return True
+
+ def _validate_install_page(self):
+ instmethod = self._get_config_install_page()
+ installer = None
+ location = None
+ extra = None
+ cdrom = None
+ install_bootdev = None
+ is_import = False
+ init = None
+ fs = None
+ template = None
+ osobj = self._os_list.get_selected_os()
+
+ if not self._is_container_install() and not osobj:
+ return self.err.val_err(_("You must select an OS.") +
+ "\n\n" + self._os_list.eol_text)
+
+ if instmethod == INSTALL_PAGE_ISO:
+ media = self._get_config_local_media()
+ if not media:
+ return self.err.val_err(
+ _("An install media selection is required."))
+ cdrom = media
+
+ elif instmethod == INSTALL_PAGE_URL:
+ media, extra = self._get_config_url_info()
+
+ if not media:
+ return self.err.val_err(_("An install tree is required."))
+
+ location = media
+
+ elif instmethod == INSTALL_PAGE_PXE:
+ install_bootdev = "network"
+
+ elif instmethod == INSTALL_PAGE_IMPORT:
+ is_import = True
+ import_path = self._get_config_import_path()
+ if not import_path:
+ return self.err.val_err(
+ _("A storage path to import is required."))
+
+ if not virtinst.DeviceDisk.path_definitely_exists(
+ self.conn.get_backend(),
+ import_path):
+ return self.err.val_err(_("The import path must point to "
+ "an existing storage."))
+
+ elif instmethod == INSTALL_PAGE_CONTAINER_APP:
+ init = self.widget("install-app-entry").get_text()
+ if not init:
+ return self.err.val_err(_("An application path is required."))
+
+ elif instmethod == INSTALL_PAGE_CONTAINER_OS:
+ fs = self.widget("install-oscontainer-fs").get_text()
+ if not fs:
+ return self.err.val_err(_("An OS directory path is required."))
+
+ if self._get_config_oscontainer_bootstrap():
+ src_url = self._get_config_oscontainer_source_url()
+ user = self._get_config_oscontainer_source_username()
+ passwd = self._get_config_oscontainer_source_password()
+
+ # Check if the source path was provided
+ if not src_url:
+ return self.err.val_err(_("Source URL is required"))
+
+ # Require username and password when authenticate
+ # to source registry.
+ if user and not passwd:
+ return self.err.val_err(_("Please specify password "
+ "for accessing source registry"))
+
+ # Validate destination path
+ if os.path.exists(fs):
+ if not os.path.isdir(fs):
+ return self.err.val_err(_("Destination path "
+ "is not directory: %s") % fs)
+ if not os.access(fs, os.W_OK):
+ return self.err.val_err(_("No write permissions for "
+ "directory path: %s") % fs)
+ if os.listdir(fs) != []:
+ # Show Yes/No dialog if the destination is not empty
+ res = self.err.yes_no(
+ _("OS root directory is not empty"),
+ _("Creating root file system in a non-empty "
+ "directory might fail due to file conflicts.\n"
+ "Would you like to continue?"))
+ if not res:
+ return False
+
+
+ elif instmethod == INSTALL_PAGE_VZ_TEMPLATE:
+ template = self.widget("install-container-template").get_text()
+ if not template:
+ return self.err.val_err(_("A template name is required."))
+
+ # Build the installer and Guest instance
+ try:
+ # Overwrite the guest
+ installer = virtinst.Installer(
+ self.conn.get_backend(),
+ location=location, cdrom=cdrom,
+ install_bootdev=install_bootdev)
+ variant = osobj and osobj.name or None
+ self._guest = self._build_guest(variant)
+ if not self._guest:
+ return False
+ except Exception as e:
+ return self.err.val_err(
+ _("Error setting installer parameters."), e)
+
+ # Validate media location
+ try:
+ if extra:
+ installer.set_extra_args([extra])
+ if init:
+ self._guest.os.init = init
+
+ if fs:
+ fsdev = virtinst.DeviceFilesystem(self._guest.conn)
+ fsdev.target = "/"
+ fsdev.source = fs
+ self._guest.add_device(fsdev)
+
+ if template:
+ fsdev = virtinst.DeviceFilesystem(self._guest.conn)
+ fsdev.target = "/"
+ fsdev.type = "template"
+ fsdev.source = template
+ self._guest.add_device(fsdev)
+
+ except Exception as e:
+ return self.err.val_err(
+ _("Error setting install media location."), e)
+
+ # Setting kernel
+ if instmethod == INSTALL_PAGE_IMPORT:
+ kernel = self.widget("kernel").get_text() or None
+ kargs = self.widget("kernel-args").get_text() or None
+ initrd = self.widget("initrd").get_text() or None
+ dtb = self.widget("dtb").get_text() or None
+
+ if not self.widget("dtb").get_visible():
+ dtb = None
+ if not self.widget("kernel").get_visible():
+ kernel = None
+ initrd = None
+ kargs = None
+
+ self._guest.os.kernel = kernel
+ self._guest.os.initrd = initrd
+ self._guest.os.dtb = dtb
+ self._guest.os.kernel_args = kargs
+
+ try:
+ name = virtinst.Guest.generate_name(self._guest)
+ self.widget("create-vm-name").set_text(name)
+ self._guest.validate_name(self._guest.conn, name)
+ self._guest.name = name
+ except Exception as e:
+ return self.err.val_err(_("Error setting default name."), e)
+
+ # Kind of wonky, run storage validation now, which will assign
+ # the import path. Import installer skips the storage page.
+ if is_import:
+ if not self._validate_storage_page():
+ return False
+
+ for path in installer.get_search_paths(self._guest):
+ self._addstorage.check_path_search(
+ self, self.conn, path)
+
+ res = self._guest.osinfo.get_recommended_resources()
+ ram = res.get_recommended_ram(self._guest.os.arch)
+ n_cpus = res.get_recommended_ncpus(self._guest.os.arch)
+ storage = res.get_recommended_storage(self._guest.os.arch)
+ log.debug("Recommended resources for os=%s: "
+ "ram=%s ncpus=%s storage=%s",
+ self._guest.osinfo.name, ram, n_cpus, storage)
+
+ # Change the default values suggested to the user.
+ ram_size = DEFAULT_MEM
+ if ram:
+ ram_size = ram // (1024 ** 2)
+ self.widget("mem").set_value(ram_size)
+
+ self.widget("cpus").set_value(n_cpus or 1)
+
+ if storage:
+ storage_size = storage // (1024 ** 3)
+ self._addstorage.widget("storage-size").set_value(storage_size)
+
+ # Stash the installer in the _guest instance so we don't need
+ # to cache both objects individually
+ self._guest.installer_instance = installer
+
+ # Validation passed, store the install path (if there is one) in
+ # gsettings
+ self._get_config_oscontainer_source_url(store_media=True)
+ self._get_config_local_media(store_media=True)
+ self._get_config_url_info(store_media=True)
+ return True
+
+ def _validate_mem_page(self):
+ cpus = self.widget("cpus").get_value()
+ mem = self.widget("mem").get_value()
+
+ # VCPUS
+ try:
+ self._guest.vcpus = int(cpus)
+ except Exception as e:
+ return self.err.val_err(_("Error setting CPUs."), e)
+
+ # Memory
+ try:
+ self._guest.currentMemory = int(mem) * 1024
+ self._guest.memory = int(mem) * 1024
+ except Exception as e:
+ return self.err.val_err(_("Error setting guest memory."), e)
+
+ return True
+
+ def _get_storage_path(self, vmname, do_log):
+ failed_disk = None
+ if self._failed_guest:
+ failed_disk = _get_vmm_device(self._failed_guest, "disk")
+
+ path = None
+ path_already_created = False
+
+ if self._get_config_install_page() == INSTALL_PAGE_IMPORT:
+ path = self._get_config_import_path()
+
+ elif self._is_default_storage():
+ if failed_disk:
+ # Don't generate a new path if the install failed
+ path = failed_disk.path
+ path_already_created = failed_disk.storage_was_created
+ if do_log:
+ log.debug("Reusing failed disk path=%s "
+ "already_created=%s", path, path_already_created)
+ else:
+ path = self._addstorage.get_default_path(vmname)
+ if do_log:
+ log.debug("Default storage path is: %s", path)
+
+ return path, path_already_created
+
+ def _validate_storage_page(self):
+ path, path_already_created = self._get_storage_path(
+ self._guest.name, do_log=True)
+
+ disk = None
+ storage_enabled = self.widget("enable-storage").get_active()
+ try:
+ if storage_enabled:
+ disk = self._addstorage.build_device(
+ self._guest.name, path=path)
+
+ if disk and self._addstorage.validate_device(disk) is False:
+ return False
+ except Exception as e:
+ return self.err.val_err(_("Storage parameter error."), e)
+
+ if self._get_config_install_page() == INSTALL_PAGE_ISO:
+ # CD/ISO install and no disks implies LiveCD
+ self._guest.installer_instance.livecd = not storage_enabled
+
+ _remove_vmm_device(self._guest, "disk")
+
+ if not storage_enabled:
+ return True
+
+ disk.storage_was_created = path_already_created
+ _mark_vmm_device(disk)
+ self._guest.add_device(disk)
+
+ return True
+
+
+ def _validate_final_page(self):
+ # HV + Arch selection
+ name = self._get_config_name()
+ if name != self._guest.name:
+ try:
+ self._guest.validate_name(self._guest.conn, name)
+ self._guest.name = name
+ except Exception as e:
+ return self.err.val_err(_("Invalid guest name"), str(e))
+ if self._is_default_storage():
+ log.debug("User changed VM name and using default "
+ "storage, re-validating with new default storage path.")
+ # User changed the name and we are using default storage
+ # which depends on the VM name. Revalidate things
+ if not self._validate_storage_page():
+ return False
+
+ nettype = self._netlist.get_network_selection()[0]
+ if nettype is None:
+ # No network device available
+ instmethod = self._get_config_install_page()
+ methname = None
+ if instmethod == INSTALL_PAGE_PXE:
+ methname = "PXE"
+ elif instmethod == INSTALL_PAGE_URL:
+ methname = "URL"
+
+ if methname:
+ return self.err.val_err(
+ _("Network device required for %s install.") %
+ methname)
+
+ macaddr = virtinst.DeviceInterface.generate_mac(
+ self.conn.get_backend())
+
+ net = self._netlist.build_device(macaddr)
+ self._netlist.validate_device(net)
+
+ _remove_vmm_device(self._guest, "interface")
+ if net:
+ _mark_vmm_device(net)
+ self._guest.add_device(net)
+
+ return True
+
+
+ #############################
+ # Distro detection handling #
+ #############################
+
+ def _start_detect_os_if_needed(self, forward_after_finish=False):
+ """
+ Will kick off the OS detection thread if all conditions are met,
+ like we actually have media to detect, detection isn't already
+ in progress, etc.
+
+ Returns True if we actually start the detection process
+ """
+ is_install_page = (self.widget("create-pages").get_current_page() ==
+ PAGE_INSTALL)
+ cdrom, location = self._get_config_detectable_media()
+
+ if self._detect_os_in_progress:
+ return
+ if not is_install_page:
+ return
+ if not cdrom and not location:
+ return
+ if not self._is_os_detect_active():
+ return
+ if self._os_already_detected_for_media:
+ return
+
+ self._do_start_detect_os(cdrom, location, forward_after_finish)
+ return True
+
+ def _do_start_detect_os(self, cdrom, location, forward_after_finish):
+ self._detect_os_in_progress = False
+
+ log.debug("Starting OS detection thread for cdrom=%s location=%s",
+ cdrom, location)
+ self.widget("create-forward").set_sensitive(False)
+
+ class ThreadResults(object):
+ """
+ Helper object to track results from the detection thread
+ """
+ _DETECT_FAILED = 1
+ _DETECT_INPROGRESS = 2
+ def __init__(self):
+ self._results = self._DETECT_INPROGRESS
+
+ def in_progress(self):
+ return self._results == self._DETECT_INPROGRESS
+
+ def set_failed(self):
+ self._results = self._DETECT_FAILED
+
+ def set_distro(self, distro):
+ self._results = distro
+ def get_distro(self):
+ if self._results == self._DETECT_FAILED:
+ return None
+ return self._results
+
+ thread_results = ThreadResults()
+ detectThread = threading.Thread(target=self._detect_thread_cb,
+ name="Actual media detection",
+ args=(cdrom, location, thread_results))
+ detectThread.setDaemon(True)
+ detectThread.start()
+
+ self._os_list.search_entry.set_text(_("Detecting..."))
+ spin = self.widget("install-detect-os-spinner")
+ spin.start()
+
+ self._report_detect_os_progress(0, thread_results,
+ forward_after_finish)
+
+ def _detect_thread_cb(self, cdrom, location, thread_results):
+ """
+ Thread callback that does the actual detection
+ """
+ try:
+ installer = virtinst.Installer(self.conn.get_backend(),
+ cdrom=cdrom,
+ location=location)
+ distro = installer.detect_distro(self._guest)
+ thread_results.set_distro(distro)
+ except Exception:
+ log.exception("Error detecting distro.")
+ thread_results.set_failed()
+
+ def _report_detect_os_progress(self, idx, thread_results,
+ forward_after_finish):
+ """
+ Checks detection progress via the _detect_os_results variable
+ and updates the UI labels, counts the number of iterations,
+ etc.
+
+ We set a hard time limit on the distro detection to avoid the
+ chance of the detection hanging (like slow URL lookup)
+ """
+ try:
+ if (thread_results.in_progress() and
+ (idx < (DETECT_TIMEOUT * 2))):
+ # Thread is still going and we haven't hit the timeout yet,
+ # so update the UI labels and reschedule this function
+ self.timeout_add(500, self._report_detect_os_progress,
+ idx + 1, thread_results, forward_after_finish)
+ return
+
+ distro = thread_results.get_distro()
+ except Exception:
+ distro = None
+ log.exception("Error in distro detect timeout")
+
+ spin = self.widget("install-detect-os-spinner")
+ spin.stop()
+ log.debug("Finished UI OS detection.")
+
+ self.widget("create-forward").set_sensitive(True)
+ self._os_already_detected_for_media = True
+ self._detect_os_in_progress = False
+
+ if not self._is_os_detect_active():
+ # If the user changed the OS detect checkbox in the meantime,
+ # don't update the UI
+ return
+
+ if distro:
+ self._os_list.select_os(virtinst.OSDB.lookup_os(distro))
+ else:
+ self._os_list.reset_state()
+ self._os_list.search_entry.set_text(_("None detected"))
+
+ if forward_after_finish:
+ self.idle_add(self._forward_clicked, ())
+
+
+ ##########################
+ # Guest install routines #
+ ##########################
+
+ def _finish_clicked(self, src_ignore):
+ # Validate the final page
+ page = self.widget("create-pages").get_current_page()
+ if self._validate(page) is not True:
+ return False
+
+ log.debug("Starting create finish() sequence")
+ self._failed_guest = None
+ guest = self._guest
+
+ try:
+ self.set_finish_cursor()
+
+ # This encodes all the virtinst defaults up front, so the customize
+ # dialog actually shows disk buses, cache values, default devices,
+ # etc. Not required for straight start_install but doesn't hurt.
+ guest.installer_instance.set_install_defaults(guest)
+
+ if not self.widget("summary-customize").get_active():
+ self._start_install(guest)
+ return
+
+ log.debug("User requested 'customize', launching dialog")
+ self._show_customize_dialog(self._guest)
+ except Exception as e:
+ self.reset_finish_cursor()
+ self.err.show_err(_("Error starting installation: ") + str(e))
+ return
+
+ def _cleanup_customize_window(self):
+ if not self._customize_window:
+ return
+
+ # We can re-enter this: cleanup() -> close() -> "details-closed"
+ window = self._customize_window
+ self._customize_window = None
+ window.cleanup()
+
+ def _show_customize_dialog(self, origguest):
+ orig_vdomain = vmmDomainVirtinst(self.conn, origguest, origguest.uuid)
+
+ def customize_finished_cb(src, vdomain):
+ if not self.is_visible():
+ return
+ log.debug("User finished customize dialog, starting install")
+ self._failed_guest = None
+ self._start_install(vdomain.get_backend())
+
+ def config_canceled_cb(src):
+ log.debug("User closed customize window, closing wizard")
+ self._close_requested()
+
+ # We specifically don't use vmmVMWindow.get_instance here since
+ # it's not a top level VM window
+ self._cleanup_customize_window()
+ self._customize_window = vmmVMWindow(orig_vdomain, self.topwin)
+ self._customize_window.connect(
+ "customize-finished", customize_finished_cb)
+ self._customize_window.connect("closed", config_canceled_cb)
+ self._customize_window.show()
+
+ def _install_finished_cb(self, error, details, guest, parentobj):
+ self.reset_finish_cursor(parentobj.topwin)
+
+ if error:
+ error = (_("Unable to complete install: '%s'") % error)
+ parentobj.err.show_err(error, details=details)
+ self._failed_guest = guest
+ return
+
+ foundvm = None
+ for vm in self.conn.list_vms():
+ if vm.get_uuid() == guest.uuid:
+ foundvm = vm
+ break
+
+ self._close()
+
+ # Launch details dialog for new VM
+ vmmVMWindow.get_instance(self, foundvm).show()
+
+
+ def _start_install(self, guest):
+ """
+ Launch the async job to start the install
+ """
+ bootstrap_args = {}
+ # If creating new container and "container bootstrap" is enabled
+ if (guest.os.is_container() and
+ self._get_config_oscontainer_bootstrap()):
+ bootstrap_arg_keys = {
+ 'src': self._get_config_oscontainer_source_url,
+ 'dest': self.widget("install-oscontainer-fs").get_text,
+ 'user': self._get_config_oscontainer_source_username,
+ 'passwd': self._get_config_oscontainer_source_password,
+ 'insecure': self._get_config_oscontainer_isecure,
+ 'root_password': self._get_config_oscontainer_root_password,
+ }
+ for key, getter in bootstrap_arg_keys.items():
+ bootstrap_args[key] = getter()
+
+ parentobj = self._customize_window or self
+ progWin = vmmAsyncJob(self._do_async_install, [guest, bootstrap_args],
+ self._install_finished_cb, [guest, parentobj],
+ _("Creating Virtual Machine"),
+ _("The virtual machine is now being "
+ "created. Allocation of disk storage "
+ "and retrieval of the installation "
+ "images may take a few minutes to "
+ "complete."),
+ parentobj.topwin)
+ progWin.run()
+
+ def _do_async_install(self, asyncjob, guest, bootstrap_args):
+ """
+ Kick off the actual install
+ """
+ meter = asyncjob.get_meter()
+
+ if bootstrap_args:
+ # Start container bootstrap
+ self._create_directory_tree(asyncjob, meter, bootstrap_args)
+ if asyncjob.has_error():
+ # Do not continue if virt-bootstrap failed
+ return
+
+ # Build a list of pools we should refresh, if we are creating storage
+ refresh_pools = []
+ for disk in guest.devices.disk:
+ if not disk.wants_storage_creation():
+ continue
+
+ pool = disk.get_parent_pool()
+ if not pool:
+ continue
+
+ poolname = pool.name()
+ if poolname not in refresh_pools:
+ refresh_pools.append(poolname)
+
+ log.debug("Starting background install process")
+ guest.installer_instance.start_install(guest, meter=meter)
+ log.debug("Install completed")
+
+ # Wait for VM to show up
+ self.conn.schedule_priority_tick(pollvm=True)
+ count = 0
+ foundvm = None
+ while count < 200:
+ for vm in self.conn.list_vms():
+ if vm.get_uuid() == guest.uuid:
+ foundvm = vm
+ if foundvm:
+ break
+ count += 1
+ time.sleep(.1)
+
+ if not foundvm:
+ raise RuntimeError(
+ _("VM '%s' didn't show up after expected time.") % guest.name)
+ vm = foundvm
+
+ if vm.is_shutoff():
+ # Domain is already shutdown, but no error was raised.
+ # Probably means guest had no 'install' phase, as in
+ # for live cds. Try to restart the domain.
+ vm.startup()
+ elif guest.installer_instance.has_install_phase():
+ # Register a status listener, which will restart the
+ # guest after the install has finished
+ def cb():
+ vm.connect_opt_out("state-changed",
+ self._check_install_status)
+ return False
+ self.idle_add(cb)
+
+ # Kick off pool updates
+ for poolname in refresh_pools:
+ try:
+ pool = self.conn.get_pool(poolname)
+ self.idle_add(pool.refresh)
+ except Exception:
+ log.debug("Error looking up pool=%s for refresh after "
+ "VM creation.", poolname, exc_info=True)
+
+
+ def _check_install_status(self, vm):
+ """
+ Watch the domain that we are installing, waiting for the state
+ to change, so we can restart it as needed
+ """
+ if vm.is_crashed():
+ log.debug("VM crashed, cancelling install plans.")
+ return True
+
+ if not vm.is_shutoff():
+ return
+
+ if vm.get_install_abort():
+ log.debug("User manually shutdown VM, not restarting "
+ "guest after install.")
+ return True
+
+ try:
+ log.debug("Install should be completed, starting VM.")
+ vm.startup()
+ except Exception as e:
+ self.err.show_err(_("Error continue install: %s") % str(e))
+
+ return True
+
+
+ def _create_directory_tree(self, asyncjob, meter, bootstrap_args):
+ """
+ Call bootstrap method from virtBootstrap and show logger messages
+ as state/details.
+ """
+ import logging
+ import virtBootstrap
+
+ meter.start(text=_("Bootstraping container"), size=100)
+ def progress_update_cb(prog):
+ meter.text = _(prog['status'])
+ meter.update(prog['value'])
+
+ asyncjob.details_enable()
+ # Use logging filter to show messages of the progreess on the GUI
+ class SetStateFilter(logging.Filter):
+ def filter(self, record):
+ asyncjob.details_update("%s\n" % record.getMessage())
+ return True
+
+ # Use string buffer to store log messages
+ log_stream = io.StringIO()
+
+ # Get virt-bootstrap logger
+ vbLogger = logging.getLogger('virtBootstrap')
+ vbLogger.setLevel(logging.DEBUG)
+ # Create handler to store log messages in the string buffer
+ hdlr = logging.StreamHandler(log_stream)
+ hdlr.setFormatter(logging.Formatter('%(message)s'))
+ # Use logging filter to show messages on GUI
+ hdlr.addFilter(SetStateFilter())
+ vbLogger.addHandler(hdlr)
+
+ # Key word arguments to be passed
+ kwargs = {'uri': bootstrap_args['src'],
+ 'dest': bootstrap_args['dest'],
+ 'not_secure': bootstrap_args['insecure'],
+ 'progress_cb': progress_update_cb}
+ if bootstrap_args['user'] and bootstrap_args['passwd']:
+ kwargs['username'] = bootstrap_args['user']
+ kwargs['password'] = bootstrap_args['passwd']
+ if bootstrap_args['root_password']:
+ kwargs['root_password'] = bootstrap_args['root_password']
+ log.debug('Start container bootstrap')
+ try:
+ virtBootstrap.bootstrap(**kwargs)
+ # Success - uncheck the 'install-oscontainer-bootstrap' checkbox
+
+ def cb():
+ self.widget("install-oscontainer-bootstrap").set_active(False)
+ self.idle_add(cb)
+ except Exception as err:
+ asyncjob.set_error("virt-bootstrap did not complete successfully",
+ '%s\n%s' % (err, log_stream.getvalue()))