"""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)