Initial commit: VMware vSphere API simulator scaffold.
Add the FastAPI app, PostgreSQL migrations, Docker/Helm packaging, API contracts, docs, client examples, and the unit/integration/compatibility test suite for local client and tooling labs without a real vCenter.
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
"""Durable task concurrency and recovery tests."""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import uuid
|
||||
|
||||
import asyncpg # type: ignore[import-untyped]
|
||||
import pytest
|
||||
from asyncpg import Pool
|
||||
|
||||
from app.db.migrations import migrate
|
||||
from app.tasks.repository import TaskRepository
|
||||
|
||||
pytestmark = [
|
||||
pytest.mark.integration,
|
||||
pytest.mark.skipif(not os.getenv("TEST_DATABASE_URL"), reason="TEST_DATABASE_URL is required"),
|
||||
]
|
||||
|
||||
|
||||
async def repository() -> tuple[Pool, TaskRepository]:
|
||||
pool = await asyncpg.create_pool(os.environ["TEST_DATABASE_URL"], min_size=1, max_size=4)
|
||||
async with pool.acquire() as connection:
|
||||
await migrate(connection)
|
||||
return pool, TaskRepository(pool)
|
||||
|
||||
|
||||
async def test_two_worker_exclusion_idempotency_and_logs() -> None:
|
||||
pool, tasks = await repository()
|
||||
key = uuid.uuid4().hex
|
||||
try:
|
||||
created = await tasks.create(
|
||||
upid=f"UPID:pve1:00000001:00000001:00000001:test:{key}:root@pam:",
|
||||
task_type="test",
|
||||
payload={"value": 1},
|
||||
resource_key=f"vm:{key}",
|
||||
idempotency_key=key,
|
||||
)
|
||||
repeated = await tasks.create(
|
||||
upid=f"ignored-{key}", task_type="test", payload={}, idempotency_key=key
|
||||
)
|
||||
assert repeated.id == created.id
|
||||
|
||||
first, second = await asyncio.gather(
|
||||
tasks.claim("worker-a", 30), tasks.claim("worker-b", 30)
|
||||
)
|
||||
claimed = first or second
|
||||
assert claimed is not None
|
||||
assert (first is None) != (second is None)
|
||||
worker = "worker-a" if first is not None else "worker-b"
|
||||
await tasks.append_log(claimed.id, "started")
|
||||
await tasks.progress(claimed.id, worker, 50)
|
||||
await tasks.finish(claimed.id, worker, status="success", result={"ok": True})
|
||||
assert await tasks.logs(claimed.id) == ("started",)
|
||||
finished = await tasks.get(claimed.id)
|
||||
assert finished is not None
|
||||
assert finished.status == "success"
|
||||
finally:
|
||||
await pool.close()
|
||||
|
||||
|
||||
async def test_expired_lease_is_reclaimed_after_restart() -> None:
|
||||
pool, tasks = await repository()
|
||||
key = uuid.uuid4().hex
|
||||
try:
|
||||
created = await tasks.create(
|
||||
upid=f"UPID:pve1:00000001:00000001:00000001:test:{key}:root@pam:",
|
||||
task_type="test",
|
||||
payload={},
|
||||
)
|
||||
assert await tasks.claim("dead-worker", 0) is not None
|
||||
recovered = await tasks.claim("new-worker", 30)
|
||||
assert recovered is not None
|
||||
assert recovered.id == created.id
|
||||
assert recovered.attempt == 2
|
||||
await tasks.request_cancel(recovered.id)
|
||||
cancelled = await tasks.get(recovered.id)
|
||||
assert cancelled is not None
|
||||
assert cancelled.cancel_requested
|
||||
await tasks.finish(recovered.id, "new-worker", status="cancelled")
|
||||
finally:
|
||||
await pool.close()
|
||||
Reference in New Issue
Block a user