virt-install: Add --boot uefi

Will enable UEFI if we know libvirt + hypervisor support it, and libvirt
is advertising a suitable UEFI binary.
This commit is contained in:
Cole Robinson 2015-02-18 15:16:48 -05:00
parent 760465fd2b
commit a04307cd70
8 changed files with 131 additions and 46 deletions

View File

@ -451,28 +451,6 @@ configurations like ARM or PPC
Use BIOSPATH as the virtual machine BIOS. Use BIOSPATH as the virtual machine BIOS.
=item B<--boot loader=/usr/share/OVMF/OVMF_CODE.fd,loader_ro=yes,loader_type=pflash>
Specify that the virtual machine use the system-wide installed OVMF binary as
boot firmware, mapped as a virtual flash chip. This form requests that libvirt
instantiate the VM-specific UEFI varstore from the varstore template that is
assigned to "/usr/share/OVMF/OVMF_CODE.fd" in libvirtd's "qemu.conf", in the
"nvram" stanza.
=item B<--boot loader=/.../OVMF_CODE.fd,loader_ro=yes,loader_type=pflash,nvram_template=/.../OVMF_VARS.fd>
Specify that the virtual machine use the custom OVMF binary as boot firmware,
mapped as a virtual flash chip. In addition, request that libvirt instantiate
the VM-specific UEFI varstore from the custom "/.../OVMF_VARS.fd" varstore
template.
=item B<--boot loader=/.../OVMF_CODE.fd,loader_ro=yes,loader_type=pflash,nvram=/.../guest_VARS.fd>
Specify that the virtual machine use the custom OVMF binary as boot firmware,
mapped as a virtual flash chip. In addition, the VM-specific UEFI varstore is
directly the preexistent file "/.../guest_VARS.fd"; it is not instantiated from
any template.
=item B<--boot menu=on,useserial=on> =item B<--boot menu=on,useserial=on>
Enable the bios boot menu, and enable sending bios text output over Enable the bios boot menu, and enable sending bios text output over
@ -484,6 +462,21 @@ Path to a binary that the container guest will init. If a root C<--filesystem>
has been specified, virt-install will default to /sbin/init, otherwise has been specified, virt-install will default to /sbin/init, otherwise
will default to /bin/sh. will default to /bin/sh.
=item B<--boot uefi>
Configure the VM to boot from UEFI. In order for virt-install to know the
correct UEFI parameters, libvirt needs to be advertising known UEFI binaries
via domcapabilities XML, so this will likely only work if using properly
configured distro packages.
=item B<--boot loader=/.../OVMF_CODE.fd,loader_ro=yes,loader_type=pflash,nvram_template=/.../OVMF_VARS.fd>
Specify that the virtual machine use the custom OVMF binary as boot firmware,
mapped as a virtual flash chip. In addition, request that libvirt instantiate
the VM-specific UEFI varstore from the custom "/.../OVMF_VARS.fd" varstore
template. This is the recommended UEFI setup, and should be used if
--boot uefi doesn't know about your UEFI binaries.
=back =back
Use --boot=? to see a list of all available sub options. Complete details at L<http://libvirt.org/formatdomain.html#elementsOS> Use --boot=? to see a list of all available sub options. Complete details at L<http://libvirt.org/formatdomain.html#elementsOS>

View File

@ -13,9 +13,8 @@
</numatune> </numatune>
<os> <os>
<type arch="x86_64">hvm</type> <type arch="x86_64">hvm</type>
<boot dev="network"/> <loader readonly="yes" type="pflash">/usr/share/OVMF/OVMF_CODE.fd</loader>
<boot dev="hd"/> <boot dev="hd"/>
<bootmenu enable="yes"/>
</os> </os>
<features> <features>
<acpi/> <acpi/>

View File

