Installed host RDMA qualification#
This gate consumes a packaged orbit-te crate as a normal Rust dependency.
It runs the public TransferEngine API with the rdma feature only. Private
libtests and CUDA are absent from the installed binary. Native fault and rail
accounting profiles add a separate process-local qualification library.
The published alpha on crates.io remains a name reservation; this workflow
packages the selected source without publishing it.
Build the artifact#
Use the prerequisites in the build guide, including the native compiler/libclang environment. From a clean committed checkout, select an artifact directory outside the repository:
python3 tests/hardware/build_installed.py \
--toolchain stable --output /var/tmp/orbitte-installed/stable
python3 tests/hardware/build_installed.py \
--toolchain 1.85.0 --output /var/tmp/orbitte-installed/msrv
The script creates a .crate, extracts it into the artifact directory and
copies its packaged rdma_host fixture and resource/restart modules into an
independent consumer package.
Cargo fetches the packaged lockfile dependencies first, then resolves OrbitTE
from that extracted package offline, preserves locked dependency versions, checks formatting/Clippy and installs a release executable
under installed/bin/orbitte-rdma-host. The manifest pins the source commit,
archive, every fixture module, consumer lockfile, binary, features and local
native libraries. orbitte-installed-host-v4 identifies this test protocol;
the current production wire is v19; historical v14 artifacts retain their own
recorded qualification. The host profile remains MW-based.
The consumer is an acceptance fixture, not a new production CLI/API.
CI performs this build on stable and Rust 1.85.0 and retains the artifacts.
Running --protocol proves the binary starts; it is not hardware qualification.
--allow-dirty is for local build diagnosis only. The hardware controller
rejects a dirty or mismatched installation manifest.
Run the two-host profile#
Use the existing identity-pinned SSH profile and supervisor described in
the native RDMA guide. Select runner: "orbitte-installed-host",
two explicit rails, a gid_index, distinct source/requester nodes, and one
absolute resource lock per RNIC on each node. No GPU is requested. Each case
must name hosts with different inventoried hostnames and kernel boot IDs;
two executor aliases on one machine cannot qualify the two-host profile.
The controller rejects that placement before remote access, then rechecks each
host/boot identity during deployment and inside the resource-lock supervisor.
Each case has these explicit fields:
{
"source": "a", "requester": "b",
"source_kind": "cpu", "requester_kind": "cpu", "transport": "rdma",
"mode": "installed-host", "operation": "read", "rdma_events": false,
"parents": 16, "payload": 6291457, "block": 1048576,
"num_lanes": 3, "mask": 3
}
python3 tests/hardware/run.py --profile /var/tmp/host-profile.json \
--installation /var/tmp/orbitte-installed/stable/manifest.json \
--binary /var/tmp/orbitte-installed/stable/installed/bin/orbitte-rdma-host \
--output /var/tmp/orbitte-installed/native-stable
The fixture bounds each peer to 256 MiB of application backing, 32 parents, 32 MiB per parent and 1–3 QPs per peer/context/operation. Each of three rounds starts concurrent public parent requests, varies every parent’s input, checks the complete allocation and guards, and waits for source-handler/grant drain. Both contexts publish persistent MRs even with a one-rail dispatch mask. Native completion-byte counters, bounded QP creation and increasing reuse must agree with the workload; persistent MRs must not be recreated. Both peers close, unregister and release all counted owners before reporting success.
TCP carries only rendezvous and test barriers. Payload requests explicitly
select Transport::Rdma; native byte counters and mapped providers must prove
that path. The controller independently checks the complete round/result
ledger, provider mappings, native-child exit and released resource locks.
Missing, duplicate, mismatched or partially drained results fail the run.
Physical rail accounting#
Select mode: "installed-rail-accounting" with the existing healthy consumer.
The frozen case uses 16 parents, payload: 4194305, block: 1048576,
num_lanes: 1 and mask: 3. Run READ/polling and WRITE/events, reversing
physical source/requester hosts for the second compiler artifact. Each request
has three 1 MiB slices and one folded 1 MiB + 1 byte tail. Every round therefore
has exactly 64 payload WRs and 67,108,880 payload bytes.
The hash-pinned observer runs only in the requester. accounting.c forwards
native calls and CQEs unchanged; it never withholds completions or injects a
fault. It joins each accepted payload WR to its actual successful CQE using
context, CQ, QPN and WR ID. Native context device names must map to the two
published, distinct PCI RNICs. The external verifier checks every parent
address/length, both rails’ payload, overlap between rails, and at least two
simultaneously outstanding parents sharing each rail’s QP. Payload SQ debt
must remain at most eight WRs per QP. Completed native bytes must match the
public engine counters, followed by complete owner drain and native teardown.
The observer serializes receipt bookkeeping around native submissions. It measures accepted work awaiting completion observation, not instantaneous hardware occupancy, fabric saturation, fairness or throughput. Those require separate performance profiles. CPU controls exercise pass-through behavior and reject plausible but corrupted native ledgers; they do not establish physical contention. Exact qualifying artifacts belong in qualification.
Automatic discovery against an independent inventory#
Add "discovery": "auto" to the frozen physical-rail accounting cases.
Device, port and GID pins are unset, while the whitelist contains exactly the
two locked RNICs. An identical blacklist checks the documented whitelist
precedence. A separate engine registers a small host allocation to trigger lazy
discovery with strict GID 255. Registration must return Unavailable, and
closing that engine must leave no retained owners, before the normal automatic
engine is constructed; it must not fall back to another GID.
The supervisor runs the packaged, hash-pinned discovery_inventory.py under
the original RNIC locks before and after each child. This read-only tool imports
no OrbitTE discovery code. It records raw sysfs PCI, NUMA, port, GID/type,
network-interface/index and NUMA-node information. The verifier requires an
unchanged inventory on the correct host and compares the public topology to it.
The frozen profile requires one active Ethernet port and one physical,
non-link-local RoCEv2 candidate per RNIC; ambiguous inventories fail rather than
being ranked by a second copy of the implementation. Public NIC IDs, memory
priority tiers and strict-pin rejection are checked alongside the existing
native WR/CQE and full-payload ledger.
This exercises the resource boundary corresponding to frozen TENT’s
platform/cpu_probe.cpp, runtime/topology.cpp and RDMA context address
selection: host probing supplies PCI/NUMA identity, topology assigns memory
priority tiers, and each context owns its selected port/GID. The collector is
an external oracle for this fixed inventory, not a new production layer.
This qualifies installed automatic selection on the declared RoCE inventory. It does not qualify down-port transitions, physical link loss, InfiniBand, page placement, hotplug or independent reviewer acceptance. Results and exact artifacts belong in qualification.
Peer restart with a surviving engine#
Select mode: "installed-restart" with fault_role: "source" or
"requester" and generations: 2 or 3. The other profile fields and resource
bounds above still apply. The supervisor restarts only the selected direct
child; the other process keeps the same TransferEngine and registration
through every generation. RNIC locks stay held until the complete sequence is
reaped. The controller forwards each new test-control address independently of
the surviving process’s generation.
Each generation executes three full public READ/WRITE rounds. Between generations, the target reaches zero SQ/RQ debt and active grants, while its cached QPs and four persistent MRs remain live. It publishes its exact PID and resource snapshot, then the owning supervisor sends SIGKILL and reaps that child before starting the replacement. The target never calls orderly close at this point. This is abrupt death with idle cached native resources; it is not death during outstanding DMA.
The replacement reuses its original engine service address and registration
ID, with a fresh epoch. Its peer verifies the unchanged surviving engine’s
endpoint, epoch, region ID, address and length. The survivor keeps every old
RemoteSegment returned by the public API. Once each replacement is ready,
all earlier epochs must fail both open_segment and a data submission with
EpochMismatch, zero QP/WR creation, zero transferred bytes,
RemoteEffect::None and local drain. Both processes compare their complete
allocations before starting new-authority transfers.
The survivor’s PID, engine identity, registered address and four opened MRs
must remain stable. Connection counters continue across generations; each
new generation creates at most the selected rail count times num_lanes QPs,
and the configured physical-QP budget bounds retained old plus new pairs.
Native byte counters must match each generation’s workload. At the end, both
remaining processes close and unregister, with zero counted owners and every
opened QP closed. SIGKILL receipts prove child termination, not a Rust close
result for the killed process.
installed_restart.py reconciles phase/round receipts with the supervisor’s
actual child PIDs and exit statuses. It also checks provider mappings for
every process, distinct replacement epochs, all stale-generation refusals,
persistent-MR counts, final byte/drain results and released locks. Its negative
controls reject incomplete or contradictory evidence. The same supervisor
continues to support the separate native cleanup fixtures that replace both
processes; their evidence must not be interpreted as a surviving-engine run.
Interrupt a posted completion RQ#
installed-interruption uses the same restart roles and two/three-generation
supervisor policy. Set payload <= block (one slice per parent), with 1–32
concurrent parents. The test protocol is orbitte-installed-host-v4; the
current production protocol is v19.
Before each nonfinal restart, complete three healthy byte/guard rounds, then arm a source-side control relay. It forwards Describe and the v19 single-slice RDMA handshake unchanged, but holds READY/GRANT after the source has bound its MW and posted its real completion receive. It rejects TCP payload and sliced opcodes. All native payload traffic still travels directly over RDMA.
Both peers must have all grants pending, unchanged payload-byte counters and
no newly opened QPs. Source receipts require one posted RQ, bound MW and
receive credit per parent. The requester must expose Running, undrained public
batches: short waits time out and free() returns BatchBusy. Conflicting CPU
writes fail on both peers. Only after both receipts agree may the supervisor
SIGKILL the exact source or requester child.
When the requester dies, the surviving source relay closes its held control
sockets; native source teardown must drain the outstanding RQ. When the source
dies, the surviving requester must report failed, locally drained batches
with zero bytes and RemoteEffect::None. The survivor verifies all backing
bytes, regains CPU write access, releases temporary grants/MWs/batches, and
keeps its process, engine, context/PD and four persistent MRs across recovery.
Failed CQ lanes must be fully reclaimed (progress_failures == progress_recoveries);
replacements are created on demand by the next connection. Fresh-epoch rounds
require all selected lanes, unchanged failure/recovery counters and correct bytes.
All old-epoch refusals then repeat as above.
This is a deterministic pre-payload authorization interruption with native RQ
debt. No failed request has permission to post payload yet. It does not prove
mid-payload DMA interruption, lost SQ CQEs, physical link/device loss or native
cleanup-failure retention. The relay is a test boundary, not a transport or
production proxy. CPU relay controls execute in every packaged-consumer build;
installed_interruption.py independently reconciles pending/drain receipts
with the restart ledger. Actual execution status is in qualification.
TENT correspondence and limits#
The reference is the frozen TENT commit
e2d6e8e0f2df113d84f9e39379f98c4b67b199ab; see
the source mapping.
TENT responsibility |
OrbitTE path exercised by the installed consumer |
|---|---|
Context/PD and registered buffers outlive individual endpoints |
Public registration publishes context-owned persistent MRs; they survive peer process replacement in the live engine |
|
Endpoint arrays and connection generations fence cached QPs before replacement; data uses the fresh peer authority |
Transport |
The surviving worker pool keeps servicing the same contexts while new endpoints are admitted |
|
Exact WR completion drives grants; missing observation fences shared QPs and releases unresolved ownership only after native drain |
Frozen TENT’s endpoint.cpp distinguishes duplicate peer QP arrays from a new
connection on the same NIC path, retiring the latter for reconnection. OrbitTE
additionally makes the caller-supplied source epoch explicit in its public
segment contract. The old segment must fail rather than silently adopt the
new process’s authority. This gate checks that Rust contract and owner
separation; it is not a TENT wire/API interoperability or native restart oracle.
TENT polls each context’s worker-assigned CQ, dispatches completions to slices
and sweeps software timeouts. OrbitTE follows those ownership boundaries while
retaining its explicit Rust DMA loans and WRITE Unknown outcome. The native
loss tool tests those contracts without changing the production worker design.
The main fixture owns public requests, rendezvous and byte checks.
rdma_host/resources.rs owns context selection, registration and drain;
rdma_host/restart.rs owns the peer replacement sequence;
rdma_host/interruption.rs owns public pending-batch and drain assertions;
rdma_host/gate.rs owns the bounded test control relay.
rdma_host/completion.rs controls the separate native loss tool;
installed_completion.py joins native WR/CQE/destruction identities.
No test-only route bypasses the production handshake or native resource lifecycle. TCP carries
test coordination; payload requests remain explicit RDMA.
The installed gate covers healthy host traffic, idle-resource process restart, posted-RQ interruption before payload authorization, lost payload CQEs, requester QP cleanup failure, in-flight QP interruption and MR/MW cleanup with same-process engine replacement. The QP cases and memory cases, along with CQ/channel/PD dependency cases, have selected two-host qualification. Physical port/device faults, per-rail bandwidth/fairness and InfiniBand/GPU transfers are outside this profile. Native fault libtests retain their separate internal ownership evidence. See qualification for executed artifact/profile results and P4’s final acceptance queue for the remaining hardware and independent-review gates.
Lost payload completions#
Select mode: "installed-lost-completion" with the same restart role and
2–3 generations. Freeze payload <= block (one native payload WR per parent),
8 parents with one lane or 16 with three lanes, a 1 MiB payload, both rails,
both directions, READ/WRITE, and polling/event-driven progress before running.
Regressions include healthy single-rail and posted-RQ interruption cases.
build_installed.py also compiles the packaged tests/hardware/cq_fault/
qualification tool against the system verbs headers. Its CPU control checks
real-callback pass-through, selective loss and error preservation. The manifest
pins the tool sources, compiler and shared-library hash. The controller copies
that immutable library beside the installed consumer; the supervisor checks
its hash before each requester generation and sets LD_PRELOAD only for that
fault profile. The production crate and dependency features remain unchanged.
After healthy warmup, both peers replace their buffers with a new input pattern
and destination sentinels. The tool arms only on the requester. It calls the
original provider post_send and poll_cq; it records accepted payload WRs and
removes their actual successful READ/WRITE CQEs from the returned completion
array. Nonmatching and error completions pass through. Separate bounded JSONL
receipts identify the native process, context, CQ, QP, WR and addresses. No
payload or successful completion is fabricated. The controller joins exact WR
identities and parent ranges, independently of public fixture counters.
At the kill point, all payload CQEs have been observed by the tool, while the
engine still owns SQ debt, source RQ/MW authorization, DMA loans and busy public
batches. The supervisor kills its exact configured child. The surviving
requester must fail and drain its batches, retain zero reported payload bytes,
and classify WRITE effects as Unknown (READ has no remote write effect).
Actual successful ibv_destroy_qp receipts must cover every affected native QP
before the public drain receipt. On either surviving role, full new payload
bytes and guards must match, temporary owners must drain, the original four
persistent MRs remain registered, and CPU access becomes available. Subsequent
generations reject stale authority and resume healthy native transfers.
This profile establishes lost engine observation after real payload completion. It does not establish a physical link failure or termination of DMA interrupted mid-flight. Native cleanup failures and physical faults keep separate qualification gates; this is correctness evidence, not performance.
Native cleanup failure#
mode: "installed-cleanup-failure" exercises the packaged public consumer’s
quarantine contract. Freeze fault_role: "requester", two generations, one
parent, payload <= block, one lane and rail mask 1 or 2. It uses the same
identity-pinned two-peer controller and immutable fault library as completion
loss. The fixture passes the focused
single-host diagnostic and the
declared two-host cases.
The qp selector below is the default. The
CQ/channel/PD selectors
reuse this supervisor with their own dependency and memory-lifetime checks.
After the healthy rounds, the observer records one accepted payload WR and
withholds its actual successful CQE. It arms cleanup failure on that exact
QP. When the engine retires the timed-out connection, a joined helper thread
calls the real ibv_destroy_qp under a thread-local seccomp filter. The filter
rejects write/ioctl only on that context’s uverbs command descriptor. The
provider must return EIO; the calling engine worker remains unfiltered.
This is syscall fault injection, not a spontaneous hardware failure.
The public batch must fail with Unquiesced and locally_drained = false;
free stays busy, CPU writes remain excluded, and SQ, direct-byte, batch and
inflight-byte charges remain held. Unregister removes lookup ownership, while
two close attempts cannot report drain or retry native destruction. After all
fixture-owned strong handles drop, an external allocation’s weak owner and a
real QP query must still prove retained backing and the same native QP.
Closing fences software admission before calling the destructor; it does not
require a preceding transition to ERR. Record the actual queried state. Separate
native receipts join the submitted WR, one destructor failure and both QP
queries to the public ownership proof. The supervisor then reaps that exact
process; its replacement runs the existing stale-authority and healthy-transfer
checks against the surviving source.
CPU controls verify FD/thread isolation and reject wrong QP identities, retries, missing backing and false drain evidence. They do not qualify native retention. This profile also does not prove interrupted in-flight DMA, MR/MW or channel cleanup failure through the installed API; those have their own profiles below. Physical link loss and provider teardown remain separate gates.
In-flight DMA interruption#
mode: "installed-inflight-dma" uses one parent, one lane, rail mask 1 or 2,
and payload == block (one native WR). The focused profile selects 4 MiB
READ/polling and 16 MiB WRITE/events, with two persistent registered contexts.
Only this mode permits blocks up to 16 MiB; other modes retain their 4 MiB
limit. The first 4 MiB WRITE diagnostic completed every byte before ERR and
was correctly rejected as proof of interruption.
It has no restart controls: both original engines must survive the fault.
Use the same packaged consumer, pinned observer library, provider inventory
and resource leases described above. Implementation and executed scope are
reported separately in qualification.
After three healthy rounds, the requester observer calls the real provider
post_send, then transitions that exact test-owned QP to IBV_QPS_ERR through
ibv_modify_qp. It changes no port or device configuration. All CQEs pass
through unchanged. The observer joins the accepted WR to its actual error WC
by CQ, QPN and WR ID; it never reads opcode or byte length from an error WC.
A real successful QP destruction must follow that failed completion before
the observer can report native drain.
The public batch must fail with zero successful payload bytes and proven local drain. WRITE effects remain unknown. Both peers wait for SQ/RQ, grants, windows and byte/batch charges to drain and for progress recovery. Only then does the fixture read the entire registered allocation through the public API. The destination must contain both copied source bytes and untouched sentinel bytes; positions where source equals the sentinel are ambiguous and count toward neither proof. Zero-progress and fully completed copies fail qualification. Every other byte, including guards and inputs, must be unchanged. No concurrent CPU read races with active DMA.
Both original processes retain their four persistent MRs and complete three
more healthy rounds using bounded replacement QPs, then close all resources.
rdma_host/inflight.rs owns the public assertions; cq_fault/inflight.c owns
the process-local native trigger/observer; installed_inflight.py joins the
independent native and public ledgers. CPU controls reject completed/no-op
copies, wrong WR/address/process identities, missing destruction and unpaid
debt. The trigger corresponds to the frozen TENT endpoint’s
beginDestroyNoLock
ERR transition. OrbitTE keeps PD/MRs in RdmaContext, QPs in RdmaEndpoint,
WR completion ownership in CQ lanes, and shared progress in transport Workers.
The fixture verifies those existing owners through the installed API.
Both compiler profiles pass the focused single-host diagnostic and declared two-host cases. These process-local QP faults do not qualify physical link/device loss, InfiniBand or throughput.
MR/MW cleanup and engine replacement#
installed-memory-cleanup exercises the packaged public API with one parent,
one QP and one payload slice. Select cleanup_kind: "mr" and an explicit
fault_role of requester or source; cleanup_kind: "mw" requires source.
Use one rail (mask: 1 or 2), while registering both locked contexts.
The frozen cases reuse 64 KiB READ/polling and WRITE/events workloads; larger
payloads and higher concurrency add no new owner contract to this gate.
After three healthy byte/guard rounds, the target fails the real
ibv_dereg_mr or provider dealloc_mw call with EIO. A joined helper thread
filters command writes/ioctls only on that context’s uverbs FD; engine workers
remain unfiltered. The observer selects the actual payload lkey or source
parent MR, and joins MW cleanup to the accepted BIND’s MR/MW/key identity.
It rejects any destructor retry on the retained object.
Idle MR failure must retain the registration charge and actual backing, with zero SQ/RQ debt. MW failure must retain its parent MR borrow, grant, exact DMA loan, byte quota and key domain. Repeated public close must not report drain. After unregistering and dropping fixture owners, a weak reference to the actual allocation must survive. The observer checks unchanged native handle/key fields and calls the real PD destructor, which must still return EBUSY. This is a native dependency check, not an MR query API or a physical-device-loss test.
Both original processes then create fresh engines at the same service addresses. Old source epochs and segments fail before native QP creation. Three more complete byte/guard rounds reuse the fresh engines’ persistent MRs and QPs; the fresh allocations are released, while the old quarantined allocation and native dependency remain retained. Process exit and supervisor reaping finally release the kernel resources. A successful replacement does not mark the old engine drained or recover its quarantined owners.
rdma_host/memory.rs owns these public lifecycle assertions;
cq_fault/memory.c owns native observation and scoped syscall failure;
installed_memory.py independently joins both ledgers to supervisor PIDs,
provider mappings and released locks. CPU controls reject wrong identities,
missing native errors, retries, early backing/quota release, false drain,
stale epochs and unreaped children. The six frozen two-host cases
pass on stable and Rust 1.85; that qualification is separate from physical
device loss, other teardown owners and independent acceptance.
CQ, completion-channel and PD dependency failures#
Use the existing installed-cleanup-failure mode with cleanup_kind set to
cq, channel or pd. Omitting the selector keeps the original qp case.
The scope remains one requester, one parent, one QP/active rail, two registered
contexts and two process generations. Channel failure requires event progress.
This extends the existing restart fixture; it does not introduce a transport or
production failure-injection API.
Owner |
Native failure |
Required lifetime after dropping fixture owners |
|---|---|---|
CQ |
A real dependent RESET QP prevents CQ destruction after the payload QP closes |
CQ/PD and unresolved grant, SQ, batch and actual allocation remain retained |
Completion channel |
A real extra CQ prevents channel destruction after both engine CQs close |
Channel/context and unresolved grant/actual allocation remain retained; QPs and engine CQs stay released |
PD |
A real Type-2 MW prevents PD destruction during idle teardown |
PD/device remain retained; all payload MRs, grants, batches and actual business allocation are released |
Each real destructor returns EBUSY once. The observer rejects engine retries
and premature parent-device close. It queries the native dependency before and
after all fixture owners are dropped: QP query for CQ, empty-CQ poll plus channel
identity/open FD for the completion channel, and a real PD busy probe for the
MW dependency. The extra blocker has no application allocation or DMA loan;
it remains until the supervisor reaps that exact process.
CQ/channel cases reuse a withheld successful payload CQE and require failed, unquiesced public batches and unchanged memory/byte/SQ charges. The idle PD case has no lost CQE or pending request. All three refuse repeated successful close, then reuse the existing live-peer restart checks: fresh epoch at the same endpoint, stale authority refused before QP/WR creation, three healthy full-byte/guard rounds and final release of resource locks. Native receipts, public ownership and the supervisor ledger must agree.
The selected profile and executed artifact status are recorded in qualification. Process-local dependency failures do not qualify physical link loss, provider removal or an InfiniBand fabric.