Preserve UEFI NVRAM variable store

Preserve NVRAM variable store during stop/start, hard reboot, live
migration, and volume retype.

This does not affect cold migration or shelve.

For UEFI guests (hw_firmware_type=uefi), every time the instance is
started, the UEFI variable storage for that instance
(/var/lib/libvirt/qemu/nvram/instance-xxxxxxxx_VARS.fd) is deleted
and reinitialized from the default template.

The changes are based on this patch by Jonas Schäfer to preserve the
vTPM state:
https://review.opendev.org/c/openstack/nova/+/955657

Closes-Bug: #1633447
Closes-Bug: #2131730
Change-Id: I444a9285c07a04bf08a73772235f8dd73d75e513
Signed-off-by: Nicolai Ruckel <nicolai.ruckel@cloudandheat.com>
This commit is contained in:
Nicolai Ruckel
2025-07-22 09:49:55 +02:00
parent 264e868d49
commit 35b1945522
7 changed files with 74 additions and 19 deletions
+1
View File
@@ -100,6 +100,7 @@ VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY = 7
VIR_DOMAIN_UNDEFINE_MANAGED_SAVE = 1
VIR_DOMAIN_UNDEFINE_NVRAM = 4
VIR_DOMAIN_UNDEFINE_KEEP_TPM = 64
VIR_DOMAIN_UNDEFINE_KEEP_NVRAM = 8
VIR_DOMAIN_AFFECT_CURRENT = 0
VIR_DOMAIN_AFFECT_LIVE = 1
@@ -2863,6 +2863,7 @@ class LibvirtConfigGuestTest(LibvirtConfigBaseTest):
obj.os_mach_type = "pc-q35-5.1"
obj.os_loader = '/tmp/OVMF_CODE.secboot.fd'
obj.os_loader_type = 'pflash'
obj.os_nvram = '/foo/bar/instance-00000012_VARS.fd'
obj.os_loader_secure = True
obj.os_loader_stateless = True
xml = obj.to_xml()
@@ -2877,6 +2878,7 @@ class LibvirtConfigGuestTest(LibvirtConfigBaseTest):
<os>
<type machine="pc-q35-5.1">hvm</type>
<loader stateless='yes' secure='yes' readonly='yes' type='pflash'>/tmp/OVMF_CODE.secboot.fd</loader>
<nvram>/foo/bar/instance-00000012_VARS.fd</nvram>
</os>
</domain>""", # noqa: E501
xml,
+35 -7
View File
@@ -19517,10 +19517,11 @@ class LibvirtConnTestCase(test.NoDBTestCase,
fake_guest = mock.Mock()
mock_get.return_value = fake_guest
drvr._undefine_domain(instance, keep_vtpm=True)
drvr._undefine_domain(instance, keep_vtpm=True, keep_nvram=False)
fake_guest.delete_configuration.assert_called_once_with(
keep_vtpm=False,
keep_nvram=False,
)
# Check that it truly forces it to False and doesn't do a `not` or
@@ -19530,6 +19531,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
fake_guest.delete_configuration.assert_called_once_with(
keep_vtpm=False,
keep_nvram=False,
)
@mock.patch.object(host.Host, "get_guest")
@@ -19540,9 +19542,11 @@ class LibvirtConnTestCase(test.NoDBTestCase,
fake_guest = mock.Mock()
mock_get.return_value = fake_guest
drvr._undefine_domain(instance, keep_vtpm=True)
drvr._undefine_domain(instance, keep_vtpm=True, keep_nvram=False)
fake_guest.delete_configuration.assert_called_once_with(keep_vtpm=True)
fake_guest.delete_configuration.assert_called_once_with(
keep_vtpm=True,
keep_nvram=False)
# Check that it does not force keep_vtpm to true, just because it is
# supported.
@@ -19551,8 +19555,27 @@ class LibvirtConnTestCase(test.NoDBTestCase,
fake_guest.delete_configuration.assert_called_once_with(
keep_vtpm=False,
keep_nvram=False,
)
@mock.patch.object(host.Host, "get_guest")
def test_undefine_domain_passes_keep_nvram_if_supported(self, mock_get):
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
instance = objects.Instance(**self.test_instance)
fake_guest = mock.Mock()
mock_get.return_value = fake_guest
drvr._undefine_domain(instance, keep_nvram=True)
fake_guest.delete_configuration.assert_called_once_with(
keep_vtpm=False, keep_nvram=True)
# Check that it does not force keep_nvram to true, just because it is
# supported.
fake_guest.reset_mock()
drvr._undefine_domain(instance, keep_nvram=False)
fake_guest.delete_configuration.assert_called_once_with(
keep_vtpm=False,
keep_nvram=False,
)
@mock.patch.object(host.Host, "list_instance_domains")
@mock.patch.object(objects.BlockDeviceMappingList, "bdms_by_instance_uuid")
@mock.patch.object(objects.InstanceList, "get_by_filters")
@@ -19852,7 +19875,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
disk_actual_size = 3687091200
disk_actual_size_blocks = disk_actual_size / 512
expected_over_committed_disk_size = disk_virtual_size -\
disk_actual_size
disk_actual_size
mock_getsize.return_value = disk_virtual_size
mock_stat.return_value = mock.Mock(st_blocks=disk_actual_size_blocks)
@@ -22191,7 +22214,10 @@ class LibvirtConnTestCase(test.NoDBTestCase,
mock_delete_files.assert_called_once_with(fake_inst)
# vTPM secret should not be deleted until instance is deleted.
mock_delete_vtpm.assert_not_called()
mock_undefine.assert_called_once_with(fake_inst, keep_vtpm=False)
mock_undefine.assert_called_once_with(
fake_inst,
keep_vtpm=False,
keep_nvram=False)
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._undefine_domain')
@mock.patch('nova.crypto.delete_vtpm_secret')
@@ -22217,7 +22243,8 @@ class LibvirtConnTestCase(test.NoDBTestCase,
mock_get_mapping.assert_called_once_with(None)
mock_delete_files.assert_not_called()
mock_delete_vtpm.assert_not_called()
mock_undefine.assert_called_once_with(fake_inst, keep_vtpm=True)
mock_undefine.assert_called_once_with(fake_inst, keep_vtpm=True,
keep_nvram=True)
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._undefine_domain')
@mock.patch('nova.crypto.delete_vtpm_secret')
@@ -22242,7 +22269,8 @@ class LibvirtConnTestCase(test.NoDBTestCase,
drvr.cleanup('ctxt', fake_inst, 'netinfo')
# vTPM secret should not be deleted until instance is deleted.
mock_delete_vtpm.assert_not_called()
mock_undefine.assert_called_once_with(fake_inst, keep_vtpm=False)
mock_undefine.assert_called_once_with(fake_inst, keep_vtpm=False,
keep_nvram=False)
@mock.patch.object(libvirt_driver.LibvirtDriver, 'delete_instance_files',
return_value=True)
@@ -145,6 +145,12 @@ class GuestTestCase(test.NoDBTestCase):
fakelibvirt.VIR_DOMAIN_UNDEFINE_NVRAM |
fakelibvirt.VIR_DOMAIN_UNDEFINE_KEEP_TPM)
def test_delete_configuration_keep_nvram(self):
self.guest.delete_configuration(keep_nvram=True)
self.domain.undefineFlags.assert_called_once_with(
fakelibvirt.VIR_DOMAIN_UNDEFINE_MANAGED_SAVE |
fakelibvirt.VIR_DOMAIN_UNDEFINE_KEEP_NVRAM)
def test_delete_configuration_exception(self):
self.domain.undefineFlags.side_effect = fakelibvirt.libvirtError(
'oops')
+15 -10
View File
@@ -1652,7 +1652,7 @@ class LibvirtDriver(driver.ComputeDriver):
self.cleanup(context, instance, network_info, block_device_info,
destroy_disks, destroy_secrets=destroy_secrets)
def _delete_guest_configuration(self, guest, keep_vtpm):
def _delete_guest_configuration(self, guest, keep_vtpm, keep_nvram):
"""Wrapper around guest.delete_configuration which incorporates version
checks for the additional arguments.
@@ -1671,13 +1671,14 @@ class LibvirtDriver(driver.ComputeDriver):
)
keep_vtpm = False
guest.delete_configuration(keep_vtpm=keep_vtpm)
guest.delete_configuration(keep_vtpm=keep_vtpm, keep_nvram=keep_nvram)
def _undefine_domain(self, instance, keep_vtpm=False):
def _undefine_domain(self, instance, keep_vtpm=False, keep_nvram=False):
try:
guest = self._host.get_guest(instance)
try:
self._delete_guest_configuration(guest, keep_vtpm=keep_vtpm)
self._delete_guest_configuration(guest, keep_vtpm=keep_vtpm,
keep_nvram=keep_nvram)
except libvirt.libvirtError as e:
with excutils.save_and_reraise_exception() as ctxt:
errcode = e.get_error_code()
@@ -1764,9 +1765,10 @@ class LibvirtDriver(driver.ComputeDriver):
:param destroy_vifs: if plugged vifs should be unplugged
:param cleanup_instance_dir: If the instance dir should be removed
:param cleanup_instance_disks: If the instance disks should be removed.
Also removes ephemeral encryption secrets, if present.
:param destroy_secrets: If the cinder volume encryption libvirt secrets
should be deleted.
Also removes ephemeral encryption secrets, if present, as well as
vTPM and NVRAM data.
:param destroy_secrets: If the cinder volume encryption secrets should
be deleted.
"""
# zero the data on backend pmem device
vpmems = self._get_vpmems(instance)
@@ -1843,7 +1845,8 @@ class LibvirtDriver(driver.ComputeDriver):
self._cleanup_ephemeral_encryption_secrets(
context, instance, block_device_info)
self._undefine_domain(instance, keep_vtpm=not cleanup_instance_disks)
self._undefine_domain(instance, keep_vtpm=not cleanup_instance_disks,
keep_nvram=not cleanup_instance_disks)
def _cleanup_ephemeral_encryption_secrets(
self, context, instance, block_device_info
@@ -2418,7 +2421,8 @@ class LibvirtDriver(driver.ComputeDriver):
# undefine it. If any part of this block fails, the domain is
# re-defined regardless.
if guest.has_persistent_configuration():
self._delete_guest_configuration(guest, keep_vtpm=True)
self._delete_guest_configuration(guest, keep_vtpm=True,
keep_nvram=True)
try:
dev.copy(conf.to_xml(), reuse_ext=True)
@@ -3543,7 +3547,8 @@ class LibvirtDriver(driver.ComputeDriver):
# If any part of this block fails, the domain is
# re-defined regardless.
if guest.has_persistent_configuration():
self._delete_guest_configuration(guest, keep_vtpm=True)
self._delete_guest_configuration(guest, keep_vtpm=True,
keep_nvram=True)
# NOTE (rmk): Establish a temporary mirror of our root disk and
# issue an abort once we have a complete copy.
+8 -2
View File
@@ -289,12 +289,15 @@ class Guest:
yield VCPUInfo(
id=vcpu[0], cpu=vcpu[3], state=vcpu[1], time=vcpu[2])
def delete_configuration(self, keep_vtpm=False):
def delete_configuration(self, keep_vtpm=False, keep_nvram=False):
"""Undefines a domain from hypervisor.
:param keep_vtpm: If true, the vTPM data will be preserved. Otherwise,
it will be deleted. Defaults to false (that is, deleting the vTPM
data).
:param keep_nvram: If true, the NVRAM data will be preserved.
Otherwise, it will be deleted. Defaults to false (that is, deleting
the NVRAM data).
Calling this with `keep_vtpm` set to True should, eventually, be
followed up with a call where it is set to False (after re-defining
@@ -306,9 +309,12 @@ class Guest:
"""
try:
flags = libvirt.VIR_DOMAIN_UNDEFINE_MANAGED_SAVE
flags |= libvirt.VIR_DOMAIN_UNDEFINE_NVRAM
if keep_vtpm:
flags |= libvirt.VIR_DOMAIN_UNDEFINE_KEEP_TPM
if keep_nvram:
flags |= libvirt.VIR_DOMAIN_UNDEFINE_KEEP_NVRAM
else:
flags |= libvirt.VIR_DOMAIN_UNDEFINE_NVRAM
self._domain.undefineFlags(flags)
except libvirt.libvirtError:
LOG.debug("Error from libvirt during undefineFlags for guest "
@@ -0,0 +1,7 @@
---
fixes:
- |
NVRAM variable store is preserved during stop/start, hard reboot, and live
migration by passing the corresponding flag to libvirt.
See https://bugs.launchpad.net/nova/+bug/1633447 for more details.