Merge "libvirt: Launch instances with stateless firmware"
This commit is contained in:
commit
c79bec0f22
@ -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
|
||||
|
@ -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:
|
||||
|
11
nova/tests/fixtures/libvirt.py
vendored
11
nova/tests/fixtures/libvirt.py
vendored
@ -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,
|
||||
|
298
nova/tests/functional/libvirt/test_stateless_firmware.py
Normal file
298
nova/tests/functional/libvirt/test_stateless_firmware.py
Normal 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'])
|
@ -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,
|
||||
|
@ -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"
|
||||
|
@ -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))
|
||||
|
@ -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):
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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 (
|
||||
|
@ -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
|
||||
|
@ -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.
|
Loading…
Reference in New Issue
Block a user