details: Add UI for enabling UEFI via 'customize before install'

We expose a simple combobox with two entries: BIOS, and UEFI. The
UEFI option is only selectable if 1) libvirt supports the necessary
domcapabilities bits, 2) it detects that qemu supports the necessary
command line options, and 3) libvirt detects a UEFI binary on the
host that maps to a known template via qemu.conf

If those conditions aren't met, we disable the UEFI option, and show
a small warning icon with an explanatory tooltip.

The option can only be changed via New VM->Customize Before Install.
For existing x86 VMs, it's a readonly label.
This commit is contained in:
Cole Robinson 2014-09-17 17:25:03 -04:00
parent ead9d3f56d
commit ae26313ed4
4 changed files with 183 additions and 8 deletions

View File

@ -957,6 +957,7 @@
<object class="GtkLabel" id="overview-machine-label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">start</property>
<property name="xalign">1</property>
<property name="label" translatable="yes">Machine _Type: </property>
<property name="use_underline">True</property>
@ -982,16 +983,15 @@
<object class="GtkLabel" id="overview-chipset-title">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">2</property>
<property name="valign">start</property>
<property name="xalign">1</property>
<property name="yalign">0</property>
<property name="label" translatable="yes">Chipse_t:</property>
<property name="use_underline">True</property>
<property name="ellipsize">middle</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">4</property>
<property name="top_attach">5</property>
</packing>
</child>
<child>
@ -1061,6 +1061,8 @@ if you know what you are doing.&lt;/small&gt;</property>
<object class="GtkLabel" id="overview-chipset-label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">start</property>
<property name="vexpand">True</property>
<property name="xalign">0</property>
<property name="label">label</property>
</object>
@ -1077,6 +1079,79 @@ if you know what you are doing.&lt;/small&gt;</property>
</packing>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">5</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="overview-firmware-title">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">start</property>
<property name="xalign">1</property>
<property name="label" translatable="yes">Firmware:</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">4</property>
</packing>
</child>
<child>
<object class="GtkBox" id="box11">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">3</property>
<child>
<object class="GtkBox" id="box16">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkComboBox" id="overview-firmware">
<property name="visible">True</property>
<property name="can_focus">False</property>
<signal name="changed" handler="on_overview_firmware_changed" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="overview-firmware-label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">start</property>
<property name="vexpand">False</property>
<property name="label">label</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkImage" id="overview-firmware-warn">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-dialog-warning</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">4</property>
@ -1310,9 +1385,6 @@ if you know what you are doing.&lt;/small&gt;</property>
<property name="position">2</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
</child>
<child type="tab">

View File

@ -48,6 +48,7 @@ from virtinst import VirtualRNGDevice
(EDIT_NAME,
EDIT_TITLE,
EDIT_MACHTYPE,
EDIT_FIRMWARE,
EDIT_DESC,
EDIT_IDMAP,
@ -103,7 +104,7 @@ EDIT_FS,
EDIT_HOSTDEV_ROMBAR,
) = range(1, 44)
) = range(1, 45)
# Columns in hw list model
@ -398,6 +399,18 @@ def _chipset_label_from_machine(machine):
return "i440FX"
def _firmware_label_from_loader(vm, loader, force_uefi=False):
domcaps = vm.get_domain_capabilities()
if (domcaps.os.loader.values and
loader == domcaps.os.loader.values[0].value) or force_uefi:
return "UEFI"
if loader is None:
return "BIOS"
return "Custom"
class vmmDetails(vmmGObjectUI):
__gsignals__ = {
"action-save-domain": (GObject.SignalFlags.RUN_FIRST, None, [str, str]),
@ -548,6 +561,7 @@ class vmmDetails(vmmGObjectUI):
"on_overview_name_changed": lambda *x: self.enable_apply(x, EDIT_NAME),
"on_overview_title_changed": lambda *x: self.enable_apply(x, EDIT_TITLE),
"on_machine_type_changed": lambda *x: self.enable_apply(x, EDIT_MACHTYPE),
"on_overview_firmware_changed": lambda *x: self.enable_apply(x, EDIT_FIRMWARE),
"on_overview_chipset_changed": lambda *x: self.enable_apply(x, EDIT_MACHTYPE),
"on_idmap_uid_target_changed": lambda *x: self.enable_apply(x, EDIT_IDMAP),
"on_idmap_uid_count_changed": lambda *x: self.enable_apply(x, EDIT_IDMAP),
@ -873,6 +887,49 @@ class vmmDetails(vmmGObjectUI):
continue
machtype_model.append([machine])
# Firmware
combo = self.widget("overview-firmware")
model = Gtk.ListStore(str, str, bool)
combo.set_model(model)
text = Gtk.CellRendererText()
combo.pack_start(text, True)
combo.add_attribute(text, "text", 0)
combo.add_attribute(text, "sensitive", 2)
domcaps = self.vm.get_domain_capabilities()
uefipath = None
if domcaps.os.loader.values:
uefipath = domcaps.os.loader.values[0].value
warn_icon = self.widget("overview-firmware-warn")
hv_supports_uefi = ("readonly" in domcaps.os.loader.enum_names() and
"yes" in domcaps.os.loader.get_enum("readonly").get_values())
if not hv_supports_uefi:
warn_icon.set_tooltip_text(
_("Libvirt or hypervisor does not support UEFI."))
elif not uefipath:
warn_icon.set_tooltip_text(
_("Libvirt did not detect any UEFI/OVMF firmware image "
"installed on the host."))
model.append([_firmware_label_from_loader(self.vm, None),
None, True])
model.append([_firmware_label_from_loader(
self.vm, uefipath, force_uefi=True), uefipath,
bool(uefipath and hv_supports_uefi)])
combo.set_active(0)
self.widget("overview-firmware-warn").set_visible(
not (uefipath and hv_supports_uefi) and self.is_customize_dialog)
self.widget("overview-firmware").set_visible(self.is_customize_dialog)
self.widget("overview-firmware-label").set_visible(
not self.is_customize_dialog)
show_firmware = ((self.conn.is_qemu() or self.conn.is_test_conn()) and
arch in ["i686", "x86_64"] and
not self.vm.is_management_domain())
uiutil.set_grid_row_visible(
self.widget("overview-firmware-title"), show_firmware)
# Chipset
combo = self.widget("overview-chipset")
model = Gtk.ListStore(str, str)
@ -1974,6 +2031,10 @@ class vmmDetails(vmmGObjectUI):
kwargs["title"] = self.widget("overview-title").get_text()
hotplug_args["title"] = kwargs["title"]
if self.edited(EDIT_FIRMWARE):
kwargs["loader"] = uiutil.get_list_selection(
self.widget("overview-firmware"), 1)
if self.edited(EDIT_MACHTYPE):
if self.widget("overview-chipset").is_visible():
kwargs["machine"] = uiutil.get_list_selection(
@ -2402,6 +2463,15 @@ class vmmDetails(vmmGObjectUI):
self.widget("overview-arch").set_text(arch)
self.widget("overview-emulator").set_text(emu)
# Firmware
firmware = _firmware_label_from_loader(self.vm,
self.vm.get_xmlobj().os.loader)
if self.widget("overview-firmware").is_visible():
uiutil.set_combo_entry(
self.widget("overview-firmware"), firmware)
elif self.widget("overview-firmware-label").is_visible():
self.widget("overview-firmware-label").set_text(firmware)
# Machine settings
machtype = self.vm.get_machtype()
if arch not in ["i686", "x86_64"]:

View File

@ -27,6 +27,7 @@ import threading
import libvirt
from virtinst import DomainCapabilities
from virtinst import DomainSnapshot
from virtinst import Guest
from virtinst import util
@ -301,6 +302,7 @@ class vmmDomain(vmmLibvirtObject):
self._has_managed_save = None
self._snapshot_list = None
self._autostart = None
self._domain_caps = None
self.lastStatus = libvirt.VIR_DOMAIN_SHUTOFF
self._lastStatusReason = getattr(libvirt, "VIR_DOMAIN_SHUTOFF_SHUTDOWN",
@ -505,6 +507,20 @@ class vmmDomain(vmmLibvirtObject):
return _("Snapshots require at least one writeable qcow2 disk "
"image allocated to the guest.")
def get_domain_capabilities(self):
if not self.conn.check_support(
self.conn.SUPPORT_CONN_DOMAIN_CAPABILITIES):
self._domain_caps = DomainCapabilities(self.conn.get_backend())
if not self._domain_caps:
xml = self.conn.get_backend().getDomainCapabilities(
self.get_xmlobj().emulator, self.get_xmlobj().os.arch,
self.get_xmlobj().os.machine, self.get_xmlobj().type)
self._domain_caps = DomainCapabilities(self.conn.get_backend(),
parsexml=xml)
return self._domain_caps
#############################
# Internal XML handling API #
@ -619,7 +635,7 @@ class vmmDomain(vmmLibvirtObject):
newname)
def define_overview(self, machine=_SENTINEL, description=_SENTINEL,
title=_SENTINEL, idmap_list=_SENTINEL):
title=_SENTINEL, idmap_list=_SENTINEL, loader=_SENTINEL):
def change(guest):
if machine != _SENTINEL:
guest.os.machine = machine
@ -628,6 +644,20 @@ class vmmDomain(vmmLibvirtObject):
if title != _SENTINEL:
guest.title = title or None
if loader != _SENTINEL:
if loader is None:
# Implies seabios, aka the default, so clear everything
guest.os.loader = None
guest.os.loader_ro = None
guest.os.loader_type = None
guest.os.nvram = None
guest.os.nvram_template = None
else:
# Implies UEFI
guest.os.loader = loader
guest.os.loader_type = "pflash"
guest.os.loader_ro = True
if idmap_list != _SENTINEL:
if idmap_list is not None:
# pylint: disable=unpacking-non-sequence

View File

@ -298,6 +298,9 @@ SUPPORT_CONN_HYPERV_VAPIC = _make(
SUPPORT_CONN_HYPERV_CLOCK = _make(
version="1.2.2", hv_version={"qemu": "2.0.0", "test": 0})
SUPPORT_CONN_LOADER_ROM = _make(version="1.2.9")
SUPPORT_CONN_DOMAIN_CAPABILITIES = _make(
function="virConnect.getDomainCapabilities",
run_args=(None, None, None, None))
# Domain checks