firegex/tests/unit/test_service_lifecycle_lock.py
2026-09-23 11:21:51 +02:00

76 lines
2.2 KiB
Python

"""Two starts, or two stops, arriving at one service together.
A double click does it, and so does an operator's start racing the watchdog's restart.
`active` used to be asked before the lock was taken, so both calls saw a stopped service
and the second built a datapath of its own over the first — whose engine was left running
with nobody holding it. Two stops, likewise, both saw it running.
"""
import asyncio
import pytest
from modules.services import firewall
from modules.services.models import TRANSPORT, Service
class _Transport:
def __init__(self, seen: dict):
self.seen = seen
async def start(self, chain):
self.seen["start"] += 1
await asyncio.sleep(0.05)
return {}
async def stop(self):
self.seen["stop"] += 1
await asyncio.sleep(0.05)
class _Quiet:
"""Stands in for the rules, the log and the database: none of them is the question."""
def add(self, *args, **kwargs):
pass
def delete(self, *args, **kwargs):
pass
def query(self, *args, **kwargs):
return []
@pytest.fixture
def manager(monkeypatch):
seen = {"start": 0, "stop": 0}
monkeypatch.setattr(firewall, "nft", _Quiet())
monkeypatch.setattr(firewall, "log_for", lambda service_id: _Quiet())
monkeypatch.setattr(firewall.transports, "build", lambda srv, **kw: _Transport(seen))
srv = Service(service_id="s", name="n", status="stop", proto="tcp",
transport=TRANSPORT.PROXY)
return firewall.ServiceManager(srv, _Quiet()), seen
def test_two_starts_at_once_build_one_datapath(manager):
service, seen = manager
async def run():
await asyncio.gather(service.enable(), service.enable())
asyncio.run(run())
assert seen["start"] == 1, "the second start built a datapath over the first"
assert service.active
def test_two_stops_at_once_take_it_down_once(manager):
service, seen = manager
async def run():
await service.enable()
await asyncio.gather(service.disable(), service.disable())
asyncio.run(run())
assert seen["stop"] == 1, "the second stop tore down a datapath that was already gone"
assert not service.active