mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-11 12:46:49 +00:00
feat(logs): changes to networks, storage, interfaces, secrets and filters are logged
The Logs page now records every change an admin makes to a compute's libvirt objects, with the compute's name in the host column: - networks: add, start, stop, delete, autostart, DHCP range, fixed addresses, QoS and XML edits; - storage pools: add, start, stop, delete, autostart, from web and API; volumes: add and delete (web and API), clone and ISO upload; - host interfaces: add, start, stop, delete; - secrets: add, set value (never the value), delete; - network filters: add, clone, delete, XML edits, rules and references. A refused or failed change is a failed row with its reason (libvirt text through safe_text). The volume clone and upload jobs write their row when they end; a refused submission is a failed row at once. A volume row names the volume libvirt created (a dir pool adds .qcow2 or .img). Volume create or delete through the API on an inactive pool (409) is a failed row. Network filters: the rows read "Network filter f1: add" instead of "f1 network filter is created", and their host is the compute's name instead of its hostname. A duplicate name or UUID no longer raises a bare exception, and a filter XML that cannot be parsed no longer ends in UnboundLocalError.
This commit is contained in:
parent
e04adec87b
commit
5b8793ad4c
14 changed files with 716 additions and 147 deletions
38
interfaces/tests/test_audit_rows.py
Normal file
38
interfaces/tests/test_audit_rows.py
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
"""Host interface changes write rows on the Logs page."""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from computes.models import Compute
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from logs.models import Logs
|
||||
|
||||
|
||||
class InterfaceRowTests(TestCase):
|
||||
def setUp(self):
|
||||
self.compute = Compute.objects.create(name="c1", hostname="localhost", login="", password="", type=4)
|
||||
self.client.force_login(get_user_model().objects.create_superuser("audit-admin", "a@example.com", "pw"))
|
||||
Logs.objects.all().delete() # the login's row
|
||||
|
||||
@patch("interfaces.views.wvmInterface")
|
||||
@patch("interfaces.views.wvmInterfaces")
|
||||
def test_interface_actions(self, ifaces, iface):
|
||||
ifaces.return_value.can_change_interfaces.return_value = True
|
||||
ifaces.return_value.get_net_devices.return_value = ["eth0"]
|
||||
ifaces.return_value.get_ifaces.return_value = []
|
||||
iface.return_value.can_change_interfaces.return_value = True
|
||||
self.client.post(reverse("interfaces", args=[self.compute.id]), {
|
||||
"create": "", "name": "br1", "itype": "bridge", "start_mode": "onboot", "netdev": "eth0",
|
||||
"ipv4_type": "dhcp", "ipv6_type": "none", "stp": "on", "delay": "0"})
|
||||
ifaces.return_value.create_iface.assert_called_once()
|
||||
url = reverse("interface", args=[self.compute.id, "br1"])
|
||||
for action in ("start", "stop", "delete"):
|
||||
self.client.post(url, {action: ""})
|
||||
self.assertEqual(list(Logs.objects.order_by("id").values_list("message", "success", "host")), [
|
||||
("Interface br1: add", True, "c1"),
|
||||
("Interface br1: start", True, "c1"),
|
||||
("Interface br1: stop", True, "c1"),
|
||||
("Interface br1: delete", True, "c1"),
|
||||
])
|
||||
|
|
@ -6,6 +6,7 @@ from django.utils.translation import gettext_lazy as _
|
|||
from libvirt import libvirtError
|
||||
from computes.models import Compute
|
||||
from interfaces.forms import AddInterface
|
||||
from logs.audit import audit
|
||||
from vrtManager.interface import wvmInterface, wvmInterfaces
|
||||
from admin.decorators import superuser_only
|
||||
|
||||
|
|
@ -57,20 +58,21 @@ def interfaces(request, compute_id):
|
|||
form = AddInterface(request.POST, netdevs=netdevs)
|
||||
if form.is_valid():
|
||||
data = form.cleaned_data
|
||||
conn.create_iface(
|
||||
data["name"],
|
||||
data["itype"],
|
||||
data["start_mode"],
|
||||
data["netdev"],
|
||||
data["ipv4_type"],
|
||||
data["ipv4_addr"],
|
||||
data["ipv4_gw"],
|
||||
data["ipv6_type"],
|
||||
data["ipv6_addr"],
|
||||
data["ipv6_gw"],
|
||||
data["stp"],
|
||||
data["delay"],
|
||||
)
|
||||
with audit(request, compute, f"Interface {data['name']}", "add"):
|
||||
conn.create_iface(
|
||||
data["name"],
|
||||
data["itype"],
|
||||
data["start_mode"],
|
||||
data["netdev"],
|
||||
data["ipv4_type"],
|
||||
data["ipv4_addr"],
|
||||
data["ipv4_gw"],
|
||||
data["ipv6_type"],
|
||||
data["ipv6_addr"],
|
||||
data["ipv6_gw"],
|
||||
data["stp"],
|
||||
data["delay"],
|
||||
)
|
||||
return HttpResponseRedirect(request.get_full_path())
|
||||
else:
|
||||
for msg_err in form.errors.values():
|
||||
|
|
@ -117,13 +119,16 @@ def interface(request, compute_id, iface):
|
|||
# the page says why nothing is done on a host that cannot change interfaces
|
||||
if request.method == "POST" and changeable:
|
||||
if "stop" in request.POST:
|
||||
conn.stop_iface()
|
||||
with audit(request, compute, f"Interface {iface}", "stop"):
|
||||
conn.stop_iface()
|
||||
return HttpResponseRedirect(request.get_full_path())
|
||||
if "start" in request.POST:
|
||||
conn.start_iface()
|
||||
with audit(request, compute, f"Interface {iface}", "start"):
|
||||
conn.start_iface()
|
||||
return HttpResponseRedirect(request.get_full_path())
|
||||
if "delete" in request.POST:
|
||||
conn.delete_iface()
|
||||
with audit(request, compute, f"Interface {iface}", "delete"):
|
||||
conn.delete_iface()
|
||||
return HttpResponseRedirect(reverse("interfaces", args=[compute_id]))
|
||||
conn.close()
|
||||
except libvirtError as lib_err:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue