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:
parent
760465fd2b
commit
a04307cd70
|
@ -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>
|
||||||
|
|
|
@ -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/>
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 #
|
||||||
###################
|
###################
|
||||||
|
|
Loading…
Reference in New Issue