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