1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 19:26:48 +00:00
webvirtcloud/instances/tests/test_audit_rows.py
catborise c1d36053d6 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>
2026-10-24 14:14:53 +03:00

76 lines
3.9 KiB
Python

"""VM page owner changes and flavor changes write rows on the Logs page."""
from accounts.models import UserInstance
from appsettings.models import AppSettings
from computes.models import Compute
from django.contrib.auth import get_user_model
from django.test import TestCase
from django.urls import reverse
from instances.models import Instance
from logs.models import Logs
def rows():
return list(Logs.objects.order_by("id").values_list("message", "success"))
class VMOwnerRowTests(TestCase):
def setUp(self):
self.client.force_login(get_user_model().objects.create_superuser("audit-admin", "audit@example.com", "pw"))
self.bob = get_user_model().objects.create_user("bob")
self.carol = get_user_model().objects.create_user("carol")
compute = Compute.objects.create(name="c1", hostname="127.0.0.1", login="", password="", type=4)
self.vm = Instance.objects.create(compute=compute, name="vm", uuid="77777777-7777-7777-7777-777777777777")
Logs.objects.all().delete() # the login's row
def test_vm_page_owners(self):
add = reverse("instances:add_owner", args=[self.vm.pk])
self.client.post(add, {"user_id": self.bob.pk})
self.client.post(add, {"user_id": self.bob.pk}) # already an owner: nothing changed
AppSettings.objects.filter(key="ALLOW_INSTANCE_MULTIPLE_OWNER").update(value="False")
self.client.post(add, {"user_id": self.carol.pk})
self.assertFalse(UserInstance.objects.filter(user=self.carol).exists())
ui = UserInstance.objects.get(user=self.bob)
self.client.post(reverse("instances:del_owner", args=[self.vm.pk]), {"userinstance": ui.pk})
self.assertEqual(rows(), [
("VM vm: add owner bob", True),
("VM vm: add owner carol: only one owner is allowed", False),
("VM vm: remove owner bob", True),
])
shown = [log["message"] for log in self.client.get(reverse("vm_logs", args=[self.vm.pk])).json()]
self.assertCountEqual(shown, [m for m, _ in rows()])
class FlavorRowTests(TestCase):
def setUp(self):
self.admin = get_user_model().objects.create_superuser("audit-admin", "audit@example.com", "pw")
self.client.force_login(self.admin)
Logs.objects.all().delete() # the login's row
def test_flavor_web_and_api(self):
from instances.models import Flavor
from rest_framework.test import APIClient
data = {"label": "tiny-a", "memory": 512, "vcpu": 1, "disk": 10}
self.client.post(reverse("instances:flavor_create"), data)
flavor = Flavor.objects.get(label="tiny-a")
self.client.post(reverse("instances:flavor_update", args=[flavor.pk]), {**data, "vcpu": 2})
self.client.post(reverse("instances:flavor_update", args=[flavor.pk]), {**data, "vcpu": 2}) # no change
self.client.post(reverse("instances:flavor_delete", args=[flavor.pk]))
self.assertFalse(Flavor.objects.filter(label="tiny-a").exists())
api = APIClient()
api.force_authenticate(self.admin)
data = {"label": "huge-a", "memory": 4096, "vcpu": 4, "disk": 40}
self.assertEqual(api.post(reverse("instance-flavor-list"), data, format="json").status_code, 201)
detail = reverse("instance-flavor-detail", args=[Flavor.objects.get(label="huge-a").pk])
self.assertEqual(api.patch(detail, {"vcpu": 8}, format="json").status_code, 200)
self.assertEqual(api.put(detail, {**data, "vcpu": 8, "memory": 8192}, format="json").status_code, 200)
self.assertEqual(Flavor.objects.get(label="huge-a").memory, 8192)
self.assertEqual(api.delete(detail).status_code, 204)
self.assertFalse(Flavor.objects.filter(label="huge-a").exists())
self.assertEqual([m for m, _ in rows()], [
"Flavor tiny-a: add", "Flavor tiny-a: change vcpu", "Flavor tiny-a: delete",
"Flavor huge-a: add", "Flavor huge-a: change vcpu", "Flavor huge-a: change memory", "Flavor huge-a: delete",
])