1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-09 03:36:48 +00:00

fix(logs): a resolved migration and an unapplied console setting log what happened

From the Codex and agy review of the VM audit log before v3.2.0:

- Resolving an uncertain migration wrote its row without the VM's UUID,
  because the migrate job writes no entries of its own; the resolution now
  takes the UUID from the job's intent, so the VM's Logs tab shows it.
- A console keymap or listen address the VM could not take (no graphics
  device, or a console on a socket) was logged as done although nothing
  changed; it now writes no row, as an edit that changes nothing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
catborise 2026-10-24 14:28:09 +03:00
parent c1d36053d6
commit 70e5c317ff
3 changed files with 34 additions and 6 deletions

View file

@ -90,6 +90,17 @@ class VMRowTests(TestCase):
self.assertEqual(rows, [("VM vm: set console password: The VM has no graphics device", False),
("VM vm: set console password", True)])
def test_a_console_setting_that_changed_nothing_writes_no_row(self):
self.proxy.get_console_type.return_value = "vnc"
self.proxy.set_console_keymap.return_value = False # no graphics device
self.proxy.set_console_listener_addr.return_value = False # a socket, no address
self.post("update_console", {"listen_on": "0.0.0.0", "keymap": "de"})
self.proxy.set_console_keymap.return_value = None # the define's result
self.proxy.set_console_listener_addr.return_value = None
self.post("update_console", {"listen_on": "0.0.0.0", "keymap": "de"})
self.assertEqual(self.rows(), [("VM vm: set console keymap de", True),
("VM vm: set console listen address 0.0.0.0", True)])
def test_a_deleted_vm_and_each_of_its_disks_have_their_own_row(self):
self.proxy.split_disk_paths_by_use.return_value = (["/pool/a.qcow2", "/pool/b.qcow2"], [])
volume = self.proxy.get_volume_by_path.return_value
@ -159,3 +170,18 @@ class JobRowTests(TestCase):
op = Operation(kind=kind, resource_label=label, params=params, intent=intent)
state = Operation.FAILED if kind == "snapshot.delete" else Operation.SUCCEEDED
self.assertEqual(get_kind(kind).log_entry(op, state), entry)
class MigrationResolutionRowTests(TestCase):
def test_a_resolved_migration_carries_the_vm_uuid(self):
from operations import jobs
from operations.models import Operation
admin = get_user_model().objects.create_superuser("res-admin", "res@example.com", "pw")
op = Operation.objects.create(kind="instance.migrate", user=admin, username=admin.username,
submission_key="key-res-00000001", resource="instance:" + UUID,
resource_label="c1/vm", state=Operation.UNCERTAIN,
intent={"compute": 1, "uuid": UUID})
jobs.resolve(op.pk, admin, Operation.SUCCEEDED, "the VM runs on the target")
row = Logs.objects.get(message__contains="resolved")
self.assertEqual((row.instance, row.instance_uuid, row.success), ("c1/vm", UUID, True))

View file

@ -1781,12 +1781,13 @@ def update_console(request, pk):
if "keymap" in form.changed_data or "clear_keymap" in form.changed_data:
keymap = "" if form.cleaned_data["clear_keymap"] else form.cleaned_data["keymap"]
with audit_vm(request, instance, f"set console keymap {keymap}" if keymap else "remove console keymap"):
instance.proxy.set_console_keymap(keymap)
# False: no graphics device, nothing changed
with audit_vm(request, instance, f"set console keymap {keymap}" if keymap else "remove console keymap") as entry:
entry.skip = instance.proxy.set_console_keymap(keymap) is False
if "listen_on" in form.changed_data:
with audit_vm(request, instance, f"set console listen address {form.cleaned_data['listen_on']}"):
instance.proxy.set_console_listener_addr(form.cleaned_data["listen_on"])
with audit_vm(request, instance, f"set console listen address {form.cleaned_data['listen_on']}") as entry:
entry.skip = instance.proxy.set_console_listener_addr(form.cleaned_data["listen_on"]) is False
if not form.is_valid():
messages.error(request, _("Console settings were not saved: %(errors)s") % {"errors": form.errors.as_text()})

View file

@ -262,9 +262,10 @@ def resolve(op_id, user, outcome_state, note):
Operation.objects.filter(pk=op.pk).update(state=outcome_state, resolution=outcome_state,
resolution_note=note, resolved_by=user, resolved_at=timezone.now())
Claim.objects.filter(operation=op).delete()
# the host, VM and UUID columns of the kind's own entries, so the VM's log shows it
# the host, VM and UUID columns of the kind's own entries, so the VM's log shows it;
# a kind without entries (a migration) still names its VM by the UUID in its intent
entry = _entry(op, outcome_state)
host, instance, uuid = entry[:3] if entry is not None else ("-", op.resource_label, "")
host, instance, uuid = entry[:3] if entry is not None else ("-", op.resource_label, op.intent.get("uuid", ""))
_log(user.username, host, instance, uuid, f"Job {op.pk} resolved as {outcome_state}: {note}",
outcome_state == Operation.SUCCEEDED)
op.refresh_from_db()