Skip to content

Backend

Async FastAPI app under backend/app/. Driven by Pydantic schemas, talks to Postgres via SQLAlchemy async, and persists lab topology to JSON files on disk.

Module layout

backend/app/
├── main.py             # FastAPI application + router wiring
├── config.py           # Settings (Pydantic BaseSettings)
├── database.py         # Async engine + session factory
├── dependencies.py     # FastAPI dependency injectors (auth, lab lookup, etc.)
├── core/               # Cross-cutting concerns (logging, error handlers)
├── models/             # SQLAlchemy models — users, sessions, audit
├── schemas/            # Pydantic request/response models
├── routers/            # HTTP + WebSocket route handlers
└── services/           # Business logic — pure Python, no FastAPI coupling

Routers

Each module under routers/ mounts an APIRouter at a path prefix:

Router Prefix Surface
auth.py /api/auth Session login / logout / current-user.
users.py /api/users User CRUD (admin-only).
folders.py /api/folders Lab folder hierarchy.
labs.py /api/labs Lab CRUD, node start/stop, runtime control.
links.py /api/labs/{lab}/links Link create / delete / list.
networks.py /api/labs/{lab}/networks Network create / patch / delete.
listing.py /api/list Vendor template + image listings.
system.py /api/system Health, version, environment info.
ws.py /ws WebSocket fan-out for runtime state.

Services

Each service owns one concern. Routers stay thin and delegate everywhere:

Service What it owns
auth_service Password hashing, session token issuance, role checks.
lab_service Lab JSON read/write, validation, schema upgrades.
lab_lock Cooperative file lock for lab JSON writes.
link_service Link create / delete with cascading runtime detach.
link_utils Pure helpers for link endpoint shape.
network_service Bridge create / delete / rename, display-name uniqueness.
node_runtime_service Start / stop nodes, attach / detach interfaces at runtime.
runtime_mutex Per-(lab, node, iface) async locks gating runtime mutations.
runtime_pids PID tracking and exit-watcher per running node.
host_net Shell-out to the privileged helper for bridge / veth / TAP ops.
mac_registry Live-MAC reader + planned-MAC comparator.
template_service Template discovery and instantiation.
folder_service Lab folder hierarchy.
guacamole_db_service Wire console connections into the Guacamole DB.
html5_service HTML5 console URL builder.
ws_hub WebSocket fan-out — subscribe-by-lab, broadcast runtime events.

EVE-NG importer

The EVE-NG importer is not part of the running backend. It lives at backend/scripts/import_eveng/ and runs as an offline CLI:

backend/scripts/import_eveng/
├── cli.py              # argparse entry; resolves modes, walks, plans, copies
├── walker.py           # KIND_QEMU / KIND_DYNAMIPS / KIND_IOL / KIND_DOCKER discovery
├── copy_engine.py      # Idempotent copy + sha256 verify
├── idempotency.py      # Per-file skip decisions
├── migrate.py          # Top-level run orchestrator
├── manifest.py         # Manifest schema + writer
├── permissions.py      # chown / chmod after copy
├── _app_owner.py       # Owner resolution (NOVA_VE_OWNER → SUDO_USER → ubuntu)
├── _hash.py            # sha256 helper
├── logging_setup.py    # JSON-line logger config
├── template_schema.py  # Result schema for adapter convert()
├── parsers/            # PHP / YAML parsers for EVE-NG source templates
└── adapters/           # One module per vendor (arista_veos, cisco_iol, …)

See CLI — import-eveng-templates for the user-facing flags and Vendor coverage for the adapter list.

Testing

# Full suite
cd backend && .venv/bin/pytest

# A subset
.venv/bin/pytest tests/test_networks_api.py -k "test_create_network_duplicate"

# Importer-only
.venv/bin/pytest tests/import_eveng/

Integration tests use a real Postgres via the host package; unit tests for services run without it.