mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 19:26:48 +00:00
The delete ran in the request: undefine, then the VM's record, then its disk files. A request that died in between (a gunicorn worker past its timeout, OOM, a restart), or a database write that failed after the undefine, left a record that later showed as removed outside the panel, and the disk files the user asked to delete were never deleted. Deleting a VM is now an instance.delete job. It holds the compute exclusively, as the request did, so no other VM attaches a file it is about to delete. The job chooses the files before it stops the VM (a host that cannot be checked still stops nothing) and records them first. It then stops and undefines the VM, removes its record, and deletes the files, each with its own Logs row. Recovery decides from libvirt: a VM still on its host means the delete failed; a VM gone means libvirt did it, so recovery removes the record and deletes the recorded files that are still there. The page queues the job and returns to the VM page, where the job panel follows it and opens the VM list when it ends; disks that were kept are the result's warning. The API's delete answers 202 with the job and needs a submission_key. backing_path moves to wvmConnect, as recovery has no domain to ask.
67 lines
3 KiB
Python
67 lines
3 KiB
Python
"""
|
|
A migration may move a VM between a request reading it and taking its
|
|
compute's claim: the request then holds the old compute's claim, not the
|
|
new one's. It must not run on the new host but answer "moved, try again",
|
|
and the retry claims the right compute.
|
|
"""
|
|
from contextlib import contextmanager
|
|
from unittest.mock import PropertyMock, patch
|
|
|
|
from django.contrib.auth import get_user_model
|
|
from django.test import TestCase
|
|
from django.urls import reverse
|
|
|
|
from computes.models import Compute
|
|
from instances.jobs import DeleteInstance
|
|
from instances.models import Instance
|
|
from operations.models import Operation
|
|
from vrtManager.util import OperationError
|
|
|
|
|
|
class MovedWhileWaitingTestCase(TestCase):
|
|
def setUp(self):
|
|
self.admin = get_user_model().objects.create_superuser("moved_admin", "m@example.com", "x")
|
|
self.source = Compute.objects.create(name="src", hostname="192.0.2.1", login="root", password="", type=1)
|
|
self.target = Compute.objects.create(name="dst", hostname="192.0.2.2", login="root", password="", type=1)
|
|
self.vm = Instance.objects.create(
|
|
compute=self.source, name="moved-vm", uuid="eeeeeeee-ffff-0000-1111-222222222222"
|
|
)
|
|
self.client.force_login(self.admin)
|
|
|
|
def poweroff(self, lock):
|
|
with patch("instances.views.libvirt_instance_lock", lock), patch(
|
|
"instances.models.wvmInstance"
|
|
) as wvm, patch.object(Compute, "status", new_callable=PropertyMock, return_value=True):
|
|
response = self.client.post(reverse("instances:poweroff", args=[self.vm.id]))
|
|
return response, wvm.return_value.shutdown.called
|
|
|
|
def test_a_vm_moved_while_the_request_waited_is_not_touched(self):
|
|
@contextmanager
|
|
def lock_after_a_migration(instance):
|
|
# the migration that held the lock moved the VM before releasing it
|
|
Instance.objects.filter(pk=instance.pk).update(compute=self.target)
|
|
yield
|
|
|
|
response, touched = self.poweroff(lock_after_a_migration)
|
|
self.assertFalse(touched)
|
|
self.assertEqual(response.status_code, 302)
|
|
messages = [str(m) for m in response.wsgi_request._messages]
|
|
self.assertIn("The VM was moved meanwhile; try again.", messages)
|
|
|
|
def test_a_vm_that_stayed_is_changed(self):
|
|
@contextmanager
|
|
def plain_lock(instance):
|
|
yield
|
|
|
|
response, touched = self.poweroff(plain_lock)
|
|
self.assertTrue(touched)
|
|
|
|
def test_destroy_of_a_vm_moved_while_it_waited_is_not_run(self):
|
|
# the delete job holds the whole compute; a migration moved the VM before it ran
|
|
with patch("instances.models.wvmInstance"):
|
|
self.client.post(reverse("instances:destroy", args=[self.vm.id]),
|
|
{"delete_disk": "on", "submission_key": "key-moved001"})
|
|
op = Operation.objects.get(kind="instance.delete")
|
|
Instance.objects.filter(pk=self.vm.pk).update(compute=self.target)
|
|
with self.assertRaisesMessage(OperationError, "The VM moved to another compute"):
|
|
DeleteInstance().precheck(op, None)
|