1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 19:26:48 +00:00
webvirtcloud/vrtManager/tests/test_disk_move.py
catborise 42ab293dc5 test: remove code no test run executes
- 25 vrtManager test modules configured settings themselves when Django
  had none (MAC_OUI, and in test_network_ip a fake django module): under
  manage.py test settings are always configured, so it never ran.
- webvirtcloud/tests/test_common_tags.py tested no tag: it fetched an
  unknown URL, as test_error_pages and test_logging already do.
- .gitignore covers the parallel runs' test_db_N.sqlite3 files too.
2026-11-06 13:48:03 +03:00

152 lines
6.9 KiB
Python

"""A disk moves to another storage pool of its host: a running or paused
VM's by a block copy and a pivot, a shut-off VM's by a volume copy and a
redefinition; the source file stays. Disks that cannot move are refused
before anything changes, and a failed move deletes the copy unless a
definition names it."""
import unittest
from unittest import mock
import libvirt
from vrtManager import util
from vrtManager.instance import wvmInstance
IMAGES = "/var/lib/libvirt/images"
FAST = "/srv/fast"
def error(code, message="error"):
err = libvirt.libvirtError(message)
err.err = (code, 0, message, 2, None, None, None, 0, 0)
return err
def domain(path=f"{IMAGES}/vm.qcow2", fmt="qcow2", extra=""):
return (f"<domain><devices><disk type='file' device='disk'><driver name='qemu' type='{fmt}'/>"
f"<source file='{path}'/><target dev='vda'/>{extra}</disk></devices></domain>")
class Pool:
def __init__(self, files=()):
self.files = set(files)
self.deleted = []
refreshes = 0
def refresh(self, flags=0):
self.refreshes += 1
def storageVolLookupByName(self, name):
if name not in self.files:
raise error(libvirt.VIR_ERR_NO_STORAGE_VOL)
volume = mock.Mock()
volume.delete.side_effect = lambda flags: self.deleted.append(name)
return volume
def vm(state=1, xml=None, config=None, backing=None, fast=None, snapshots=0):
"""A wvmInstance of a VM in state whose vda is in IMAGES; pool fast is on FAST."""
proxy = wvmInstance.__new__(wvmInstance)
proxy.instance = dom = mock.Mock()
dom.info.return_value = [state, 0, 0, 1, 0]
dom.isPersistent.return_value = True
dom.isActive.return_value = state != 5
dom.snapshotNum.return_value = snapshots
dom.hasManagedSaveImage.return_value = 0
dom.blockJobInfo.return_value = {}
xml = xml or domain()
dom.XMLDesc.side_effect = lambda flags: (config or xml) if flags & libvirt.VIR_DOMAIN_XML_INACTIVE else xml
proxy.wvm = mock.Mock()
volume = proxy.wvm.storageVolLookupByPath.return_value
volume.info.return_value = [0, 1 << 30, 1 << 20]
volume.XMLDesc.return_value = f"<volume><backingStore><path>{backing}</path></backingStore></volume>" \
if backing else "<volume/>"
proxy.fast = fast or Pool()
proxy.file_pools = lambda: {"fast": (proxy.fast, FAST), "default": (Pool(), IMAGES)}
proxy.get_snapshot_jobs = lambda: []
return proxy
class DiskMovePlanTestCase(unittest.TestCase):
def test_a_disk_file_moves_into_another_pool(self):
move = vm().disk_move_plan("vda", "fast")
self.assertEqual((move["path"], move["dest"], move["format"], move["running"]),
(f"{IMAGES}/vm.qcow2", f"{FAST}/vm.qcow2", "qcow2", True))
def test_refusals(self):
cases = (
(vm(state=4), "fast", "defined and running, paused or shut off"),
(vm(snapshots=1), "fast", "snapshots"),
(vm(xml=domain(extra="<readonly/>")), "fast", "only writable raw and qcow2"),
(vm(xml=domain(fmt="vmdk")), "fast", "only writable raw and qcow2"),
(vm(config=domain(path=f"{IMAGES}/next.qcow2")), "fast", "the same in its running and next start"),
(vm(backing=f"{IMAGES}/base.qcow2"), "fast", "backing file"),
(vm(), "rbd", "not an active pool of disk files"),
(vm(), "default", "in pool default already"),
(vm(fast=Pool(files={"vm.qcow2"})), "fast", "Pool fast already has a file vm.qcow2"),
)
for proxy, pool, reason in cases:
with self.subTest(reason=reason):
with self.assertRaisesRegex(util.OperationError, reason):
proxy.disk_move_plan("vda", pool)
class MoveDiskTestCase(unittest.TestCase):
def test_a_running_vm_is_copied_and_pivoted(self):
proxy = vm()
proxy.instance.blockJobInfo.side_effect = [{"cur": 1, "end": 2}, {"cur": 2, "end": 2}]
with mock.patch("vrtManager.instance.time.sleep"):
proxy.move_disk(proxy.disk_move_plan("vda", "fast"))
dev, xml, params, flags = proxy.instance.blockCopy.call_args.args
self.assertEqual((dev, flags), ("vda", libvirt.VIR_DOMAIN_BLOCK_COPY_TRANSIENT_JOB))
self.assertIn(f"<source file='{FAST}/vm.qcow2'/>", xml)
proxy.instance.blockJobAbort.assert_called_once_with("vda", libvirt.VIR_DOMAIN_BLOCK_JOB_ABORT_PIVOT)
self.assertEqual(proxy.fast.refreshes, 2) # the plan's, and one so that the pool lists the copy
def test_a_shut_off_vm_gets_a_copy_and_a_new_definition(self):
proxy = vm(state=5)
proxy.fast.createXMLFrom = mock.Mock()
proxy.move_disk(proxy.disk_move_plan("vda", "fast"))
volume_xml, source, flags = proxy.fast.createXMLFrom.call_args.args
self.assertIn("<name>vm.qcow2</name><capacity>1073741824</capacity>", volume_xml)
self.assertIn(f'<source file="{FAST}/vm.qcow2" />', proxy.wvm.defineXML.call_args.args[0])
proxy.instance.blockCopy.assert_not_called()
def test_a_copy_that_ends_early_is_deleted(self):
proxy = vm()
proxy.instance.blockJobInfo.return_value = {} # aborted by a cancel
move = proxy.disk_move_plan("vda", "fast")
proxy.fast.files.add("vm.qcow2") # libvirt made it
with self.assertRaisesRegex(util.OperationError, "ended before it was complete"):
proxy.move_disk(move)
self.assertEqual(proxy.fast.deleted, ["vm.qcow2"])
def test_a_copy_a_definition_names_is_kept(self):
"""The pivot may have happened before its reply was lost."""
proxy = vm()
proxy.instance.blockJobInfo.side_effect = [{"cur": 2, "end": 2}, {}]
proxy.instance.blockJobAbort.side_effect = libvirt.libvirtError("Cannot recv data")
move = proxy.disk_move_plan("vda", "fast")
proxy.instance.XMLDesc.side_effect = lambda flags: domain(path=f"{FAST}/vm.qcow2")
proxy.fast.files.add("vm.qcow2")
with self.assertRaisesRegex(util.OperationError, f"the VM may use {FAST}/vm.qcow2 now"):
proxy.move_disk(move)
self.assertEqual(proxy.fast.deleted, [])
def test_a_file_made_by_someone_else_is_never_deleted(self):
"""It appeared after the plan, and the copy failed on it."""
proxy = vm(state=5)
move = proxy.disk_move_plan("vda", "fast")
proxy.fast.files.add("vm.qcow2")
proxy.fast.createXMLFrom = mock.Mock(side_effect=error(libvirt.VIR_ERR_STORAGE_VOL_EXIST))
with self.assertRaises(libvirt.libvirtError):
proxy.move_disk(move)
self.assertEqual(proxy.fast.deleted, [])
running = vm()
move = running.disk_move_plan("vda", "fast")
running.fast.files.add("vm.qcow2")
running.instance.blockCopy.side_effect = error(libvirt.VIR_ERR_OPERATION_FAILED)
with self.assertRaises(libvirt.libvirtError):
running.move_disk(move)
self.assertEqual(running.fast.deleted, [])