mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 11:16:47 +00:00
fix(jobs): a recovered migration restores autostart and removes the source NVRAM file
migrate_instance sets autostart on the destination (it is per host and the migration does not carry it) and deletes the NVRAM file libvirt keeps on the source. A worker that stopped after libvirt moved the VM skipped both: recovery only recorded the move, so an autostart VM no longer started after the destination rebooted, and a later VM of the same name on the source started with these UEFI variables. The job now records both before the move, and recovery does them after a proven move; a step that fails is a warning of the job's result.
This commit is contained in:
parent
c3dfab287d
commit
65345bd980
6 changed files with 96 additions and 27 deletions
|
|
@ -930,11 +930,15 @@ def migration_target(instance, target_pk):
|
|||
return target
|
||||
|
||||
|
||||
def _connect(compute_pk):
|
||||
c = Compute.objects.get(pk=compute_pk)
|
||||
return wvmConnect(c.hostname, c.login, c.password, c.type)
|
||||
|
||||
|
||||
def _domain(compute_pk, uuid):
|
||||
"""The VM on the compute, or None when it has none there."""
|
||||
c = Compute.objects.get(pk=compute_pk)
|
||||
try:
|
||||
return wvmConnect(c.hostname, c.login, c.password, c.type).wvm.lookupByUUIDString(uuid)
|
||||
return _connect(compute_pk).wvm.lookupByUUIDString(uuid)
|
||||
except libvirtError as err:
|
||||
if err.get_error_code() != VIR_ERR_NO_DOMAIN:
|
||||
raise # unreachable: recovery waits and retries
|
||||
|
|
@ -947,6 +951,25 @@ def _shut_off_again(op, dom):
|
|||
dom.destroy()
|
||||
|
||||
|
||||
def _moved(op, dest):
|
||||
"""What migrate_instance does once libvirt moved the VM, for a job that
|
||||
stopped before it: autostart is per host, and the source keeps the
|
||||
NVRAM file. The job's result, with a warning for a step that failed."""
|
||||
notes = []
|
||||
if op.intent.get("autostart"):
|
||||
try:
|
||||
dest.setAutostart(1)
|
||||
except libvirtError as err:
|
||||
notes += say(_("The VM migrated, but autostart could not be set on the destination: %(error)s"),
|
||||
error=err)
|
||||
if nvram := op.intent.get("nvram"):
|
||||
try:
|
||||
_connect(op.intent["compute"]).remove_nvram(nvram, _connect(op.params["target"]))
|
||||
except libvirtError as err:
|
||||
notes += say(_("The VM migrated, but its NVRAM file stays on the source: %(error)s"), error=err)
|
||||
return {"warning": notes} if notes else {}
|
||||
|
||||
|
||||
@register
|
||||
class MigrateInstance(Kind):
|
||||
name = "instance.migrate"
|
||||
|
|
@ -993,8 +1016,10 @@ class MigrateInstance(Kind):
|
|||
def run(self, op, ctx):
|
||||
p = op.params
|
||||
instance = _instance(p["instance"])
|
||||
# a shut-off VM may migrate started paused (migrate_running): recovery shuts it off again
|
||||
ctx.record_intent(shut_off=instance.proxy.get_status() == 5)
|
||||
# a shut-off VM may migrate started paused (migrate_running): recovery shuts it off again;
|
||||
# it also does what migrate_instance does after the move (_moved)
|
||||
proxy = instance.proxy
|
||||
ctx.record_intent(shut_off=proxy.get_status() == 5, autostart=proxy.get_autostart(), nvram=proxy.get_nvram())
|
||||
ctx.set_phase(gettext_noop("Migrating the VM"))
|
||||
try:
|
||||
utils.migrate_instance(Compute.objects.get(pk=p["target"]), instance, op.user,
|
||||
|
|
@ -1048,7 +1073,7 @@ class MigrateInstance(Kind):
|
|||
if instance is not None: # the job stopped before it moved the record
|
||||
utils.record_migration(instance, Compute.objects.get(pk=op.params["target"]))
|
||||
_shut_off_again(op, dest)
|
||||
return Outcome(Operation.SUCCEEDED)
|
||||
return Outcome(Operation.SUCCEEDED, result=_moved(op, dest))
|
||||
if source is not None and dest is None:
|
||||
_shut_off_again(op, source)
|
||||
return Outcome(Operation.FAILED, error=say(_("The VM stayed on the source host")))
|
||||
|
|
|
|||
|
|
@ -100,6 +100,27 @@ class MigrateJobTestCase(TransactionTestCase):
|
|||
MigrateInstance().reconcile(op)
|
||||
dom.destroy.assert_not_called()
|
||||
|
||||
def test_recovery_of_a_moved_vm_sets_its_autostart_and_removes_the_source_nvram(self):
|
||||
self.vm.proxy.get_autostart.return_value = True
|
||||
self.vm.proxy.get_nvram.return_value = "/var/lib/libvirt/qemu/nvram/mig-vm_VARS.fd"
|
||||
op = self.queue()
|
||||
ctx = self.start(op)
|
||||
self.dest_conn.moveto.side_effect = OSError("the worker stops here")
|
||||
with self.assertRaises(OSError):
|
||||
get_kind(op.kind).run(op, ctx)
|
||||
dom = MagicMock()
|
||||
with patch("instances.jobs._domain", side_effect=[None, dom]), patch("instances.jobs._connect") as connect:
|
||||
outcome = MigrateInstance().reconcile(op)
|
||||
self.assertEqual((outcome.state, outcome.result), (Operation.SUCCEEDED, {}))
|
||||
dom.setAutostart.assert_called_once_with(1)
|
||||
connect.return_value.remove_nvram.assert_called_once_with(
|
||||
"/var/lib/libvirt/qemu/nvram/mig-vm_VARS.fd", connect.return_value)
|
||||
dom.setAutostart.side_effect = libvirtError("refused")
|
||||
with patch("instances.jobs._domain", side_effect=[None, dom]), patch("instances.jobs._connect"):
|
||||
outcome = MigrateInstance().reconcile(op)
|
||||
self.assertEqual(outcome.state, Operation.SUCCEEDED)
|
||||
self.assertIn("autostart could not be set", outcome.result["warning"][0]["text"])
|
||||
|
||||
def test_a_copy_claims_its_volume_on_both_computes(self):
|
||||
Instance.proxy.get_disk_devices.return_value = [
|
||||
{"dev": "vda", "storage": "default", "image": "mig-vm.qcow2", "path": "/i/mig-vm.qcow2"},
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -3018,53 +3018,65 @@ msgstr "%(compute)s sunucusuna ulaşılamıyor"
|
|||
msgid "%(compute)s is the VM's own host under another name"
|
||||
msgstr "%(compute)s, sanal makinenin kendi sunucusunun başka bir adı"
|
||||
|
||||
#: instances/jobs.py:947
|
||||
#: instances/jobs.py:963
|
||||
#, python-format
|
||||
msgid ""
|
||||
"The VM migrated, but autostart could not be set on the destination: %(error)s"
|
||||
msgstr ""
|
||||
"Sanal makine taşındı, ancak hedefte otomatik başlatma ayarlanamadı: %(error)s"
|
||||
|
||||
#: instances/jobs.py:969
|
||||
#, python-format
|
||||
msgid "The VM migrated, but its NVRAM file stays on the source: %(error)s"
|
||||
msgstr "Sanal makine taşındı, ancak NVRAM dosyası kaynakta kaldı: %(error)s"
|
||||
|
||||
#: instances/jobs.py:976
|
||||
msgid "Migrate VM"
|
||||
msgstr "Sanal makineyi taşı"
|
||||
|
||||
#: instances/jobs.py:991
|
||||
#: instances/jobs.py:1023
|
||||
msgid "Migrating the VM"
|
||||
msgstr "Sanal makine taşınıyor"
|
||||
|
||||
#: instances/jobs.py:1038
|
||||
#: instances/jobs.py:1070
|
||||
msgid "libvirt is still migrating the VM"
|
||||
msgstr "libvirt sanal makineyi hâlâ taşıyor"
|
||||
|
||||
#: instances/jobs.py:1045
|
||||
#: instances/jobs.py:1079
|
||||
msgid "The VM stayed on the source host"
|
||||
msgstr "Sanal makine kaynak sunucuda kaldı"
|
||||
|
||||
#: instances/jobs.py:1047
|
||||
#: instances/jobs.py:1081
|
||||
msgid ""
|
||||
"The VM is on both computes or on neither; check them, then resolve this job."
|
||||
msgstr ""
|
||||
"Sanal makine ya iki sunucuda da var ya da hiçbirinde yok; kontrol edin, sonra"
|
||||
" bu işi çözün."
|
||||
|
||||
#: instances/jobs.py:1095
|
||||
#: instances/jobs.py:1129
|
||||
msgid "Move disk"
|
||||
msgstr "Diski taşı"
|
||||
|
||||
#: instances/jobs.py:1102
|
||||
#: instances/jobs.py:1136
|
||||
msgid "Choose a disk of the VM"
|
||||
msgstr "Sanal makinenin bir diskini seçin"
|
||||
|
||||
#: instances/jobs.py:1126
|
||||
#: instances/jobs.py:1160
|
||||
msgid "Copying the disk"
|
||||
msgstr "Disk kopyalanıyor"
|
||||
|
||||
#: instances/jobs.py:1169
|
||||
#: instances/jobs.py:1203
|
||||
#, python-format
|
||||
msgid "Another block job runs on %(dev)s; check the VM"
|
||||
msgstr ""
|
||||
"%(dev)s üzerinde başka bir blok işi çalışıyor; sanal makineyi kontrol edin"
|
||||
|
||||
#: instances/jobs.py:1175
|
||||
#: instances/jobs.py:1209
|
||||
#, python-format
|
||||
msgid "The disk stayed in %(path)s; remove %(dest)s if it is there"
|
||||
msgstr "Disk %(path)s içinde kaldı; varsa %(dest)s dosyasını silin"
|
||||
|
||||
#: instances/jobs.py:1177
|
||||
#: instances/jobs.py:1211
|
||||
#, python-format
|
||||
msgid "The definitions of %(dev)s name %(paths)s; check the VM"
|
||||
msgstr "%(dev)s tanımları %(paths)s gösteriyor; sanal makineyi kontrol edin"
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -2898,50 +2898,61 @@ msgstr "无法连接 %(compute)s"
|
|||
msgid "%(compute)s is the VM's own host under another name"
|
||||
msgstr "%(compute)s 是该虚拟机所在主机的另一个名称"
|
||||
|
||||
#: instances/jobs.py:947
|
||||
#: instances/jobs.py:963
|
||||
#, python-format
|
||||
msgid ""
|
||||
"The VM migrated, but autostart could not be set on the destination: %(error)s"
|
||||
msgstr "虚拟机已迁移,但无法在目标主机上设置自动启动:%(error)s"
|
||||
|
||||
#: instances/jobs.py:969
|
||||
#, python-format
|
||||
msgid "The VM migrated, but its NVRAM file stays on the source: %(error)s"
|
||||
msgstr "虚拟机已迁移,但其 NVRAM 文件仍留在源主机上:%(error)s"
|
||||
|
||||
#: instances/jobs.py:976
|
||||
msgid "Migrate VM"
|
||||
msgstr "迁移虚拟机"
|
||||
|
||||
#: instances/jobs.py:991
|
||||
#: instances/jobs.py:1023
|
||||
msgid "Migrating the VM"
|
||||
msgstr "正在迁移虚拟机"
|
||||
|
||||
#: instances/jobs.py:1038
|
||||
#: instances/jobs.py:1070
|
||||
msgid "libvirt is still migrating the VM"
|
||||
msgstr "libvirt 仍在迁移虚拟机"
|
||||
|
||||
#: instances/jobs.py:1045
|
||||
#: instances/jobs.py:1079
|
||||
msgid "The VM stayed on the source host"
|
||||
msgstr "虚拟机留在了源主机上"
|
||||
|
||||
#: instances/jobs.py:1047
|
||||
#: instances/jobs.py:1081
|
||||
msgid ""
|
||||
"The VM is on both computes or on neither; check them, then resolve this job."
|
||||
msgstr "虚拟机同时位于两个计算节点上,或都不在;请检查后处理此任务。"
|
||||
|
||||
#: instances/jobs.py:1095
|
||||
#: instances/jobs.py:1129
|
||||
msgid "Move disk"
|
||||
msgstr "移动磁盘"
|
||||
|
||||
#: instances/jobs.py:1102
|
||||
#: instances/jobs.py:1136
|
||||
msgid "Choose a disk of the VM"
|
||||
msgstr "请选择虚拟机的一个磁盘"
|
||||
|
||||
#: instances/jobs.py:1126
|
||||
#: instances/jobs.py:1160
|
||||
msgid "Copying the disk"
|
||||
msgstr "正在复制磁盘"
|
||||
|
||||
#: instances/jobs.py:1169
|
||||
#: instances/jobs.py:1203
|
||||
#, python-format
|
||||
msgid "Another block job runs on %(dev)s; check the VM"
|
||||
msgstr "%(dev)s 上正在运行另一个块任务;请检查虚拟机"
|
||||
|
||||
#: instances/jobs.py:1175
|
||||
#: instances/jobs.py:1209
|
||||
#, python-format
|
||||
msgid "The disk stayed in %(path)s; remove %(dest)s if it is there"
|
||||
msgstr "磁盘仍在 %(path)s;如果 %(dest)s 存在,请将其删除"
|
||||
|
||||
#: instances/jobs.py:1177
|
||||
#: instances/jobs.py:1211
|
||||
#, python-format
|
||||
msgid "The definitions of %(dev)s name %(paths)s; check the VM"
|
||||
msgstr "%(dev)s 的定义指向 %(paths)s;请检查虚拟机"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue