1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-09 11:46:49 +00:00

feat(instances): VM clone runs as a job

This commit is contained in:
catborise 2026-10-14 23:42:54 +03:00
parent 49232c6e1d
commit fbf82da5a9
12 changed files with 346 additions and 270 deletions

View file

@ -1923,7 +1923,7 @@ class wvmInstance(wvmConnect):
mac_tuples = [mac[i : i + n] for i in range(0, len(mac), n)]
return ":".join(mac_tuples)
def clone_instance(self, clone_data):
def clone_instance(self, clone_data, uuid):
"""Copy a shut-off VM, with its disks and UEFI variables (NVRAM).
Inputs are checked before anything is allocated, and on failure only
the volumes this call created are removed. Returns the clone's UUID."""
@ -1935,10 +1935,10 @@ class wvmInstance(wvmConnect):
tree = etree.fromstring(self._XMLDesc(PERSISTENT_XML))
tree.find("name").text = clone_data["name"]
uuid = tree.find("uuid")
if uuid is None:
uuid = etree.SubElement(tree, "uuid")
uuid.text = util.randomUUID()
uuid_el = tree.find("uuid")
if uuid_el is None:
uuid_el = etree.SubElement(tree, "uuid")
uuid_el.text = uuid
self._set_options(
tree,
{

View file

@ -41,7 +41,7 @@ def proxy(nvram):
class CloneNvramTestCase(unittest.TestCase):
def test_the_copy_keeps_the_format_owner_and_mode(self):
p = proxy("<nvram format='qcow2'>/var/lib/libvirt/qemu/nvram/vm_VARS.qcow2</nvram>")
p.clone_instance({"name": "copy"})
p.clone_instance({"name": "copy"}, "abababab-0000-1111-2222-333333333333")
xml, original, _ = p.pool.createXMLFrom.call_args.args
volume = etree.fromstring(xml)
self.assertEqual(volume.findtext("name"), "copy_VARS.qcow2")
@ -54,7 +54,7 @@ class CloneNvramTestCase(unittest.TestCase):
def test_an_nvram_source_element_points_at_the_copy(self):
p = proxy("<nvram type='file'>\n <source file='/var/lib/libvirt/qemu/nvram/vm_VARS.qcow2'/>\n</nvram>")
p.clone_instance({"name": "copy"})
p.clone_instance({"name": "copy"}, "abababab-0000-1111-2222-333333333333")
p.pool.storageVolLookupByName.assert_called_once_with("vm_VARS.qcow2")
(defined,), _ = p._defineXML.call_args
self.assertEqual(
@ -64,5 +64,5 @@ class CloneNvramTestCase(unittest.TestCase):
def test_an_nvram_that_is_not_a_file_is_refused_before_any_copy(self):
p = proxy("<nvram type='network'><source protocol='iscsi' name='x'/></nvram>")
with self.assertRaises(util.OperationError):
p.clone_instance({"name": "copy"})
p.clone_instance({"name": "copy"}, "abababab-0000-1111-2222-333333333333")
p.pool.createXMLFrom.assert_not_called()

View file

@ -142,7 +142,7 @@ class TestCloneRollback(unittest.TestCase):
inst._defineXML = MagicMock(side_effect=libvirt.libvirtError("bad definition"))
with self.assertRaises(libvirt.libvirtError):
inst.clone_instance({"name": "copy", "disk-vda": "copy.qcow2", "disk_owner_uid": 0, "disk_owner_gid": 0})
inst.clone_instance({"name": "copy", "disk-vda": "copy.qcow2", "disk_owner_uid": 0, "disk_owner_gid": 0}, "abababab-0000-1111-2222-333333333333")
copy.delete.assert_called_once_with(0)
source_vol.delete.assert_not_called()
@ -153,7 +153,7 @@ class TestCloneRollback(unittest.TestCase):
inst.get_status = MagicMock(return_value=1)
inst._XMLDesc = MagicMock()
with self.assertRaisesRegex(libvirt.libvirtError, "Shut the VM down"):
inst.clone_instance({"name": "copy"})
inst.clone_instance({"name": "copy"}, "abababab-0000-1111-2222-333333333333")
inst._XMLDesc.assert_not_called()
@ -352,7 +352,7 @@ class TestEmptyDiskClone(unittest.TestCase):
# clone is still defined and looked up through the real test driver.
inst._XMLDesc = MagicMock(return_value=etree.tostring(config).decode())
inst.get_status = MagicMock(return_value=5) # only shut-off VMs are cloned
clone_uuid = inst.clone_instance({"name": "empty-disk-clone"})
clone_uuid = inst.clone_instance({"name": "empty-disk-clone"}, "abababab-0000-1111-2222-333333333333")
clone = conn.lookupByUUIDString(clone_uuid)
self.addCleanup(clone.undefine)
clone_config = etree.fromstring(clone.XMLDesc(libvirt.VIR_DOMAIN_XML_INACTIVE))
@ -371,5 +371,5 @@ class TestEmptyDiskClone(unittest.TestCase):
inst._XMLDesc = MagicMock()
inst.get_status = MagicMock(return_value=5)
with self.assertRaisesRegex(ValueError, "already exists"):
inst.clone_instance({"name": "test"})
inst.clone_instance({"name": "test"}, "abababab-0000-1111-2222-333333333333")
inst._XMLDesc.assert_not_called()