mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-11 04:36:48 +00:00
fix(migration): remove the NVRAM file a live migration leaves on the source
libvirt undefines the source domain after a migration but keeps its NVRAM file. A VM created later on that host with the same name reuses the file and starts with the old VM's UEFI variables (boot entries, enrolled keys): checked on libvirt 11.10, where only VIR_DOMAIN_START_RESET_NVRAM recreates it from the template. After a successful migration the file is now deleted on the source, as a volume of the pool on libvirt's NVRAM directory or of a transient one. Only a file in /var/lib/libvirt/qemu/nvram, and only when the destination cannot create a marker file the source just created there (an exclusive create also fails on shared storage whose listing an NFS client caches). A failed removal is logged with the migration, which stands and is recorded.
This commit is contained in:
parent
e14a4cc10d
commit
975b7f0baf
6 changed files with 345 additions and 2 deletions
|
|
@ -7,6 +7,7 @@ from computes.models import Compute
|
|||
from django.contrib.auth import get_user_model
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from libvirt import libvirtError
|
||||
from logs.models import Logs
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
|
|
@ -22,11 +23,18 @@ class MigrationLogTests(TestCase):
|
|||
self.dest = Compute.objects.create(name="ml-dst", hostname="198.51.100.9", login="root", password="", type=2)
|
||||
self.vm = Instance.objects.create(compute=self.source, name="vm", uuid="u-ml")
|
||||
|
||||
def migrate(self, outcome, **request):
|
||||
def migrate(self, outcome, nvram=None, remove_nvram=True, **request):
|
||||
self.proxy = MagicMock()
|
||||
self.proxy.get_autostart.return_value = False
|
||||
self.proxy.get_nvram.return_value = nvram
|
||||
if isinstance(remove_nvram, Exception):
|
||||
self.proxy.remove_nvram.side_effect = remove_nvram
|
||||
else:
|
||||
self.proxy.remove_nvram.return_value = remove_nvram
|
||||
with patch.object(Compute, "status", True), \
|
||||
patch("instances.utils.wvmInstances") as instances, \
|
||||
patch("instances.utils.wvmInstance"), \
|
||||
patch.object(Instance, "proxy", MagicMock()), \
|
||||
patch.object(Instance, "proxy", self.proxy), \
|
||||
patch.object(Instance, "autostart", False):
|
||||
if isinstance(outcome, Exception):
|
||||
instances.return_value.moveto.side_effect = outcome
|
||||
|
|
@ -34,6 +42,40 @@ class MigrationLogTests(TestCase):
|
|||
instances.return_value.moveto.return_value = outcome
|
||||
return migrate_instance(self.dest, self.vm, self.admin, **request)
|
||||
|
||||
def test_the_nvram_file_left_on_the_source_is_removed(self):
|
||||
self.migrate("live", nvram="/var/lib/libvirt/qemu/nvram/vm_VARS.fd", live=True)
|
||||
path, destination = self.proxy.remove_nvram.call_args[0]
|
||||
self.assertEqual(path, "/var/lib/libvirt/qemu/nvram/vm_VARS.fd")
|
||||
self.assertIsNotNone(destination)
|
||||
self.assertEqual(Logs.objects.get().message, "Instance is migrated(live) to 198.51.100.9")
|
||||
|
||||
def test_a_vm_without_nvram_removes_nothing(self):
|
||||
self.migrate("live", live=True)
|
||||
self.assertFalse(self.proxy.remove_nvram.called)
|
||||
|
||||
def test_a_failed_nvram_removal_is_logged_but_the_migration_stands(self):
|
||||
self.migrate("live", nvram="/var/lib/libvirt/qemu/nvram/vm_VARS.fd",
|
||||
remove_nvram=libvirtError("cannot delete"), live=True)
|
||||
self.assertEqual(
|
||||
Logs.objects.get().message,
|
||||
"Instance is migrated(live) to 198.51.100.9; removing its NVRAM file "
|
||||
"/var/lib/libvirt/qemu/nvram/vm_VARS.fd on the source failed: cannot delete",
|
||||
)
|
||||
self.vm.refresh_from_db()
|
||||
self.assertEqual(self.vm.compute, self.dest)
|
||||
|
||||
def test_any_nvram_removal_error_still_records_the_migration(self):
|
||||
self.migrate("live", nvram="/var/lib/libvirt/qemu/nvram/vm_VARS.fd",
|
||||
remove_nvram=AttributeError("unexpected"), live=True)
|
||||
self.vm.refresh_from_db()
|
||||
self.assertEqual(self.vm.compute, self.dest)
|
||||
self.assertIn("on the source failed: unexpected", Logs.objects.get().message)
|
||||
|
||||
def test_a_failed_migration_removes_nothing(self):
|
||||
with self.assertRaises(util.OperationError):
|
||||
self.migrate(util.OperationError("no"), nvram="/var/lib/libvirt/qemu/nvram/vm_VARS.fd", live=True)
|
||||
self.assertFalse(self.proxy.remove_nvram.called)
|
||||
|
||||
def test_success_names_the_mode_used(self):
|
||||
self.migrate("non-live, compressed", live=False, compress=True)
|
||||
log = Logs.objects.get()
|
||||
|
|
|
|||
|
|
@ -190,6 +190,7 @@ def migrate_instance(
|
|||
)
|
||||
|
||||
autostart = instance.proxy.get_autostart()
|
||||
nvram = instance.proxy.get_nvram()
|
||||
mode = conn_migrate.moveto(
|
||||
instance.proxy,
|
||||
instance.name,
|
||||
|
|
@ -236,6 +237,25 @@ def migrate_instance(
|
|||
if conn_new is not None:
|
||||
conn_new.close()
|
||||
|
||||
# libvirt keeps the NVRAM file on the source; a VM created there
|
||||
# later with the same name would start with these variables.
|
||||
if nvram:
|
||||
conn_new = None
|
||||
try:
|
||||
conn_new = wvmInstances(
|
||||
new_compute.hostname, new_compute.login, new_compute.password, new_compute.type
|
||||
)
|
||||
instance.proxy.remove_nvram(nvram, conn_new)
|
||||
except Exception as error: # the migration stands and is recorded
|
||||
msg = _("%(msg)s; removing its NVRAM file %(path)s on the source failed: %(error)s") % {
|
||||
"msg": msg,
|
||||
"path": nvram,
|
||||
"error": error,
|
||||
}
|
||||
finally:
|
||||
if conn_new is not None:
|
||||
conn_new.close()
|
||||
|
||||
try:
|
||||
record_migration(instance, new_compute)
|
||||
except Exception as error:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue