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

feat(instances): a running migration can be cancelled; its time limit goes

This commit is contained in:
catborise 2026-10-15 20:42:50 +03:00
parent 31cbe60aa4
commit c2d3285e0f
7 changed files with 69 additions and 127 deletions

View file

@ -1,9 +1,7 @@
"""Migration: the source definition never stays behind; a shut-off VM's
definition is moved by the app, not by libvirt's offline migration."""
import threading
import unittest
from unittest.mock import patch
import libvirt
from django.conf import settings
@ -427,64 +425,3 @@ class DestinationDisksTestCase(unittest.TestCase):
xml = with_disks("<disk type='volume' device='disk'><source pool='p' volume='v'/><target dev='vdc'/></disk>")
with self.assertRaisesRegex(util.OperationError, "vdc \\(p/v\\)"):
self.migrate(host, xml)
class SlowDomain(FakeDomain):
"""A migration that does not finish until its job is aborted."""
def __init__(self, **kw):
super().__init__(**kw)
self.aborted = threading.Event()
def migrate(self, dconn, flags, dname, uri, bandwidth):
if not self.aborted.wait(5):
raise AssertionError("the migration was not aborted")
raise libvirt.libvirtError("operation aborted: job 'migration out' canceled by client")
def abortJob(self):
self.aborted.set()
class MigrationTimeLimitTestCase(unittest.TestCase):
"""A migration that does not converge is cancelled after the time limit,
before the web server ends the request; the VM stays on the source."""
def test_a_migration_over_the_limit_is_cancelled(self):
source = FakeSource(1)
source.instance = SlowDomain()
with self.assertRaisesRegex(util.OperationError, "did not finish within 0.05 s and was cancelled.*auto converge"):
destination().moveto(source, "vm", live=True, unsafe=False, offline=False, timeout=0.05)
self.assertTrue(source.instance.aborted.is_set())
def test_a_migration_within_the_limit_stops_its_timer(self):
timers = []
class Timer(threading.Timer):
def __init__(self, *args):
super().__init__(*args)
self.joined = False
timers.append(self)
def join(self, timeout=None):
self.joined = True
super().join(timeout)
source = FakeSource(1)
with patch("vrtManager.instance.threading.Timer", Timer):
destination().moveto(source, "vm", live=True, unsafe=False, offline=False, timeout=5)
self.assertEqual(len(source.instance.calls), 1)
self.assertTrue(timers[0].finished.is_set() and timers[0].joined)
self.assertFalse(timers[0].is_alive())
def test_a_failure_the_cancel_did_not_cause_keeps_its_error(self):
source = FakeSource(1)
source.instance = SlowDomain()
def refused():
source.instance.aborted.set() # migrate() gives up, but the abort itself failed
raise libvirt.libvirtError("cannot abort migration in confirm phase")
source.instance.abortJob = refused
with self.assertRaisesRegex(libvirt.libvirtError, "operation aborted") as caught:
destination().moveto(source, "vm", live=True, unsafe=False, offline=False, timeout=0.05)
self.assertNotIsInstance(caught.exception, util.OperationError)