mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-09 03:36:48 +00:00
docs: snapshot create and revert run as jobs
Live tests that expected the snapshot or clone result at once now call the backend or run the queued job.
This commit is contained in:
parent
8d2e88f4bb
commit
149c14f1bb
7 changed files with 44 additions and 22 deletions
|
|
@ -602,6 +602,7 @@ When the restored version predates the worker, `supervisorctl` reports that `web
|
|||
- Docker no longer publishes port 6080; the console goes through nginx on the panel's port (`WS_PUBLIC_PORT`, default 80).
|
||||
- Long operations run as jobs in a worker process (`manage.py run_worker`); follow them on the VM page and on the Jobs page.
|
||||
- Cloning a VM or a volume and creating a VM run as jobs: the page returns at once and the job panel follows them. The API's VM create answers 202 with the job and needs a new `submission_key` per request. The create form no longer takes a console password; set it on the VM page.
|
||||
- Taking and reverting a snapshot run as jobs, like deleting an external snapshot: the page returns at once and the job panel follows them. Deleting an internal snapshot stays immediate.
|
||||
- Manual installs that keep their own `settings.py` copy the SQLite `OPTIONS` and `TEST` entries from `settings.py.template` and add `"operations"` to `INSTALLED_APPS`.
|
||||
- The SQLite database must be on a local file system (WAL).
|
||||
- Outside compose, stop the Docker container with `docker stop -t 95` so the worker can finish running jobs.
|
||||
|
|
|
|||
|
|
@ -8,10 +8,9 @@ from django.contrib.auth import get_user_model
|
|||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from instances.api.serializers import CreateInstanceSerializer
|
||||
from instances.jobs import CreateInstance
|
||||
from instances.models import Instance
|
||||
from instances.utils import nic_macs
|
||||
from operations.jobs import Context
|
||||
from operations.jobs import Context, Outcome, finish, get_kind
|
||||
from operations.models import Operation, acquire
|
||||
|
||||
|
||||
|
|
@ -19,11 +18,12 @@ wvm_create = MagicMock() # the page and the job reach the same host
|
|||
|
||||
|
||||
def run(op):
|
||||
"""The queued job, run as the worker runs it."""
|
||||
"""The queued job, run and finished as the worker does."""
|
||||
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()
|
||||
CreateInstance().run(op, Context(op, SimpleNamespace(identity="wvc-test:1:a")))
|
||||
result = get_kind(op.kind).run(op, Context(op, SimpleNamespace(identity="wvc-test:1:a")))
|
||||
finish(op, Outcome(Operation.SUCCEEDED, result=result))
|
||||
|
||||
|
||||
@patch("instances.jobs.wvmCreate", new=wvm_create)
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ class LiveDataLossTestCase(TestCase):
|
|||
def test_revert_external_snapshot_keeps_disk_added_later(self):
|
||||
base = livetest.create_volume(self.conn, P + "r03-a")
|
||||
dom, inst = self.vm("r03-a", [base])
|
||||
self.post("create_external_snapshot", inst, {"name": "snap"})
|
||||
inst.proxy.create_external_snapshot("snap")
|
||||
|
||||
later = livetest.create_volume(self.conn, P + "r03-later")
|
||||
dom.attachDeviceFlags(
|
||||
|
|
@ -103,7 +103,7 @@ class LiveDataLossTestCase(TestCase):
|
|||
f"<source file='{later}'/><target dev='vdb' bus='virtio'/></disk>",
|
||||
libvirt.VIR_DOMAIN_AFFECT_CONFIG,
|
||||
)
|
||||
self.post("revert_external_snapshot", inst, {"name": "snap"})
|
||||
inst.proxy.revert_external_snapshot("snap")
|
||||
|
||||
self.assertTrue(livetest.volume_exists(self.conn, later), "revert deleted a disk the snapshot never had")
|
||||
self.assertEqual(set(self.disk_sources(dom, libvirt.VIR_DOMAIN_XML_INACTIVE)), {"vda"})
|
||||
|
|
@ -111,11 +111,11 @@ class LiveDataLossTestCase(TestCase):
|
|||
def test_delete_older_external_snapshot_keeps_newer_snapshot_files(self):
|
||||
base = livetest.create_volume(self.conn, P + "r03-b")
|
||||
dom, inst = self.vm("r03-b", [base])
|
||||
self.post("create_external_snapshot", inst, {"name": "older"})
|
||||
self.post("create_external_snapshot", inst, {"name": "newer"})
|
||||
inst.proxy.create_external_snapshot("older")
|
||||
inst.proxy.create_external_snapshot("newer")
|
||||
sources = set(self.disk_sources(dom).values())
|
||||
|
||||
self.post("delete_external_snapshot", inst, {"name": "older"})
|
||||
inst.proxy.delete_external_snapshot("older")
|
||||
|
||||
for path in sources:
|
||||
self.assertTrue(livetest.volume_exists(self.conn, path), f"{path} was deleted")
|
||||
|
|
@ -130,13 +130,12 @@ class LiveDataLossTestCase(TestCase):
|
|||
dom.create()
|
||||
os_before = self.os_xml(dom)
|
||||
|
||||
response = self.post("snapshot", inst, {"name": "snap"})
|
||||
|
||||
# Taken where libvirt supports it, otherwise refused with a message;
|
||||
# never a server error and never a rewritten loader or NVRAM.
|
||||
self.assertEqual(response.status_code, 302)
|
||||
if dom.snapshotNum(0) == 0:
|
||||
self.assertTrue(list(get_messages(response.wsgi_request)))
|
||||
# Taken where libvirt supports it, otherwise refused; never a
|
||||
# rewritten loader or NVRAM.
|
||||
try:
|
||||
inst.proxy.create_snapshot("snap")
|
||||
except libvirt.libvirtError:
|
||||
self.assertEqual(dom.snapshotNum(0), 0)
|
||||
self.assertEqual(self.os_xml(dom), os_before)
|
||||
|
||||
def os_xml(self, dom):
|
||||
|
|
|
|||
|
|
@ -23,7 +23,10 @@ from django.urls import reverse
|
|||
from lxml import etree
|
||||
from vrtManager.connection import connection_manager
|
||||
|
||||
from operations.models import Operation
|
||||
|
||||
from . import livetest
|
||||
from .test_create_instance import run
|
||||
from instances.models import Instance
|
||||
from instances.utils import refr
|
||||
|
||||
|
|
@ -339,7 +342,9 @@ class LiveGuestTestCase(TestCase):
|
|||
source, inst = self.define("gsrc")
|
||||
mac = "52:54:00:aa:06:01"
|
||||
|
||||
self.post("clone", inst, {"name": P + "gclone", "disk-vda": P + "gclone.qcow2", "clone-net-mac-0": mac})
|
||||
self.post("clone", inst, {"name": P + "gclone", "disk-vda": P + "gclone.qcow2", "clone-net-mac-0": mac,
|
||||
"submission_key": "key-gclone"})
|
||||
run(Operation.objects.get(kind="instance.clone"))
|
||||
clone = self.conn.lookupByName(P + "gclone")
|
||||
self.start(clone)
|
||||
|
||||
|
|
@ -357,12 +362,12 @@ class LiveGuestTestCase(TestCase):
|
|||
dom, inst = self.boot("gsnap")
|
||||
mac = "52:54:00:aa:07:01"
|
||||
|
||||
self.post("snapshot", inst, {"name": "before"})
|
||||
inst.proxy.create_snapshot("before")
|
||||
self.assertEqual(dom.snapshotNum(0), 1, "snapshot was not taken")
|
||||
self.post("add_network", inst, {"add-net-mac": mac, "add-net-network": "net:default", "add-net-model": "virtio"})
|
||||
self.assertTrue(wait_for(lambda: mac in self.guest_view(dom)["macs"], 60), "guest does not see the new NIC")
|
||||
|
||||
self.post("revert_snapshot", inst, {"name": "before"})
|
||||
inst.proxy.snapshot_revert("before")
|
||||
self.wait_agent(dom)
|
||||
|
||||
self.assertTrue(dom.isActive(), "reverting a running snapshot left the VM stopped")
|
||||
|
|
|
|||
|
|
@ -104,6 +104,21 @@ class LiveJobsTestCase(TransactionTestCase):
|
|||
self.assertEqual(op.state, Operation.SUCCEEDED, op.error)
|
||||
return op
|
||||
|
||||
def test_snapshot_create_and_revert_run_as_jobs(self):
|
||||
running, _vm = self.vm("smoke-ext", "running")
|
||||
stopped, _vm = self.vm("smoke-int")
|
||||
steps = ((running, "snapshot.create", {"name": "m", "external": True, "memory": True}),
|
||||
(running, "snapshot.create", {"name": "d", "external": True}),
|
||||
(running, "snapshot.revert", {"name": "m", "external": True}),
|
||||
(stopped, "snapshot.create", {"name": "i"}),
|
||||
(stopped, "snapshot.revert", {"name": "i"}))
|
||||
for n, (dom, kind, params) in enumerate(steps):
|
||||
instance = Instance.objects.get_or_create(compute=self.compute, name=dom.name(), uuid=dom.UUIDString())[0]
|
||||
self.run_job(jobs.submit(kind, self.admin, {"instance": instance.pk, **params}, f"key-snap{n:04d}"))
|
||||
self.assertEqual(running.snapshotCurrent(0).getName(), "m")
|
||||
self.assertTrue(running.isActive()) # a memory snapshot resumes the VM
|
||||
self.assertEqual(stopped.snapshotCurrent(0).getName(), "i")
|
||||
|
||||
def test_clone_create_and_volume_clone_run_as_jobs(self):
|
||||
dom, _vm = self.vm("smoke-src")
|
||||
source = Instance.objects.create(compute=self.compute, name=dom.name(), uuid=dom.UUIDString())
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"""The snapshot views call the backend once (an external snapshot delete
|
||||
queues a job instead) and show its refusals as messages; they no longer
|
||||
start, pause or stop the VM themselves."""
|
||||
"""Deleting an internal snapshot calls the backend once and shows its
|
||||
refusals as messages; it no longer starts, pauses or stops the VM itself. The
|
||||
other snapshot actions queue jobs (test_snapshot_jobs)."""
|
||||
|
||||
from datetime import datetime
|
||||
from unittest.mock import PropertyMock, patch
|
||||
|
|
|
|||
|
|
@ -662,10 +662,12 @@ class InstancesTestCase(TestCase):
|
|||
"clone-title": "",
|
||||
"clone-description": "",
|
||||
"clone": "",
|
||||
"submission_key": "key-clone",
|
||||
},
|
||||
HTTP_REFERER=reverse("index"),
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
run(Operation.objects.get(kind="instance.clone"))
|
||||
self.assertEqual(Instance.objects.count(), instance_count + 1)
|
||||
|
||||
clone_qs = Instance.objects.filter(name="wvc-test-vm-clone")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue