Development
This page collects developer-facing reference material for working on RMS itself. For building and testing, see Getting Started; for adding hardware, see Adding Support for New Hardware.
Project structure
See Workspace Crates for the supporting crates and Architecture: Internal View for how the layers fit together.
Key dependencies
Adding a persistence domain
The crates/rackmanagementservice/src/persistence/ module is per-domain by design: each new domain (e.g.
inventory) gets its own trait file, two implementation files, and a migration.
The shared infrastructure (pool builder, error type, migrations runner, CI
Postgres service, docker-compose) is not touched.
To add a domain inventory:
- Domain types + trait - create
crates/rackmanagementservice/src/persistence/inventory.rswith the entity types and aSend + Synctrait whose methods take and return only domain types. - Migration - add
crates/rackmanagementservice/src/persistence/postgres/migrations/000N_inventory.sqlwith the typed schema. Usesqlx migrate add --source crates/rackmanagementservice/src/persistence/postgres/migrations <name>to generate the timestamped file. - Memory impl - create
crates/rackmanagementservice/src/persistence/memory/inventory.rswith a struct holdingRwLock-protected collections, mirroring the Postgres semantics so the same behavioral tests pass against either backend. - Postgres impl - create
crates/rackmanagementservice/src/persistence/postgres/inventory.rs. ReuseDatabaseErrorfromsuper::errorand the sharedPgPool. Usesqlx::query_as(no!macro - CI builds don’t need a live DB). - Wire into
Backends- add apub inventory: Arc<dyn InventoryStore>field to theBackendsstruct incrates/rackmanagementservice/src/persistence/mod.rs. - Tests - add scenarios to
crates/rackmanagementservice/tests/persistence.rsasasync fn<S: InventoryStore>(store: &S)and wrap each with thememory_test!andpostgres_test!macros.
sqlx-clidefaults to looking for a top-levelmigrations/directory. RMS’s live undercrates/rackmanagementservice/src/persistence/postgres/, so always pass--source.
Adding hardware support
Adding a new node type or rack generation touches API identity, endpoint policy, rack routing, node construction, firmware policy, and tests. This has its own detailed guide: Adding Support for New Hardware.
Building standalone nvfwupd
The nvfwupd CLI can be built as a standalone, portable binary. A plain build
inherits the host sysroot glibc baseline; the docker/nvfwupd-standalone/
Dockerfiles produce binaries with a glibc 2.17 baseline for broad portability.
The release container image also bundles nvfwupd; extract it with
docker cp from a created container if you need the binary that shipped in a
specific image.
Building multi-arch release images
The release image can be built for arm64 from an x86 host with Buildx plus QEMU/binfmt on the Docker daemon:
CI/CD
GitLab CI runs on every push and merge request, using the multi-stage Dockerfile to build, test, and package the service.
Merges to main are gated on fmt-check, clippy-lint, doc-check, deny-check,
and run-tests all passing. Coverage jobs run on main and are opt-in for MRs
via ci-coverage in the commit message. Release consumers should pull a versioned image
rather than latest.
The job checks out pinned StratumSim and rms-sim-orchestrator sources and builds
their runtime images. It then calls rms_ci/run.py from rms-sim-orchestrator.
That driver owns network allocation, combined fixture generation, rack target
rendering, Compose execution, failure logs, and cleanup. RMS CI retains source
authentication, revision pins, and the RMS image build without duplicating
orchestrator topology or lifecycle scripts. The NICo job follows the same
boundary: GitLab checks out authenticated sources and prepares images, then
calls rms_ci/nico.py. The orchestrator driver owns NICo runtime volumes,
network adaptation, NICo ATP, diagnostics, and cleanup.