1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-11 12:46:49 +00:00

fix(logs): a row says what happened: a recovered migration, a failed clone step, a device not there

- A migration whose libvirt call failed but which recovery finds moved (or
  cannot tell) gets a row of that end; only a failure already logged by
  migrate_instance is not logged twice.
- A clone made with its resize or start failed, and a recovered migration
  whose autostart or NVRAM step failed, write a failed row of that step, as
  migrate_instance does for its later steps.
- Setting the link state of an interface or the video model of a VM that
  has none is refused: it changed nothing and was logged as done.
- The rows of a removed VM record (deleted or expired) name its host, VM
  and UUID, so the VM's history has them.
This commit is contained in:
catborise 2026-11-07 14:22:47 +03:00
parent 470cc9860e
commit 528b4492f6
14 changed files with 390 additions and 304 deletions

View file

@ -17,6 +17,7 @@ from appsettings.settings import get_settings
from computes.models import Compute
from instances.jobs import CloneInstance
from instances.models import Instance
from logs.models import Logs
from operations.utils import words
from operations.jobs import Cancelled, Context
from vrtManager.util import ConnectionFailed, OperationError
@ -148,6 +149,9 @@ class CloneJobTestCase(TransactionTestCase):
op, result = self.clone_with(**{"clone-start": "true"})
self.assertTrue(Instance.objects.filter(uuid=op.intent["uuid"]).exists())
self.assertIn("The clone is made, but not as asked", words(result["warning"]))
# the job succeeded: Logs has the step that failed too
self.assertTrue(Logs.objects.filter(instance_uuid=op.intent["uuid"], success=False,
message="VM copy: resize or start the clone: no memory").exists())
def queued(self):
self.client.post(reverse("instances:clone", args=[self.source.pk]),

View file

@ -11,6 +11,7 @@ from appsettings.settings import get_settings
from computes.models import Compute
from instances.jobs import MigrateInstance
from instances.models import Instance
from logs.models import Logs
from libvirt import VIR_DOMAIN_XML_INACTIVE, VIR_ERR_SYSTEM_ERROR, VIR_FROM_RPC, libvirtError
from operations.jobs import Cancelled, Context, get_kind
from operations.models import Claim, Operation, acquire
@ -124,6 +125,8 @@ class MigrateJobTestCase(TransactionTestCase):
outcome = MigrateInstance().reconcile(op)
self.assertEqual(outcome.state, Operation.SUCCEEDED)
self.assertIn("autostart could not be set", outcome.result["warning"][0]["text"])
self.assertTrue(Logs.objects.filter(instance_uuid=UUID, success=False,
message="VM mig-vm: restore autostart on mig-dst: refused").exists())
def test_a_copy_claims_its_volume_on_both_computes(self):
Instance.proxy.get_disk_devices.return_value = [
@ -261,8 +264,6 @@ class MigrateJobTestCase(TransactionTestCase):
proxy.return_value.instance.abortJob.assert_not_called()
def test_each_end_of_a_migration_leaves_one_logs_row(self):
from logs.models import Logs
op = self.queue(live_migrate="true")
ctx = self.start(op)
get_kind(op.kind).run(op, ctx) # migrate_instance writes its row
@ -275,6 +276,29 @@ class MigrateJobTestCase(TransactionTestCase):
self.assertEqual(MigrateInstance().log_entry(queued, Operation.CANCELLED),
("mig-src", "mig-vm", UUID, "VM mig-vm: migrate to mig-dst (cancelled)"))
def test_a_failed_migration_recovery_finds_moved_has_a_row_of_that_end(self):
op = self.queue(live_migrate="true")
ctx = self.start(op)
self.dest_conn.moveto.side_effect = libvirtError("connection reset while migrating")
with self.assertRaises(Exception):
get_kind(op.kind).run(op, ctx) # migrate_instance writes the failed row
op.refresh_from_db()
self.assertIsNone(MigrateInstance().log_entry(op, Operation.FAILED)) # that row is the end
for state, message in ((Operation.SUCCEEDED, "VM mig-vm: migrate to mig-dst"),
(Operation.UNCERTAIN, "VM mig-vm: migrate to mig-dst (uncertain)")):
with self.subTest(state):
self.assertEqual(MigrateInstance().log_entry(op, state)[3], message)
def test_a_migration_moved_then_failed_to_record_has_its_row_once(self):
op = self.queue(live_migrate="true")
ctx = self.start(op)
with patch("instances.utils.record_migration", side_effect=RuntimeError("database locked")), \
self.assertRaises(RuntimeError):
get_kind(op.kind).run(op, ctx) # the migration's row, then the failed record's
op.refresh_from_db()
self.assertIsNone(MigrateInstance().log_entry(op, Operation.SUCCEEDED)) # recovery finds it moved
self.assertEqual(Logs.objects.filter(instance_uuid=UUID, message__startswith="VM mig-vm: migrate (").count(), 1)
def test_recording_a_migration_holds_the_ownership_lock(self):
# a sync of a third compute must not move the owners at the same time
from instances import utils