mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-11 04:36:48 +00:00
feat(logs): every VM operation writes an audit row the VM's Logs tab finds by UUID
Operations on a VM now write their rows the way the admin audit does: one
row per operation, done or failed with a safe reason, with the user, IP and
host. Each row names the VM ("VM web-1: delete snapshot snap1") and carries
its UUID in the new Logs.instance_uuid column.
- audit_vm and failed_vm wrap audit() and failed() for a VM. A failure the
view turns into a message is caught outside the block, so its failed row
is written; busy claims, permission and quota refusals and invalid input
write nothing. No password, key or XML goes into a row.
- All VM page operations move from addlogmsg to audit_vm, and addlogmsg
goes. QoS changes and defining a VM from XML are logged now; a gstfsd
error reply (root password, SSH key) is a failed row, not a done one.
- Partial results are rows of their own: the VM delete, then each disk
delete; the disk detach, then the volume delete; the migration, then a
failed autostart restore, NVRAM removal or record on the target.
- Migration rows use the compute name as the host column, as every other
row does.
- VM job rows (create, clone, snapshots) and their resolutions carry the
VM's UUID; snapshot job intents now record it. The owner rows of the VM
page and the accounts pages, and the ownership system rows, carry it too.
- The Logs tab loads vm_logs/<pk>/ and shows the rows with the VM's UUID,
so a rename keeps the history, two VMs with one name are told apart and
a VM on two computes is one VM. It gains a Result column. Rows from
before this change have no UUID and stay on the Logs page only.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
49cf16ecbe
commit
c1d36053d6
24 changed files with 639 additions and 524 deletions
|
|
@ -12,18 +12,19 @@ from logs.models import Logs
|
|||
logger = logging.getLogger("logs.audit")
|
||||
|
||||
|
||||
def write(user, host, instance, message, success=None, ip=None):
|
||||
def write(user, host, instance, message, success=None, ip=None, uuid=""):
|
||||
"""
|
||||
Save one Logs row. A failure is written to the log file instead and never
|
||||
raised, so an audit write cannot break the action it records.
|
||||
"""
|
||||
user, host, instance = user[:150], host[:255], instance[:120]
|
||||
user, host, instance, uuid = user[:150], host[:255], instance[:120], uuid[:36]
|
||||
try:
|
||||
with transaction.atomic():
|
||||
Logs.objects.create(user=user, host=host, instance=instance, message=message, success=success, ip=ip)
|
||||
Logs.objects.create(user=user, host=host, instance=instance, instance_uuid=uuid, message=message,
|
||||
success=success, ip=ip)
|
||||
except Exception as e:
|
||||
try:
|
||||
line = f"audit row not saved ({e!r}): {user} {host} {instance} success={success} ip={ip} {message}"
|
||||
line = f"audit row not saved ({e!r}): {user} {host} {instance} {uuid} success={success} ip={ip} {message}"
|
||||
logger.warning(line.replace("\r", "\\r").replace("\n", "\\n"))
|
||||
except Exception:
|
||||
pass
|
||||
|
|
@ -44,6 +45,7 @@ def request_ip(request):
|
|||
class Entry:
|
||||
action: str
|
||||
skip: bool = False
|
||||
uuid: str = ""
|
||||
|
||||
|
||||
def host_of(compute):
|
||||
|
|
@ -69,26 +71,37 @@ def reason(exc, user):
|
|||
|
||||
|
||||
@contextmanager
|
||||
def audit(request, compute, subject, action, instance=""):
|
||||
def audit(request, compute, subject, action, instance="", uuid=""):
|
||||
"""
|
||||
Write one row when the block ends: done, or failed with the reason when it
|
||||
raises (the exception goes on). The block may set entry.action, or
|
||||
entry.skip when nothing was done.
|
||||
raises (the exception goes on). The block may set entry.action, entry.uuid,
|
||||
or entry.skip when nothing was done.
|
||||
"""
|
||||
entry = Entry(action)
|
||||
entry = Entry(action, uuid=uuid)
|
||||
user, host, ip = request.user.get_username(), host_of(compute), request_ip(request)
|
||||
try:
|
||||
yield entry
|
||||
except Exception as exc:
|
||||
write(user, host, instance, f"{subject}: {entry.action}: {reason(exc, request.user)}", False, ip)
|
||||
write(user, host, instance, f"{subject}: {entry.action}: {reason(exc, request.user)}", False, ip, entry.uuid)
|
||||
raise
|
||||
if not entry.skip:
|
||||
write(user, host, instance, f"{subject}: {entry.action}", True, ip)
|
||||
write(user, host, instance, f"{subject}: {entry.action}", True, ip, entry.uuid)
|
||||
|
||||
|
||||
def failed(request, compute, subject, action, why, instance=""):
|
||||
def failed(request, compute, subject, action, why, instance="", uuid=""):
|
||||
"""Write the failure row of an action that was refused before it started."""
|
||||
write(request.user.get_username(), host_of(compute), instance, f"{subject}: {action}: {why}", False, request_ip(request))
|
||||
write(request.user.get_username(), host_of(compute), instance, f"{subject}: {action}: {why}", False,
|
||||
request_ip(request), uuid)
|
||||
|
||||
|
||||
def audit_vm(request, instance, action):
|
||||
"""audit() for an operation on a VM: the row names the VM and carries its UUID."""
|
||||
return audit(request, instance.compute, f"VM {instance.name}", action, instance=instance.name, uuid=instance.uuid)
|
||||
|
||||
|
||||
def failed_vm(request, instance, action, why):
|
||||
"""failed() for an operation on a VM, such as one whose error came back as a reply."""
|
||||
failed(request, instance.compute, f"VM {instance.name}", action, why, instance=instance.name, uuid=instance.uuid)
|
||||
|
||||
|
||||
def snapshot(obj):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue