mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 11:16:47 +00:00
fix(computes): instance sync asks libvirt outside the DB transaction
refresh_instance_database opened the transaction first and then asked libvirt for the domain list. A host that goes silent on an established connection keeps that call waiting until keepalive gives up (30 s). On SQLite, the default database, the open transaction holds a read lock, so every other write of the app failed meanwhile with "database is locked", logins (session writes) included. Reproduced in the test setup: with a relay to a TCP compute frozen after connecting, three session writes during the 30 s refresh each failed after 5 s. - The connection check and listAllDomains run before the transaction, still under the compute lock, so create, delete and migration of the compute stay excluded as before. - The transaction only applies the changes. It checks the compute row first: a compute deleted while libvirt was asked gets no instances (this ended in a foreign key error before). A failed listing still leaves the database untouched. With the change, the same reproduction writes the sessions at once and keeps the instances. Both new tests fail on the previous code.
This commit is contained in:
parent
7e08224330
commit
fbd780af02
2 changed files with 71 additions and 23 deletions
|
|
@ -1,6 +1,7 @@
|
|||
from unittest.mock import MagicMock, patch
|
||||
from unittest.mock import MagicMock, PropertyMock, patch
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test import TestCase
|
||||
from django.db import connection
|
||||
from django.test import TestCase, TransactionTestCase
|
||||
|
||||
from accounts.models import UserInstance
|
||||
from computes.models import Compute
|
||||
|
|
@ -138,3 +139,45 @@ class InstanceReconciliationTestCase(TestCase):
|
|||
with patch("computes.utils.refresh_instance_database") as mock_refresh:
|
||||
refr(self.compute)
|
||||
mock_refresh.assert_called_once_with(self.compute)
|
||||
|
||||
|
||||
class ReconciliationTransactionTestCase(TransactionTestCase):
|
||||
"""libvirt is asked outside the DB transaction: on SQLite an open transaction
|
||||
makes every other write of the app fail while a silent host is waited on."""
|
||||
|
||||
def setUp(self):
|
||||
self.compute = Compute.objects.create(name="tx-compute", hostname="127.0.0.1", login="root", password="", type=1)
|
||||
|
||||
def proxy(self, list_domains):
|
||||
mock_proxy = MagicMock()
|
||||
mock_proxy.wvm.listAllDomains.side_effect = list_domains
|
||||
self.compute.__dict__["proxy"] = mock_proxy
|
||||
|
||||
def test_libvirt_is_called_outside_the_transaction(self):
|
||||
seen = {}
|
||||
|
||||
def status():
|
||||
seen["status"] = connection.in_atomic_block
|
||||
return True
|
||||
|
||||
def list_domains():
|
||||
seen["list"] = connection.in_atomic_block
|
||||
return [MockDomain(name="vm-tx", uuid_str="77777777-7777-7777-7777-777777777777")]
|
||||
|
||||
self.proxy(list_domains)
|
||||
with patch.object(Compute, "status", new_callable=PropertyMock, side_effect=status):
|
||||
refresh_instance_database(self.compute)
|
||||
|
||||
self.assertEqual(seen, {"status": False, "list": False})
|
||||
self.assertTrue(Instance.objects.filter(compute=self.compute, name="vm-tx").exists())
|
||||
|
||||
def test_compute_deleted_while_listing_gets_no_instances(self):
|
||||
def list_domains():
|
||||
Compute.objects.filter(pk=self.compute.pk).delete()
|
||||
return [MockDomain(name="vm-orphan", uuid_str="88888888-8888-8888-8888-888888888888")]
|
||||
|
||||
self.proxy(list_domains)
|
||||
self.compute.__dict__["status"] = True
|
||||
refresh_instance_database(self.compute)
|
||||
|
||||
self.assertFalse(Instance.objects.filter(uuid="88888888-8888-8888-8888-888888888888").exists())
|
||||
|
|
|
|||
|
|
@ -220,37 +220,42 @@ def refresh_instance_database(compute):
|
|||
|
||||
try:
|
||||
with libvirt_compute_lock(compute, timeout=15.0):
|
||||
with transaction.atomic():
|
||||
comp_locked = Compute.objects.select_for_update().filter(pk=compute_pk).first()
|
||||
if not comp_locked:
|
||||
return
|
||||
# libvirt is asked before the transaction: a silent host can keep a
|
||||
# call waiting for 30 s, and on SQLite an open transaction makes every
|
||||
# other write of the app fail meanwhile. The compute lock still keeps
|
||||
# create/delete/migration of this compute out until the end.
|
||||
if hasattr(compute, "status") and compute.status is False:
|
||||
return
|
||||
|
||||
if hasattr(compute, "status") and compute.status is False:
|
||||
return
|
||||
try:
|
||||
domains = compute.proxy.wvm.listAllDomains()
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
"Failed to retrieve domains from compute %s (%s): %s",
|
||||
getattr(compute, "name", compute),
|
||||
getattr(compute, "hostname", ""),
|
||||
e,
|
||||
)
|
||||
return
|
||||
|
||||
# Map libvirt UUIDs to current domain names
|
||||
host_domains = {}
|
||||
for d in domains:
|
||||
try:
|
||||
domains = compute.proxy.wvm.listAllDomains()
|
||||
host_domains[d.UUIDString()] = d.name()
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
"Failed to retrieve domains from compute %s (%s): %s",
|
||||
"Error reading domain metadata from compute %s: %s",
|
||||
getattr(compute, "name", compute),
|
||||
getattr(compute, "hostname", ""),
|
||||
e,
|
||||
)
|
||||
return
|
||||
|
||||
# Map libvirt UUIDs to current domain names
|
||||
host_domains = {}
|
||||
for d in domains:
|
||||
try:
|
||||
host_domains[d.UUIDString()] = d.name()
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
"Error reading domain metadata from compute %s: %s",
|
||||
getattr(compute, "name", compute),
|
||||
e,
|
||||
)
|
||||
return
|
||||
with transaction.atomic():
|
||||
# the compute may have been deleted while libvirt was asked
|
||||
comp_locked = Compute.objects.select_for_update().filter(pk=compute_pk).first()
|
||||
if not comp_locked:
|
||||
return
|
||||
|
||||
db_instances = list(Instance.objects.filter(compute=compute).select_for_update())
|
||||
db_uuids = set(inst.uuid for inst in db_instances)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue