import asyncio import uuid import time from typing import Dict, Optional from app.config import ( CONTAINER_HOST, CONTAINER_RUNTIME, UERANSIM_CONTAINER_NAME, UERANSIM_ENV_FILE, UERANSIM_IMAGE, UERANSIM_NETWORK_MODE, UERANSIM_PRIVILEGED, ) _tasks: Dict[str, dict] = {} def create_task() -> str: task_id = str(uuid.uuid4()) _tasks[task_id] = { "id": task_id, "status": "pending", "logs": [], "created": time.time(), } return task_id def get_task(task_id: str) -> Optional[dict]: return _tasks.get(task_id) async def run_test(task_id: str) -> None: task = _tasks[task_id] task["status"] = "running" def log(msg: str, type: str = "info") -> None: task["logs"].append({"msg": msg, "type": type, "ts": time.strftime("%H:%M:%S")}) runtime_cmd = [CONTAINER_RUNTIME] if CONTAINER_HOST: runtime_cmd.extend(["--host", CONTAINER_HOST]) log(f"▸ Checking UERANSIM image via {CONTAINER_RUNTIME}…", "run") check = await asyncio.create_subprocess_exec( *runtime_cmd, "images", "-q", UERANSIM_IMAGE, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) out, _ = await check.communicate() if not out.strip(): log("✗ UERANSIM image not found.", "err") log(" Build it with `docker compose build ueransim` or set MARVIS_UERANSIM_IMAGE.", "err") task["status"] = "error" return log(" UERANSIM image ready", "ok") log("▸ Starting test container — allow up to 60s…", "run") try: run_cmd = [*runtime_cmd, "run", "--rm", "--name", UERANSIM_CONTAINER_NAME] if UERANSIM_NETWORK_MODE: run_cmd.append(f"--network={UERANSIM_NETWORK_MODE}") if UERANSIM_PRIVILEGED: run_cmd.append("--privileged") run_cmd.extend([ "--env-file", UERANSIM_ENV_FILE, UERANSIM_IMAGE, ]) proc = await asyncio.create_subprocess_exec( *run_cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT, ) try: stdout, _ = await asyncio.wait_for(proc.communicate(), timeout=90) except asyncio.TimeoutError: proc.kill() log("✗ Test timed out after 90s — container killed", "err") task["status"] = "error" return for line in stdout.decode(errors="replace").splitlines(): line = line.strip() if not line: continue if "ERROR" in line: log(line, "err") elif "PASSED" in line or "established" in line or "successful" in line: log(line, "ok") elif "WARNING" in line: log(line, "warn") else: log(line, "info") if proc.returncode == 0: log("✓ Emulated data session completed successfully", "ok") task["status"] = "done" elif proc.returncode == 2: log(f"⚠ Credentials not configured — edit {UERANSIM_ENV_FILE}", "warn") task["status"] = "error" else: log(f"✗ Test exited with code {proc.returncode}", "err") task["status"] = "error" except Exception as exc: log(f"✗ Unexpected error: {exc}", "err") task["status"] = "error"