mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-09 03:36:48 +00:00
fix(instances): a recovered clone gets the size and start asked
When the worker stopped after it defined a clone and before it resized and started it, recovery recorded the clone at the source's size and shut off, and the job read as a plain success. Recovery now finishes a defined clone that is shut off as the run does; one that runs went through the last step, starting, and is left as it is.
This commit is contained in:
parent
02ab78fe94
commit
297f9a8a53
2 changed files with 43 additions and 4 deletions
|
|
@ -13,7 +13,7 @@ from django.template.loader import render_to_string
|
|||
from django.utils.translation import gettext_noop
|
||||
from django.utils.translation import gettext_noop as _
|
||||
from libvirt import (VIR_CONNECT_LIST_STORAGE_POOLS_ACTIVE, VIR_DOMAIN_JOB_NONE, VIR_DOMAIN_SNAPSHOT_LIST_EXTERNAL,
|
||||
VIR_ERR_NO_DOMAIN, VIR_ERR_NO_STORAGE_VOL, libvirtError)
|
||||
VIR_DOMAIN_SHUTOFF, VIR_ERR_NO_DOMAIN, VIR_ERR_NO_STORAGE_VOL, libvirtError)
|
||||
from lxml import etree
|
||||
from vrtManager import util
|
||||
from vrtManager.connection import wvmConnect
|
||||
|
|
@ -224,6 +224,14 @@ def _finish_clone(op, clone, warning):
|
|||
return {"warning": notes} if notes else {}
|
||||
|
||||
|
||||
def _recovered_clone(op):
|
||||
"""A clone defined before the worker stopped: given the size asked and
|
||||
started if asked, unless it runs, as starting is the last step."""
|
||||
if _domain(op.intent["compute"], op.intent["uuid"]).state()[0] != VIR_DOMAIN_SHUTOFF:
|
||||
return {}
|
||||
return _finish_clone(op, op.intent["clone"], [])
|
||||
|
||||
|
||||
def _clone_made(op, result):
|
||||
"""Records the clone; the job's result, with the clone's page, which the
|
||||
job panel opens: the page the job ran from is the VM cloned."""
|
||||
|
|
@ -632,7 +640,7 @@ class CloneInstance(Kind):
|
|||
disks = [v for k, v in op.intent["clone"].items() if k.startswith("disk-") and v]
|
||||
outcome = vm_outcome(op, owner=True, volumes=disks)
|
||||
if outcome.state == Operation.SUCCEEDED:
|
||||
outcome.result = {**outcome.result, "open": _clone_page(op)}
|
||||
outcome.result = {**outcome.result, **_recovered_clone(op), "open": _clone_page(op)}
|
||||
return outcome
|
||||
proxy = _recovery_proxy(op)
|
||||
if proxy is None:
|
||||
|
|
@ -654,7 +662,7 @@ class CloneInstance(Kind):
|
|||
"""The spec's recovery table: a defined clone first, then a volume
|
||||
still being created, since it comes before the copy."""
|
||||
if _domain(op.intent["compute"], op.intent["uuid"]) is not None:
|
||||
return Outcome(Operation.SUCCEEDED, result=_clone_made(op, {}))
|
||||
return Outcome(Operation.SUCCEEDED, result=_clone_made(op, _recovered_clone(op)))
|
||||
created = op.intent.get("created", [])
|
||||
paths = [c["path"] for c in created]
|
||||
ended = Operation.CANCELLED if op.cancel_requested else Operation.FAILED
|
||||
|
|
|
|||
|
|
@ -166,6 +166,18 @@ class CloneJobTestCase(TransactionTestCase):
|
|||
self.assertEqual(outcome.state, Operation.UNCERTAIN)
|
||||
self.assertIn("copy.qcow2", words(outcome.error))
|
||||
|
||||
@patch("instances.jobs.wvmConnect")
|
||||
def test_recovery_gives_a_defined_shut_off_clone_the_size_asked(self, conn):
|
||||
conn.return_value.wvm.lookupByUUIDString.return_value.state.return_value = [libvirt.VIR_DOMAIN_SHUTOFF, 0]
|
||||
op = Operation(user=self.admin, intent={"compute": self.compute.pk, "uuid": "abababab-0000-1111-2222-333333333333",
|
||||
"name": "copy", "clone": {"name": "copy", "disk-vda": "copy.qcow2",
|
||||
"vcpu": 4, "memory": None, "start": True}})
|
||||
recovered = CloneInstance().reconcile(op)
|
||||
self.assertEqual(recovered.state, Operation.SUCCEEDED)
|
||||
self.proxy.resize_cpu.assert_called_once_with("4", "4")
|
||||
self.proxy.start.assert_called_once_with()
|
||||
self.assertIn("open", recovered.result)
|
||||
|
||||
@patch("instances.jobs.wvmConnect")
|
||||
def test_a_clone_that_left_nothing_failed(self, conn):
|
||||
op = Operation(user=self.admin, intent={"compute": self.compute.pk, "uuid": "abababab-0000-1111-2222-333333333333",
|
||||
|
|
@ -513,7 +525,7 @@ class LiveCloneJobTestCase(TransactionTestCase):
|
|||
|
||||
# recovery
|
||||
|
||||
def recovered(self, cancel=False, defined=False, **intent):
|
||||
def recovered(self, cancel=False, defined=False, state=libvirt.VIR_DOMAIN_RUNNING, **intent):
|
||||
base = {"live": True, "targets": [target("vda")],
|
||||
"planned": [{"dev": "vda", "path": "/pool/copy-vda.qcow2"}], "created": self.CREATED,
|
||||
"copy_started": True}
|
||||
|
|
@ -523,6 +535,7 @@ class LiveCloneJobTestCase(TransactionTestCase):
|
|||
with patch("instances.jobs.wvmConnect") as conn:
|
||||
if not defined:
|
||||
conn.return_value.wvm.lookupByUUIDString.side_effect = libvirt_error(libvirt.VIR_ERR_NO_DOMAIN)
|
||||
conn.return_value.wvm.lookupByUUIDString.return_value.state.return_value = [state, 0]
|
||||
return CloneInstance().reconcile(op), op
|
||||
|
||||
def test_recovery_before_the_copy_deletes_what_was_created(self):
|
||||
|
|
@ -587,6 +600,24 @@ class LiveCloneJobTestCase(TransactionTestCase):
|
|||
clone = Instance.objects.get(uuid=op.intent["uuid"])
|
||||
self.assertEqual(outcome.result["open"], reverse("instances:instance", args=[clone.pk]))
|
||||
|
||||
def test_recovery_of_a_defined_clone_gives_it_the_size_asked_unless_it_runs(self):
|
||||
# the worker stopped after it defined the clone, before or while it resized and started it
|
||||
self.proxy.backup_result.return_value = None
|
||||
self.proxy.backup_targets.return_value = {"/other/x.qcow2"}
|
||||
clone = {"name": "copy", "disk-vda": "copy-vda.qcow2", "vcpu": 2, "memory": 1024, "start": True}
|
||||
outcome, op = self.recovered(defined=True, state=libvirt.VIR_DOMAIN_SHUTOFF, clone=clone)
|
||||
self.assertEqual(outcome.state, Operation.SUCCEEDED)
|
||||
self.proxy.resize_cpu.assert_called_once_with("2", "2")
|
||||
self.proxy.resize_mem.assert_called_once_with(1024, 1024)
|
||||
self.proxy.start.assert_called_once_with()
|
||||
Instance.objects.filter(uuid=op.intent["uuid"]).delete()
|
||||
op.delete()
|
||||
self.proxy.reset_mock()
|
||||
outcome, _op = self.recovered(defined=True, clone=clone) # it runs: the last step was done
|
||||
self.assertEqual(outcome.state, Operation.SUCCEEDED)
|
||||
self.proxy.resize_cpu.assert_not_called()
|
||||
self.proxy.start.assert_not_called()
|
||||
|
||||
def test_recovery_of_a_failed_copy_deletes_what_was_created(self):
|
||||
outcome, _op = self.recovered(copy_result=[False, "No space left on device"])
|
||||
self.assertEqual(outcome.state, Operation.FAILED)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue