mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 19:26:48 +00:00
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>
76 lines
3.9 KiB
Python
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",
|
|
])
|