@ -460,7 +460,7 @@ c.add_compare(""" \
--vcpus 4 --cpuset=1,3-5 \ --vcpus 4 --cpuset=1,3-5 \
--cpu host \ --cpu host \
--description \"foobar & baz\" \ --description \"foobar & baz\" \
--boot network,hd,menu=on \ --boot uefi \
--security type=dynamic \ --security type=dynamic \
--numatune 1,2,3,5-7,^6 \ --numatune 1,2,3,5-7,^6 \
--memorybacking hugepages=on \ --memorybacking hugepages=on \
@ -675,6 +675,8 @@ c.add_invalid("--graphics spice,tlsport=5") # Invalid port
c.add_invalid("--serial unix") # Unix with no path c.add_invalid("--serial unix") # Unix with no path
c.add_invalid("--serial null,path=/tmp/foo") # Path where it doesn't belong c.add_invalid("--serial null,path=/tmp/foo") # Path where it doesn't belong
c.add_invalid("--channel pty,target_type=guestfwd") # --channel guestfwd without target_address c.add_invalid("--channel pty,target_type=guestfwd") # --channel guestfwd without target_address
c.add_invalid("--boot uefi") # URI doesn't support UEFI bits
c.add_invalid("--connect %(KVMURI)s --boot uefi,arch=ppc64") # unsupported arch for UEFI

View File

@ -803,8 +803,7 @@ class vmmDetails(vmmGObjectUI):
uefipath = domcaps.os.loader.values[0].value uefipath = domcaps.os.loader.values[0].value
warn_icon = self.widget("overview-firmware-warn") warn_icon = self.widget("overview-firmware-warn")
hv_supports_uefi = ("readonly" in domcaps.os.loader.enum_names() and hv_supports_uefi = domcaps.supports_uefi_xml()
"yes" in domcaps.os.loader.get_enum("readonly").get_values())
if not hv_supports_uefi: if not hv_supports_uefi:
warn_icon.set_tooltip_text( warn_icon.set_tooltip_text(
_("Libvirt or hypervisor does not support UEFI.")) _("Libvirt or hypervisor does not support UEFI."))
@ -826,7 +825,7 @@ class vmmDetails(vmmGObjectUI):
self.widget("overview-firmware-label").set_visible( self.widget("overview-firmware-label").set_visible(
not self.is_customize_dialog) not self.is_customize_dialog)
show_firmware = ((self.conn.is_qemu() or self.conn.is_test_conn()) and show_firmware = ((self.conn.is_qemu() or self.conn.is_test_conn()) and
arch in ["i686", "x86_64"] and domcaps.arch_can_uefi(arch) and
not self.vm.is_management_domain()) not self.vm.is_management_domain())
uiutil.set_grid_row_visible( uiutil.set_grid_row_visible(
self.widget("overview-firmware-title"), show_firmware) self.widget("overview-firmware-title"), show_firmware)

View File

@ -508,17 +508,9 @@ class vmmDomain(vmmLibvirtObject):
"image allocated to the guest.") "image allocated to the guest.")
def get_domain_capabilities(self): 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: if not self._domain_caps:
xml = self.conn.get_backend().getDomainCapabilities( self._domain_caps = DomainCapabilities.build_from_guest(
self.get_xmlobj().emulator, self.get_xmlobj().os.arch, self.get_xmlobj())
self.get_xmlobj().os.machine, self.get_xmlobj().type)
self._domain_caps = DomainCapabilities(self.conn.get_backend(),
parsexml=xml)
return self._domain_caps return self._domain_caps

View File

@ -696,7 +696,7 @@ class _VirtCLIArgument(object):
setter_cb=None, ignore_default=False, setter_cb=None, ignore_default=False,
can_comma=False, aliases=None, can_comma=False, aliases=None,
is_list=False, is_onoff=False, is_list=False, is_onoff=False,
lookup_cb=None): lookup_cb=None, is_novalue=False):
""" """
A single subargument passed to compound command lines like --disk, A single subargument passed to compound command lines like --disk,
--network, etc. --network, etc.
@ -722,6 +722,8 @@ class _VirtCLIArgument(object):
it to true/false. it to true/false.
@lookup_cb: If specified, use this function for performing match @lookup_cb: If specified, use this function for performing match
lookups. lookups.
@is_novalue: If specified, the parameter is not expected in the
form FOO=BAR, but just FOO.
""" """
self.attrname = attrname self.attrname = attrname
self.cliname = cliname self.cliname = cliname
@ -733,6 +735,7 @@ class _VirtCLIArgument(object):
self.is_list = is_list self.is_list = is_list
self.is_onoff = is_onoff self.is_onoff = is_onoff
self.lookup_cb = lookup_cb self.lookup_cb = lookup_cb
self.is_novalue = is_novalue
def parse(self, opts, inst, support_cb=None, lookup=False): def parse(self, opts, inst, support_cb=None, lookup=False):
@ -740,7 +743,7 @@ class _VirtCLIArgument(object):
for cliname in self.aliases + [self.cliname]: for cliname in self.aliases + [self.cliname]:
# We iterate over all values unconditionally, so they are # We iterate over all values unconditionally, so they are
# removed from opts # removed from opts
foundval = opts.get_opt_param(cliname) foundval = opts.get_opt_param(cliname, self.is_novalue)
if foundval is not None: if foundval is not None:
val = foundval val = foundval
if val is None: if val is None:
@ -809,12 +812,16 @@ class VirtOptionString(object):
self.opts, self.orderedopts = self._parse_optstr( self.opts, self.orderedopts = self._parse_optstr(
virtargmap, remove_first) virtargmap, remove_first)
def get_opt_param(self, key): def get_opt_param(self, key, is_novalue=False):
if key not in self.opts: if key not in self.opts:
return None return None
ret = self.opts.pop(key) ret = self.opts.pop(key)
if ret is None: if ret is None:
raise RuntimeError("Option '%s' had no value set." % key) if not is_novalue:
raise RuntimeError("Option '%s' had no value set." % key)
ret = ""
return ret return ret
def check_leftover_opts(self): def check_leftover_opts(self):
@ -1280,6 +1287,19 @@ class ParserBoot(VirtCLIParser):
def _init_params(self): def _init_params(self):
self.clear_attr = "os" self.clear_attr = "os"
# UEFI depends on these bits, so set them first
self.set_param("os.arch", "arch")
self.set_param("type", "domain_type")
self.set_param("os.os_type", "os_type")
self.set_param("emulator", "emulator")
def set_uefi(opts, inst, cliname, val):
ignore = opts
ignore = cliname
ignore = val
inst.set_uefi_default()
self.set_param(None, "uefi", setter_cb=set_uefi, is_novalue=True)
self.set_param("os.useserial", "useserial", is_onoff=True) self.set_param("os.useserial", "useserial", is_onoff=True)
self.set_param("os.enable_bootmenu", "menu", is_onoff=True) self.set_param("os.enable_bootmenu", "menu", is_onoff=True)
self.set_param("os.kernel", "kernel") self.set_param("os.kernel", "kernel")
@ -1293,11 +1313,7 @@ class ParserBoot(VirtCLIParser):
self.set_param("os.kernel_args", "kernel_args", self.set_param("os.kernel_args", "kernel_args",
aliases=["extra_args"], can_comma=True) aliases=["extra_args"], can_comma=True)
self.set_param("os.init", "init") self.set_param("os.init", "init")
self.set_param("os.arch", "arch")
self.set_param("type", "domain_type")
self.set_param("os.machine", "machine") self.set_param("os.machine", "machine")
self.set_param("os.os_type", "os_type")
self.set_param("emulator", "emulator")
def set_initargs_cb(opts, inst, cliname, val): def set_initargs_cb(opts, inst, cliname, val):
ignore = opts ignore = opts

View File

@ -18,6 +18,8 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA. # MA 02110-1301 USA.
import re
from .xmlbuilder import XMLBuilder, XMLChildProperty from .xmlbuilder import XMLBuilder, XMLChildProperty
from .xmlbuilder import XMLProperty as _XMLProperty from .xmlbuilder import XMLProperty as _XMLProperty
@ -76,6 +78,58 @@ class _Devices(_CapsBlock):
class DomainCapabilities(XMLBuilder): class DomainCapabilities(XMLBuilder):
@staticmethod
def build_from_guest(guest):
if not guest.conn.check_support(
guest.conn.SUPPORT_CONN_DOMAIN_CAPABILITIES):
# If not supported, just use a stub object
return DomainCapabilities(guest.conn)
xml = guest.conn.getDomainCapabilities(
guest.emulator, guest.os.arch, guest.os.machine, guest.type)
return DomainCapabilities(guest.conn, parsexml=xml)
# Mapping of UEFI binary names to their associated architectures. We
# only use this info to do things automagically for the user, it shouldn't
# validate anything the user explicitly enters.
_uefi_arch_patterns = {
"x86_64": [
".*OVMF_CODE\.fd", # RHEL
".*ovmf-x64/OVMF.*\.fd", # gerd's firmware repo
],
"aarch64": [
".*AAVMF_CODE\.fd", # RHEL
".*aarch64/QEMU_EFI.*", # gerd's firmware repo
],
}
def find_uefi_path_for_arch(self, arch):
"""
Search the loader paths for one that matches the passed arch
"""
if not self.arch_can_uefi(arch):
return
patterns = self._uefi_arch_patterns.get(arch)
for pattern in patterns:
for path in [v.value for v in self.os.loader.values]:
if re.match(pattern, path):
return path
def arch_can_uefi(self, arch):
"""
Return True if we know how to setup UEFI for the passed arch
"""
return arch in self._uefi_arch_patterns.keys()
def supports_uefi_xml(self):
"""
Return True if libvirt advertises support for proper UEFI setup
"""
return ("readonly" in self.os.loader.enum_names() and
"yes" in self.os.loader.get_enum("readonly").get_values())
_XML_ROOT_NAME = "domainCapabilities" _XML_ROOT_NAME = "domainCapabilities"
os = XMLChildProperty(_OS, is_single=True) os = XMLChildProperty(_OS, is_single=True)
devices = XMLChildProperty(_Devices, is_single=True) devices = XMLChildProperty(_Devices, is_single=True)

View File

@ -46,6 +46,7 @@ from .domainmemorybacking import DomainMemorybacking
from .domainmemorytune import DomainMemorytune from .domainmemorytune import DomainMemorytune
from .domainnumatune import DomainNumatune from .domainnumatune import DomainNumatune
from .domainresource import DomainResource from .domainresource import DomainResource
from .domcapabilities import DomainCapabilities
from .idmap import IdMap from .idmap import IdMap
from .osxml import OSXML from .osxml import OSXML
from .pm import PM from .pm import PM
@ -529,6 +530,35 @@ class Guest(XMLBuilder):
return osdict.lookup_osdict_key(self.os_variant, key, default) return osdict.lookup_osdict_key(self.os_variant, key, default)
###########################
# XML convenience helpers #
###########################
def set_uefi_default(self):
"""
Configure UEFI for the VM, but only if libvirt is advertising
a known UEFI binary path.
"""
domcaps = DomainCapabilities.build_from_guest(self)
if not domcaps.supports_uefi_xml():
raise RuntimeError(_("Libvirt version does not support UEFI."))
if not domcaps.arch_can_uefi(self.os.arch):
raise RuntimeError(
_("Don't know how to setup UEFI for arch '%s'") %
self.os.arch)
path = domcaps.find_uefi_path_for_arch(self.os.arch)
if not path:
raise RuntimeError(_("Did not find any UEFI binary path for "
"arch '%s'") % self.os.arch)
self.os.loader_ro = True
self.os.loader_type = "pflash"
self.os.loader = path
################### ###################
# Device defaults # # Device defaults #
################### ###################