Merge "libvirt: Launch instances with stateless firmware"

This commit is contained in:
Zuul 2024-09-01 07:57:54 +00:00 committed by Gerrit Code Review
commit c79bec0f22
12 changed files with 507 additions and 9 deletions

View File

@ -1413,3 +1413,24 @@ driver.ironic=missing
driver.libvirt-vz-vm=missing
driver.libvirt-vz-ct=missing
driver.zvm=missing
[operation.boot-stateless-firmware]
title=Boot instance with stateless firmware
status=optional
notes=The feature allows VMs to be booted with read-only firmware image without
NVRAM file. This feature is especially useful for confidential computing use
case because it allows more complete measurement of elements involved in
the boot chain and disables the potential attack serface from hypervisors.
cli=openstack server create <usual server create parameters>
driver.libvirt-kvm-x86=partial
driver-notes.libvirt-kvm-x86=This feature is supported only with UEFI firmware
driver.libvirt-kvm-aarch64=missing
driver.libvirt-kvm-ppc64=missing
driver.libvirt-kvm-s390x=missing
driver.libvirt-qemu-x86=missing
driver.libvirt-lxc=missing
driver.vmware=missing
driver.ironic=missing
driver.libvirt-vz-vm=missing
driver.libvirt-vz-ct=missing
driver.zvm=missing

View File

@ -196,6 +196,8 @@ class ResourceRequest(object):
res_req._translate_maxphysaddr_request(request_spec.flavor, image)
res_req._translate_stateless_firmware_request(image)
res_req.strip_zeros()
return res_req
@ -290,6 +292,11 @@ class ResourceRequest(object):
self._add_trait(trait, 'required')
LOG.debug("Requiring maxphysaddr support via trait %s.", trait)
def _translate_stateless_firmware_request(self, image):
if hardware.get_stateless_firmware_constraint(image):
self._add_trait(os_traits.COMPUTE_SECURITY_STATELESS_FIRMWARE,
'required')
def _translate_vtpm_request(self, flavor, image):
vtpm_config = hardware.get_vtpm_constraint(flavor, image)
if not vtpm_config:

View File

@ -1118,6 +1118,10 @@ class Domain(object):
os['type'] = os_type.text
os['arch'] = os_type.get('arch', self._connection.host_info.arch)
os_loader = tree.find('./os/loader')
if os_loader is not None:
os['loader_stateless'] = os_loader.get('stateless')
os_kernel = tree.find('./os/kernel')
if os_kernel is not None:
os['kernel'] = os_kernel.text
@ -1425,6 +1429,11 @@ class Domain(object):
pass
def XMLDesc(self, flags):
loader = '<loader/>'
if self._def['os'].get('loader_stateless'):
loader = ('<loader stateless="%s"/>' %
self._def['os'].get('loader_stateless'))
disks = ''
for disk in self._def['devices']['disks']:
if disk['type'] == 'file':
@ -1564,6 +1573,7 @@ class Domain(object):
<vcpu%(vcpuset)s>%(vcpu)s</vcpu>
<os>
<type arch='%(arch)s' machine='pc-0.12'>hvm</type>
%(loader)s
<boot dev='hd'/>
</os>
<features>
@ -1612,6 +1622,7 @@ class Domain(object):
'vcpuset': vcpuset,
'vcpu': self._def['vcpu']['number'],
'arch': self._def['os']['arch'],
'loader': loader,
'disks': disks,
'nics': nics,
'hostdevs': hostdevs,

View File

