List nodes with their underlay connections + observed status (bootstrap-admin).
curl --request GET \ --url https://stratum.example.com/v1/admin/network/underlay/nodes \ --header 'Authorization: Bearer <token>'host underlay network management. Returns every node that has
declared connections or reported status: its desired NIC/VLAN connections
plus the observed interface inventory (NICs, bonds, VLANs) and
per-connection apply state. Each interface also reports what it is FOR: its addresses, the roles cluster.yaml declares for it, the
roles the node’s own OVS/Ceph configuration shows it carrying, and a
verdict (roleStatus) — with roleMismatchReasons naming both values
when they disagree. Everything is the node’s own report.
Install-scope; gates on network:ListUnderlayNodes.
Authorizations
Section titled “Authorizations”Responses
Section titled “Responses”Nodes + underlay state.
object
object
declared host underlay connection (a NIC, VLAN or bond + addressing).
object
Resulting host interface name (<= 15 chars). e.g. enp2s0f0np0 or vlan40.
Parent device for a vlan connection.
Static CIDRs, e.g. [“192.0.2.111/24”].
traffic class this connection serves.
Firewalld zone. A cluster-internal link left in NetworkManager’s default
public zone comes up and silently drops the traffic it exists to carry, so the fabric declares trusted explicitly.
The bond this connection is a member of. A member carries no addresses of its own — the bond’s VLANs own the addressing, and an addressed member is how a link ends up half in the aggregate.
802.3ad (or active-backup) aggregation for a bond connection.
xmitHashPolicy is required for 802.3ad and is not a tuning preference.
Geneve puts every tunnel between a single MAC/IP pair, so the kernel default
(layer2) hashes ALL overlay traffic onto one member: the bond reports UP,
both links report UP, and half the fabric is unreachable.
object
Required for 802.3ad. Use layer3+4 so overlay traffic spreads across members.
Link-monitor interval in milliseconds, e.g. 100.
Keep this connection’s route out of the default path. A storage or overlay VLAN taking the default route would pull cluster egress onto the fabric.
What the link carries: the network half of
the node’s hardware plan. trafficClass is the primary one (the first
of mgmt, storage, overlay, provider); send either, or both agreeing.
oob is the stage-1 lifeline and is never an underlay connection.
An observed interface on a node: a physical NIC, a bond, or a VLAN.
this used to be physical NICs only, which on a bonded node hid the layer the traffic actually rides: the bond and the storage/overlay VLANs did not appear at all, while the members carrying every Ceph and Geneve byte showed as undeclared with no addresses.
object
UP | DOWN | UNKNOWN
Link speed in Mb/s (0 if unknown).
bridge is only ever the Stratum LB dataplane management bridge
(stratum-lbmgmt,): it holds a host address no other row
explains. Other bridges (podman, libvirt, OVS’s own) are not reported.
The bond a physical member is enslaved to.
A VLAN’s underlying device (often the bond).
A bond’s observed LACP state.
aggregated is the field that matters. Under 802.3ad a member whose LACP
negotiation failed keeps its own aggregator id and runs solo: the bond
reports UP, every link reports UP at full speed, and the cluster silently has
half the bandwidth it believes it has. Everything else here is the evidence
behind that verdict.
object
The switch the aggregate negotiated with. Absent means nothing answered LACP.
The active aggregator; every member should share it.
True only when there is an LACP partner and every UP member sits in the active aggregator. Deliberately strict — “up” is what the failure already reports.
One link in a bond, with the fields that reveal a member running outside the aggregate.
object
Up | down
The member’s LACP aggregator. A member whose negotiation failed keeps its OWN id and runs solo while still reporting up at full speed — comparing this against the bond’s aggregatorId is what exposes that.
The CIDRs actually on the link. The inventory previously reported the shape of a node’s network and nothing about what rides it.
What the cluster topology (cluster.yaml, resolved) says this interface is for. A class declared with an
interface lands on that interface; one declared by address only
(mgmt_ip, the data_ip alias, or a class collapsing to mgmt_ip)
lands on whichever interface holds the address.
What this node’s own running configuration shows the link is actually carrying, derived from the Ceph public/cluster networks, the OVN Geneve endpoint, the provider bridge mappings, the declared service VIPs and the LB dataplane management bridge.
Reported alongside declaredRoles rather than instead of them, because
the declared value is the one that goes stale. Both ceph-public and
ceph-cluster on one link is normal for a hyperconverged install and
means replication shares the link with client I/O; with no Ceph
cluster_network set, replication rides the public network, so its
link carries both.
An empty list is only “carries nothing” when the signals were read:
see roleStatus, unverifiedRoles and the node’s fabric.signals.
The topology and the running configuration disagree about this interface: it is declared for a role the running configuration puts elsewhere, or it carries a role the topology declares elsewhere. This is the field worth alarming on: was a node whose OVN Geneve endpoint had collapsed to its management address while the topology declared the overlay, and every other surface reported the node healthy while all cross-chassis tenant traffic was dropped. Both rows are flagged — the link that lost the role and the link that has it.
Never raised from a signal that could not be read (that is
unverifiedRoles), so a node without Ceph or OVN does not report
every interface as mismatched.
What disagrees, with both values, e.g. “declared overlay
192.0.2.112 on vlan91, but OVN’s Geneve endpoint (ovn-encap-ip) is
192.0.2.12 on enp89s0”. Present only when roleMismatch is true.
Declared roles this node could not check: the signal that would confirm or contradict them could not be read (no OVS database, no Ceph client), or the subsystem has nothing configured to check against. These are UNKNOWN — neither a match nor a mismatch.
Declared roles not in use yet: storage before Ceph is set
up on the node (its Ceph binds no public network). Neutral: neither
unknown nor a mismatch, and no warning. Once Ceph is set up the role
is checked against Ceph’s public and cluster networks.
The verdict for this interface. match: every declared
role was checked and agrees. mismatch: see roleMismatchReasons.
unverified: nothing disagrees, but a declared role (or the topology
itself) could not be read. pending-ceph: the storage
role is not in use yet because Ceph is not set up (“Not in use yet
(Ceph not set up)”); neutral, not a warning. undeclared: the
topology declares nothing for this link. Absent from a node whose
host-helper predates it.
What the node’s hardware plan says this interface carries
(its own link’s purposes, plus those of everything stacked on it), in
the vocabulary. The plan’s counterpart of declaredRoles.
observedRoles mapped onto purposes: mgmt / mgmt-vip ->
mgmt, ceph-public / ceph-cluster -> storage, ovn-encap -> overlay,
ovn-provider -> provider.
a VLAN sub-interface the node made itself on a planned link
(OVN’s bridged provider <trunk>.<vlan>), which no connection names,
rolls up to that link: what it runs of the trunk’s planned purposes is
observed on the trunk too, and is the sub-interface’s own purposes.
The plan against the running configuration. match: every
planned observable purpose runs on this interface or one stacked on it.
mismatch: see purposeMismatchReasons. unverified: no signal for a
planned purpose (oob is never observable). pending-ceph:
the planned storage purpose is not in use yet because Ceph is not set
up, and nothing else is unknown; neutral. undeclared: the plan gives
it none.
Observed apply-state of one declared connection.
object
The interface exists on the host.
The node-level half of the interface role attribution, as the
node itself reported it (never a peer’s view): what its topology declares
and every signal the observed roles were derived from, each with whether it
could be read. An interface with no observed roles only means “carries
nothing” when the signals it would have come from were observed.
Absent when the node’s host-helper predates it.
object
When the node’s host-helper read the signals.
This node’s traffic-class bindings as cluster.yaml resolves them
(an explicit networks entry, else the deprecated data_ip
alias, else collapse to mgmt_ip). Empty when the topology is not
observed.
One traffic class’s declared binding on a node.
object
The address the class uses on this node.
The declared link, when cluster.yaml names one. Absent means the role
sits on whichever interface holds ip.
Where the address comes from: an explicit networks.<class> entry, the
deprecated data_ip alias, or mgmt_ip (the mgmt class itself, or a
class with no binding of its own collapsing to it, -TOPO-2).
One input to a node’s interface role attribution and whether it could be read.
object
cluster-topology is this node’s entry in cluster.yaml; the others are
the node’s own OVS external_ids and Ceph config options.
observed: read, with a value. unset: read, and nothing is configured
(OVS has no such key, Ceph has no such option, the node has no Ceph
cluster at all because its ceph.conf names none, the node is not in
cluster.yaml). unobserved: could not be read — UNKNOWN, so nothing is
claimed from it, neither a role nor a mismatch.
The value read, when observed.
Why a signal is unset or unobserved (the probe’s own first line of
output), e.g. “ovs-vsctl get external_ids:ovn-encap-ip: ovs-vsctl:
unix:/var/run/openvswitch/db.sock: database connection failed”.
Example
{ "items": [ { "connections": [ { "kind": "ethernet", "trafficClass": "", "bond": { "mode": "802.3ad", "lacpRate": "slow" }, "purposes": [ "oob" ] } ], "interfaces": [ { "kind": "physical", "declaredRoles": [ "mgmt" ], "observedRoles": [ "mgmt" ], "unverifiedRoles": [ "mgmt" ], "pendingRoles": [ "storage" ], "roleStatus": "match", "purposes": [ "oob" ], "observedPurposes": [ "oob" ], "purposeStatus": "match" } ], "fabric": { "declared": [ { "role": "mgmt", "ip": "192.0.2.111", "interface": "vlan91", "source": "networks" } ], "signals": [ { "name": "cluster-topology", "state": "observed", "value": "192.0.2.111" } ] } } ]}Authenticated but not authorized.
object
The findings of a rejected IAM policy document (custom and resource policies).
One finding of a rejected IAM policy document. A rejection lists every finding, so the author fixes the document in one round trip.
object
The finding, e.g. INVALID_DOCUMENT, UNKNOWN_ACTION, ACTION_NOT_APPLICABLE.
Index of the statement at fault; -1 for the document as a whole.
The offending action, where one is at fault (ACTION_NOT_APPLICABLE).
Field errors, for codes that carry them: each problem at its field
(see FieldError). Documented on the responses that set it, e.g.
approveNodeJoin’s INVALID_INPUT and JOIN_APPROVAL_REFUSED.
Absent otherwise.
One field’s problem, the stage-2 installer API’s shape.
field is the field’s path, message what is wrong with it.
object
The field’s path, e.g. links[0].interface, disks[1].confirm,
mgmt.address or role; empty for the whole request.
Structured facts about the refusal, for codes that carry them. The
shape is fixed per code and documented on the response that answers
it, e.g. IMAGE_POOL_MISMATCH (see ImagePoolMismatchDetails) and
IMAGE_IN_USE. Absent for codes that carry none.
object
Example
{ "issues": [ { "code": "ACTION_NOT_APPLICABLE", "action": "compute:CreateVpc" } ]}