1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-09 11:46:49 +00:00
webvirtcloud/storages/tests/test_volume_clone_job.py
catborise cad392f79c fix(jobs): VM create and volume clone check the user and the name again in the worker
Every other job re-checks its permission and state in precheck before any
effect; these two checked only when queued. A queued create of a user who
is no longer a superuser, or whose VM name a host took meanwhile, ran; so
did a volume clone whose name a volume made outside the panel took.
2026-11-05 16:16:47 +03:00

89 lines
4.9 KiB
Python

"""Volume clone runs as a job: the page queues it, the worker copies."""
from types import SimpleNamespace
from unittest.mock import patch
from django.contrib.auth import get_user_model
from django.core.exceptions import PermissionDenied
from django.test import TransactionTestCase
from django.urls import reverse
from computes.models import Compute
from operations.jobs import Context
from operations.models import Claim, Operation, acquire
from libvirt import libvirtError
from storages.jobs import CloneVolume
class VolumeCloneJobTestCase(TransactionTestCase):
def setUp(self):
self.compute = Compute.objects.create(name="vol-c", hostname="192.0.2.20", login="root", password="", type=1)
admin = get_user_model().objects.create_superuser("vol_admin", "a@example.com", "pw")
self.client.force_login(admin)
patcher = patch("storages.jobs.wvmStorage")
self.stg = patcher.start().return_value
self.addCleanup(patcher.stop)
self.stg.get_volumes.return_value = ["base.qcow2"]
self.stg.get_volume_format_type.return_value = "qcow2"
self.stg.clone_name.return_value = "copy.qcow2"
self.stg.clone_volume.return_value = "copy.qcow2"
@patch("storages.views.wvmStorage")
def test_the_page_queues_the_copy_and_the_worker_makes_it(self, page):
page.return_value.is_active.return_value = False
page.return_value.get_size.return_value = (1, 1)
url = reverse("storage", args=[self.compute.pk, "pool-a"])
response = self.client.post(url, {"cln_volume": "1", "image": "base.qcow2", "name": "copy",
"submission_key": "key-vol00001"})
self.assertRedirects(response, url, fetch_redirect_response=False)
op = Operation.objects.get()
self.assertEqual((op.kind, op.intent["target"]), ("volume.clone", "copy.qcow2"))
self.assertIn((f"volume:{self.compute.pk}:pool-a/copy.qcow2", "exclusive"),
set(Claim.objects.values_list("resource", "mode")))
self.stg.clone_volume.assert_not_called()
acquire("wvc-test:1:a", "wvc-test")
Operation.objects.filter(pk=op.pk).update(state=Operation.RUNNING, worker="wvc-test:1:a")
op.refresh_from_db()
CloneVolume().run(op, Context(op, SimpleNamespace(identity="wvc-test:1:a")))
op.refresh_from_db()
self.stg.clone_volume.assert_called_once_with("base.qcow2", "copy", None, False)
self.assertEqual(op.intent["created"], "copy.qcow2")
self.assertIn(f"key=pool%3A{self.compute.pk}%3Apool-a", self.client.get(url).content.decode())
@patch("storages.views.wvmStorage")
def test_a_refused_copy_is_a_message(self, page):
# a name in use, an ISO image: the job's plan refuses, the page says why
page.return_value.is_active.return_value = False
page.return_value.get_size.return_value = (1, 1)
url = reverse("storage", args=[self.compute.pk, "pool-a"])
self.stg.get_volumes.return_value = ["base.qcow2", "copy.qcow2", "install.ISO"]
for key, image, why in (("key-vol00002", "base.qcow2", "Name of volume already in use"),
("key-vol00003", "install.ISO", "An ISO image is not cloned")):
with self.subTest(image=image):
response = self.client.post(url, {"cln_volume": "1", "image": image, "name": "copy",
"submission_key": key}, follow=True)
self.assertContains(response, why)
self.assertFalse(Operation.objects.exists())
def test_recovery(self):
self.stg.get_volumes.return_value = ["base.qcow2", "copy.qcow2"]
op = Operation(params={"compute": self.compute.pk, "pool": "pool-a"}, intent={"target": "copy.qcow2"})
self.assertEqual(CloneVolume().reconcile(op).state, Operation.UNCERTAIN) # there, not proven complete
self.stg.get_volumes.return_value = []
self.assertEqual(CloneVolume().reconcile(op).state, Operation.FAILED)
op.intent["created"] = "copy.qcow2"
self.assertEqual(CloneVolume().reconcile(op).state, Operation.SUCCEEDED)
def test_the_worker_checks_the_user_and_the_name_again(self):
user = get_user_model().objects.get(username="vol_admin")
op = Operation(kind="volume.clone", user=user, intent={"target": "copy.qcow2"},
params={"compute": self.compute.pk, "pool": "pool-a", "image": "base.qcow2", "name": "copy",
"format": "", "prealloc": False})
CloneVolume().precheck(op, None)
self.stg.get_volumes.return_value = ["base.qcow2", "copy.qcow2"] # made outside the panel meanwhile
with self.assertRaisesMessage(libvirtError, "Name of volume already in use"):
CloneVolume().precheck(op, None)
user.is_superuser = False
with self.assertRaises(PermissionDenied):
CloneVolume().precheck(op, None)