@ -0,0 +1,298 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import copy
import fixtures
from unittest import mock
from lxml import etree
from oslo_utils.fixture import uuidsentinel
from oslo_utils import versionutils
from nova import conf
from nova import context as nova_context
from nova import objects
from nova.tests.functional.libvirt import base
from nova.virt.libvirt import driver as libvirt_driver
from nova.virt.libvirt import migration as libvirt_migration
CONF = conf.CONF
class LibvirtStatelessFirmwareTest(
base.LibvirtMigrationMixin,
base.ServersTestBase,
):
# many move operations are admin-only
ADMIN_API = True
microversion = 'latest'
def setUp(self):
super().setUp()
self.context = nova_context.get_admin_context()
# Add the stateless firmware image to the glance fixture
hw_fw_stateless_image = copy.deepcopy(self.glance.image1)
hw_fw_stateless_image['id'] = uuidsentinel.fw_stateless_image_id
hw_fw_stateless_image['properties']['hw_machine_type'] = 'q35'
hw_fw_stateless_image['properties']['hw_firmware_type'] = 'uefi'
hw_fw_stateless_image['properties']['hw_firmware_stateless'] = True
self.glance.create(self.context, hw_fw_stateless_image)
self._start_compute()
self.guest_configs = {}
orig_get_config = self.computes['compute1'].driver._get_guest_config
def _get_guest_config(_self, *args, **kwargs):
guest_config = orig_get_config(*args, **kwargs)
instance = args[0]
self.guest_configs[instance.uuid] = guest_config
return self.guest_configs[instance.uuid]
self.useFixture(fixtures.MonkeyPatch(
'nova.virt.libvirt.LibvirtDriver._get_guest_config',
_get_guest_config))
# disk.rescue image_create ignoring privsep
self.useFixture(fixtures.MockPatch(
'nova.virt.libvirt.imagebackend._update_utime_ignore_eacces'))
# dir to create 'unrescue.xml'
def fake_path(_self, *args, **kwargs):
return CONF.instances_path
self.useFixture(fixtures.MonkeyPatch(
'nova.virt.libvirt.utils.get_instance_path', fake_path))
def _start_compute(self, hostname='compute1'):
self.start_compute(
hostname,
libvirt_version=versionutils.convert_version_to_int(
libvirt_driver.MIN_LIBVIRT_STATELESS_FIRMWARE
))
caps = self.computes[hostname].driver.capabilities
self.assertTrue(caps['supports_stateless_firmware'])
def _create_server_with_stateless_firmware(self):
server = self._create_server(
image_uuid=uuidsentinel.fw_stateless_image_id,
networks='none',
)
self.addCleanup(self._delete_server, server)
return server
def _create_server_without_stateless_firmware(self):
server = self._create_server(
image_uuid=self.glance.image1['id'],
networks='none',
)
self.addCleanup(self._delete_server, server)
return server
def _assert_server_has_stateless_firmware(self, server_id):
instance = objects.Instance.get_by_uuid(self.context, server_id)
self.assertTrue(
instance.image_meta.properties.hw_firmware_stateless
)
self.assertTrue(
self.guest_configs[server_id].os_loader_stateless
)
del self.guest_configs[server_id]
def _assert_server_has_no_stateless_firmware(self, server_id):
instance = objects.Instance.get_by_uuid(self.context, server_id)
self.assertIsNone(
instance.image_meta.properties.get('hw_firmware_stateless')
)
self.assertIsNone(
self.guest_configs[server_id].os_loader_stateless
)
del self.guest_configs[server_id]
def test_create_server(self):
"""Assert new instance is created with stateless firmware only when
the image has the required properties
"""
server_with = self._create_server_with_stateless_firmware()
self._assert_server_has_stateless_firmware(server_with['id'])
server_without = self._create_server_without_stateless_firmware()
self._assert_server_has_no_stateless_firmware(server_without['id'])
def test_live_migrate_server(self):
# create a server with stateless firmware
self.server = self._create_server_with_stateless_firmware()
self._assert_server_has_stateless_firmware(self.server['id'])
self._start_compute('compute2')
self.src = self.computes['compute1']
self.dest = self.computes['compute2']
self.migration_xml = None
orig_get_updated_guest_xml = libvirt_migration.get_updated_guest_xml
def migration_xml_wrapper(*args, **kwargs):
self.migration_xml = orig_get_updated_guest_xml(*args, **kwargs)
return self.migration_xml
with mock.patch(
'nova.virt.libvirt.migration.get_updated_guest_xml',
side_effect=migration_xml_wrapper
) as fake_get_updated_guest_xml:
self._live_migrate(self.server)
fake_get_updated_guest_xml.assert_called_once()
server = etree.fromstring(self.migration_xml)
self.assertEqual('yes', server.find('./os/loader').get('stateless'))
def test_migrate_server(self):
self._start_compute('compute2')
# create a server with stateless firmware
server = self._create_server_with_stateless_firmware()
self._assert_server_has_stateless_firmware(server['id'])
# TODO(stephenfin): The mock of 'migrate_disk_and_power_off' should
# probably be less...dumb
with mock.patch(
'nova.virt.libvirt.driver.LibvirtDriver'
'.migrate_disk_and_power_off', return_value='{}',
):
# cold migrate the server
self._migrate_server(server)
self._assert_server_has_stateless_firmware(server['id'])
server = self._confirm_resize(server)
def test_migrate_server_revert(self):
self._start_compute('compute2')
# create a server with stateless firmware
server = self._create_server_with_stateless_firmware()
self._assert_server_has_stateless_firmware(server['id'])
# TODO(stephenfin): The mock of 'migrate_disk_and_power_off' should
# probably be less...dumb
with mock.patch(
'nova.virt.libvirt.driver.LibvirtDriver'
'.migrate_disk_and_power_off', return_value='{}',
):
# cold migrate the server
self._migrate_server(server)
self._assert_server_has_stateless_firmware(server['id'])
server = self._revert_resize(server)
self._assert_server_has_stateless_firmware(server['id'])
def test_shelve_and_unshelve_to_same_host(self):
# create a server with stateless firmware
server = self._create_server_with_stateless_firmware()
self._assert_server_has_stateless_firmware(server['id'])
# shelve the server
server = self._shelve_server(server)
# uneshelve the server. the server is started at the same compute
server = self._unshelve_server(server)
self._assert_server_has_stateless_firmware(server['id'])
def test_shelve_and_unshelve_to_different_host(self):
# create a server with stateless firmware
server = self._create_server_with_stateless_firmware()
self._assert_server_has_stateless_firmware(server['id'])
# shelve the server
server = self._shelve_server(server)
# force down the compute node
source_compute_id = self.admin_api.get_services(
host='compute1', binary='nova-compute')[0]['id']
self.computes['compute1'].stop()
self.admin_api.put_service(
source_compute_id, {'forced_down': 'true'})
# start a new compute node and unshelve the server
self._start_compute('compute2')
server = self._unshelve_server(server)
self.assertEqual(
'compute2', server['OS-EXT-SRV-ATTR:hypervisor_hostname'])
self._assert_server_has_stateless_firmware(server['id'])
def test_evacuate_server(self):
# create a server with stateless firmware
server = self._create_server_with_stateless_firmware()
self.assertEqual(
'compute1', server['OS-EXT-SRV-ATTR:hypervisor_hostname'])
self._assert_server_has_stateless_firmware(server['id'])
# force down the compute node
source_compute_id = self.admin_api.get_services(
host='compute1', binary='nova-compute')[0]['id']
self.computes['compute1'].stop()
self.admin_api.put_service(
source_compute_id, {'forced_down': 'true'})
# start a new compute node and evecuate the server
self._start_compute('compute2')
server = self._evacuate_server(server, expected_host='compute2')
self.assertEqual(
'compute2', server['OS-EXT-SRV-ATTR:hypervisor_hostname'])
self._assert_server_has_stateless_firmware(server['id'])
def test_rescue_unrescue_server(self):
# create a server without stateless firmware
server = self._create_server_with_stateless_firmware()
self._assert_server_has_stateless_firmware(server['id'])
self.api.post_server_action(server['id'], {
"rescue": {
"rescue_image_ref": self.glance.image1['id']
}
})
server = self._wait_for_state_change(server, 'RESCUE')
# The instance object should still expect stateless firmware
instance = objects.Instance.get_by_uuid(self.context, server['id'])
self.assertTrue(
instance.image_meta.properties.hw_firmware_stateless
)
# but the actual xml config should not
self.assertIsNone(
self.guest_configs[server['id']].os_loader_stateless
)
del self.guest_configs[server['id']]
self.api.post_server_action(server['id'], {
"unrescue": None
})
server = self._wait_for_state_change(server, 'ACTIVE')
# NOTE(tkajinam): Unrescue restores the original xml file so skip
# asserting its content.
def test_rebuild_server(self):
# create a server without stateless firmware
server = self._create_server_without_stateless_firmware()
self._assert_server_has_no_stateless_firmware(server['id'])
# rebuild using the image with stateless firmware
server = self._rebuild_server(
server, uuidsentinel.fw_stateless_image_id)
self._assert_server_has_stateless_firmware(server['id'])
# rebuild using the image without stateless firmware
server = self._rebuild_server(server, self.glance.image1['id'])
self._assert_server_has_no_stateless_firmware(server['id'])

View File

@ -1289,6 +1289,30 @@ class TestUtils(TestUtilsBase):
rr = utils.ResourceRequest.from_request_spec(rs)
self.assertResourceRequestsEqual(expected, rr)
def test_resource_request_from_request_spec_with_stateless_firmware(self):
flavor = objects.Flavor(
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0,
)
image = objects.ImageMeta(
properties=objects.ImageMetaProps(
hw_firmware_type = 'uefi',
hw_firmware_stateless = True
)
)
expected = FakeResourceRequest()
expected._rg_by_id[None] = objects.RequestGroup(
use_same_provider=False,
required_traits={'COMPUTE_SECURITY_STATELESS_FIRMWARE'},
resources={
'VCPU': 1,
'MEMORY_MB': 1024,
'DISK_GB': 15,
},
)
rs = objects.RequestSpec(flavor=flavor, image=image, is_bfv=False)
rr = utils.ResourceRequest.from_request_spec(rs)
self.assertResourceRequestsEqual(expected, rr)
def test_resource_request_from_request_spec_with_secure_boot(self):
flavor = objects.Flavor(
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0,

View File

@ -2799,8 +2799,8 @@ class LibvirtConfigGuestTest(LibvirtConfigBaseTest):
xml = obj.to_xml()
self.assertXmlEqual(fake_libvirt_data.FAKE_KVM_GUEST, xml)
def test_config_uefi(self):
obj = config.LibvirtConfigGuest()
def _test_config_uefi(self):
obj = config.libvirtconfigguest()
obj.virt_type = "kvm"
obj.memory = 100 * units.Mi
obj.vcpus = 1
@ -2811,9 +2811,10 @@ class LibvirtConfigGuestTest(LibvirtConfigBaseTest):
obj.os_loader = '/tmp/OVMF_CODE.secboot.fd'
obj.os_loader_type = 'pflash'
obj.os_loader_secure = True
obj.os_loader_stateless = True
xml = obj.to_xml()
self.assertXmlEqual(
self.assertxmlequal(
"""
<domain type="kvm">
<uuid>f01cf68d-515c-4daf-b85f-ef1424d93bfc</uuid>
@ -2822,13 +2823,13 @@ class LibvirtConfigGuestTest(LibvirtConfigBaseTest):
<vcpu>1</vcpu>
<os>
<type machine="pc-q35-5.1">hvm</type>
<loader secure='yes' readonly='yes' type='pflash'>/tmp/OVMF_CODE.secboot.fd</loader>
<loader stateless='yes' secure='yes' readonly='yes' type='pflash'>/tmp/OVMF_CODE.secboot.fd</loader>
</os>
</domain>""", # noqa: E501
xml,
)
def _test_config_uefi_autoconfigure(self, secure):
def _test_config_uefi_autoconfigure(self, secure=False, stateless=None):
obj = config.LibvirtConfigGuest()
obj.virt_type = "kvm"
obj.memory = 100 * units.Mi
@ -2839,10 +2840,11 @@ class LibvirtConfigGuestTest(LibvirtConfigBaseTest):
obj.os_firmware = "efi"
obj.os_mach_type = "pc-q35-5.1"
obj.os_loader_secure = secure
obj.os_loader_stateless = stateless
return obj.to_xml()
def test_config_uefi_autoconfigure(self):
xml = self._test_config_uefi_autoconfigure(secure=False)
xml = self._test_config_uefi_autoconfigure()
self.assertXmlEqual(
xml,
@ -2877,6 +2879,24 @@ class LibvirtConfigGuestTest(LibvirtConfigBaseTest):
</domain>""",
)
def test_config_uefi_autoconfigure_stateless(self):
xml = self._test_config_uefi_autoconfigure(stateless=True)
self.assertXmlEqual(
xml,
"""
<domain type="kvm">
<uuid>f01cf68d-515c-4daf-b85f-ef1424d93bfc</uuid>
<name>uefi</name>
<memory>104857600</memory>
<vcpu>1</vcpu>
<os firmware="efi">
<type machine="pc-q35-5.1">hvm</type>
<loader stateless="yes" secure="no"/>
</os>
</domain>""",
)
def test_config_boot_menu(self):
obj = config.LibvirtConfigGuest()
obj.virt_type = "kvm"

View File

@ -5379,6 +5379,27 @@ class LibvirtConnTestCase(test.NoDBTestCase,
self.assertEqual('/usr/share/OVMF/OVMF_CODE.fd', cfg.os_loader)
self.assertEqual('/usr/share/OVMF/OVMF_VARS.fd', cfg.os_nvram_template)
def test_get_guest_config_with_uefi_and_stateless_firmware(self):
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
image_meta = objects.ImageMeta.from_dict({
"disk_format": "raw",
"properties": {
"hw_firmware_type": "uefi",
"hw_firmware_stateless": True
}
})
instance_ref = objects.Instance(**self.test_instance)
disk_info = blockinfo.get_disk_info(
CONF.libvirt.virt_type, instance_ref, image_meta)
cfg = drvr._get_guest_config(
instance_ref, [], image_meta, disk_info)
# these paths are derived from the FakeLibvirtFixture
self.assertEqual('/usr/share/OVMF/OVMF_CODE.fd', cfg.os_loader)
self.assertTrue(cfg.os_loader_stateless)
self.assertIsNone(cfg.os_nvram_template)
def test_get_guest_config_with_secure_boot_and_smm_required(self):
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
# uefi only used with secure boot
@ -21955,13 +21976,26 @@ class LibvirtConnTestCase(test.NoDBTestCase,
self.assertTrue(
driver.capabilities.get('supports_address_space_emulated'))
@mock.patch.object(fakelibvirt.Connection, 'getLibVersion',
return_value=versionutils.convert_version_to_int(
libvirt_driver.MIN_LIBVIRT_STATELESS_FIRMWARE) - 1)
def test_update_host_specific_capabilities_without_stateless_firmware(
self):
self, mock_get_version):
driver = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI())
driver._update_host_specific_capabilities()
self.assertFalse(
driver.capabilities.get('supports_stateless_firmware'))
@mock.patch.object(fakelibvirt.Connection, 'getLibVersion',
return_value=versionutils.convert_version_to_int(
libvirt_driver.MIN_LIBVIRT_STATELESS_FIRMWARE))
def test_update_host_specific_capabilities_with_stateless_firmware(
self, mock_get_version):
driver = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI())
driver._update_host_specific_capabilities()
self.assertTrue(
driver.capabilities.get('supports_stateless_firmware'))
@mock.patch.object(fakelibvirt.Connection, 'getLibVersion',
return_value=versionutils.convert_version_to_int(
libvirt_driver.MIN_LIBVIRT_MAXPHYSADDR))

View File

@ -6018,6 +6018,40 @@ class MaxphysaddrModeTest(test.NoDBTestCase):
)
@ddt.ddt
class StatelessFirmwareConstraintTest(test.NoDBTestCase):
@ddt.unpack
@ddt.data(
# pass: no configuration
(None, None, False),
# pass: uefi with stateless firmware
(True, 'uefi', True),
# pass: non-uefi with non-stateless firmware
(False, None, False),
# fail: non-uefi with stateless fiemware
(True, None, exception.Invalid),
)
def test_get_stateless_firmware_constraint(
self, firmware_stateless, firmware_type, expected,
):
image_meta_props = {}
if firmware_stateless is not None:
image_meta_props['hw_firmware_stateless'] = firmware_stateless
if firmware_type is not None:
image_meta_props['hw_firmware_type'] = firmware_type
image_meta = objects.ImageMeta.from_dict(
{'name': 'bar', 'properties': image_meta_props})
if isinstance(expected, type) and issubclass(expected, Exception):
self.assertRaises(
expected, hw.get_stateless_firmware_constraint, image_meta,
)
else:
self.assertEqual(
expected, hw.get_stateless_firmware_constraint(image_meta),
)
@ddt.ddt
class RescuePropertyTestCase(test.NoDBTestCase):

View File

@ -2085,6 +2085,27 @@ def get_secure_boot_constraint(
return policy
def get_stateless_firmware_constraint(
image_meta: 'objects.ImageMeta',
) -> bool:
"""Validate and return the requested statless firmware policy.
:param flavor: ``nova.objects.Flavor`` instance
:param image_meta: ``nova.objects.ImageMeta`` instance
:raises: nova.exception.Invalid if a value or combination of values is
invalid
"""
if not image_meta.properties.get('hw_firmware_stateless', False):
return False
if image_meta.properties.get('hw_firmware_type') != 'uefi':
raise exception.Invalid(_(
'Stateless firmware is supported only when UEFI firmware type is '
'used.'
))
return True
def numa_get_constraints(flavor, image_meta):
"""Return topology related to input request.

View File

@ -3075,6 +3075,7 @@ class LibvirtConfigGuest(LibvirtConfigObject):
self.os_firmware = None
self.os_loader_type = None
self.os_loader_secure = None
self.os_loader_stateless = None
self.os_nvram = None
self.os_nvram_template = None
self.os_kernel = None
@ -3140,7 +3141,8 @@ class LibvirtConfigGuest(LibvirtConfigObject):
if (
self.os_loader is not None or
self.os_loader_type is not None or
self.os_loader_secure is not None
self.os_loader_secure is not None or
self.os_loader_stateless is not None
):
loader = self._text_node("loader", self.os_loader)
if self.os_loader_type is not None:
@ -3149,6 +3151,9 @@ class LibvirtConfigGuest(LibvirtConfigObject):
if self.os_loader_secure is not None:
loader.set(
"secure", self.get_yes_no_str(self.os_loader_secure))
if self.os_loader_stateless is not None:
loader.set(
"stateless", self.get_yes_no_str(self.os_loader_stateless))
os.append(loader)
if (

View File

@ -257,6 +257,9 @@ LIBVIRT_PERF_EVENT_PREFIX = 'VIR_PERF_PARAM_'
MIN_LIBVIRT_MAXPHYSADDR = (8, 7, 0)
MIN_QEMU_MAXPHYSADDR = (2, 7, 0)
# stateless firmware support
MIN_LIBVIRT_STATELESS_FIRMWARE = (8, 6, 0)
REGISTER_IMAGE_PROPERTY_DEFAULTS = [
'hw_machine_type',
'hw_cdrom_bus',
@ -920,6 +923,13 @@ class LibvirtDriver(driver.ComputeDriver):
'supports_address_space_emulated': supports_maxphysaddr,
})
supports_stateless_firmware = self._host.has_min_version(
lv_ver=MIN_LIBVIRT_STATELESS_FIRMWARE,
)
self.capabilities.update({
'supports_stateless_firmware': supports_stateless_firmware,
})
def _register_all_undefined_instance_details(self) -> None:
"""Register the default image properties of instances on this host
@ -6959,6 +6969,8 @@ class LibvirtDriver(driver.ComputeDriver):
guest.os_mach_type = mach_type
hw_firmware_type = image_meta.properties.get('hw_firmware_type')
hw_firmware_stateless = hardware.get_stateless_firmware_constraint(
image_meta)
if arch == fields.Architecture.AARCH64:
if not hw_firmware_type:
@ -7016,6 +7028,9 @@ class LibvirtDriver(driver.ComputeDriver):
guest.os_loader = loader
guest.os_loader_type = 'pflash'
if hw_firmware_stateless:
guest.os_loader_stateless = True
else:
guest.os_nvram_template = nvram_template
# if the feature set says we need SMM then enable it

View File

@ -0,0 +1,8 @@
---
features:
- |
Libvirt virt driver now supports launching instances with stateless
firmware. The new ``hw_firmware_stateless`` image property can be used to
enable this feature. Note that the feature can be used only for
the instances with UEFI firmware. This feature requires libvirt v8.6.0 or
later.