1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 19:26:48 +00:00
webvirtcloud/instances/test_live_dataloss.py
catborise dbbf2ce2e8 test(instances): isolate the live libvirt suite on shared hosts
- Runs only when TEST_LIBVIRT_HOST is set, so manage.py test on a
  developer machine no longer creates VMs on the local libvirtd.
- Every VM and volume is named wvc-test-*, and volumes live in a
  temporary wvc-test pool (/var/lib/libvirt/wvc-test) instead of the
  default pool next to real images.
- Cleanup removes only prefixed domains and the test pool's volumes;
  it also clears leftovers of an interrupted run before starting, and
  tolerates domains that turned transient.
- tearDownClass fails if any domain or volume outside the test
  namespace appeared, disappeared or was replaced.
- livetest gains create_volume/define_vm helpers and
  test_live_dataloss.py, whose setUpClass opens the class transaction
  last, so a failed libvirt connection cannot lock the test database
  for later test classes.

The first live tests reproduce two known external-snapshot data losses
and are marked expectedFailure: reverting deletes a disk added after
the snapshot, and deleting the older of two snapshots deletes the
newer one's overlay.
2026-10-04 01:08:11 +03:00

115 lines
4.4 KiB
Python

"""Live reproductions of the Wave 1a data-loss findings.
Each test asserts the safe behavior and is marked expectedFailure until
its finding is fixed; a fix turns it into an unexpected success, which
fails the run until the marker is removed. Runs only with
TEST_LIBVIRT_HOST set (see instances/livetest.py).
"""
import os
import unittest
import libvirt
from computes.models import Compute
from django.contrib.auth import get_user_model
from django.test import TestCase
from django.urls import reverse
from vrtManager.connection import connection_manager
from . import livetest
from .models import Instance
from .utils import refr
P = livetest.PREFIX
class LiveDataLossTestCase(TestCase):
@classmethod
def setUpClass(cls):
if not livetest.enabled():
raise unittest.SkipTest("Set TEST_LIBVIRT_HOST to run the live libvirt tests")
cls.compute_args = dict(
hostname=os.environ["TEST_LIBVIRT_HOST"],
login=os.environ.get("TEST_LIBVIRT_LOGIN", ""),
password=os.environ.get("TEST_LIBVIRT_PASSWORD", ""),
type=int(os.environ.get("TEST_LIBVIRT_TYPE", 4)),
)
cls.conn = connection_manager.get_connection(*cls.compute_args.values())
livetest.cleanup(cls.conn)
livetest.ensure_pool(cls.conn)
cls.inventory = livetest.inventory(cls.conn)
# Last: a failure above must not leave the class transaction open,
# which would lock the shared test database for later test classes.
super().setUpClass()
@classmethod
def tearDownClass(cls):
try:
livetest.remove_pool(cls.conn)
finally:
super().tearDownClass()
if livetest.inventory(cls.conn) != cls.inventory:
raise AssertionError("Live tests changed objects outside the test namespace")
def setUp(self):
self.compute = Compute.objects.create(name="live-dataloss", details="test", **self.compute_args)
admin = get_user_model().objects.create_superuser("live_admin", "l@example.com", "pw")
self.client.force_login(admin)
self.client.raise_request_exception = False
def tearDown(self):
livetest.cleanup(self.conn)
# helpers
def vm(self, name, disks, uefi=False):
dom = livetest.define_vm(self.conn, P + name, disks, uefi=uefi)
refr(self.compute)
return dom, Instance.objects.get(compute=self.compute, uuid=dom.UUIDString())
def post(self, view, instance, data=None):
return self.client.post(
reverse(f"instances:{view}", args=[instance.id]),
data or {},
HTTP_REFERER=reverse("instances:instance", args=[instance.id]),
)
def disk_sources(self, dom, flags=0):
from lxml import etree
return {
d.find("target").get("dev"): d.find("source").get("file")
for d in etree.fromstring(dom.XMLDesc(flags)).findall("devices/disk")
}
# R-03: external snapshots
@unittest.expectedFailure
def test_r03_revert_external_snapshot_keeps_disk_added_later(self):
base = livetest.create_volume(self.conn, P + "r03-a")
dom, inst = self.vm("r03-a", [base])
self.post("create_external_snapshot", inst, {"name": "snap"})
later = livetest.create_volume(self.conn, P + "r03-later")
dom.attachDeviceFlags(
f"<disk type='file' device='disk'><driver name='qemu' type='qcow2'/>"
f"<source file='{later}'/><target dev='vdb' bus='virtio'/></disk>",
libvirt.VIR_DOMAIN_AFFECT_CONFIG,
)
self.post("revert_external_snapshot", inst, {"name": "s1.snap", "date": "", "desc": ""})
self.assertTrue(livetest.volume_exists(self.conn, later), "revert deleted a disk the snapshot never had")
@unittest.expectedFailure
def test_r03_delete_older_external_snapshot_keeps_newer_snapshot_files(self):
base = livetest.create_volume(self.conn, P + "r03-b")
dom, inst = self.vm("r03-b", [base])
self.post("create_external_snapshot", inst, {"name": "older"})
self.post("create_external_snapshot", inst, {"name": "newer"})
sources = set(self.disk_sources(dom).values())
self.post("delete_external_snapshot", inst, {"name": "s1.older"})
for path in sources:
self.assertTrue(livetest.volume_exists(self.conn, path), f"{path} was deleted")
self.assertIn("s1.newer", dom.snapshotListNames(0))