Change disk roles and link purposes in a node's hardware plan (bootstrap-admin).
curl --request PATCH \ --url https://stratum.example.com/v1/admin/nodes/example/hardware-plan \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "version": 1, "disks": [ { "key": "example", "role": "system", "pool": "example", "storageClass": "example", "confirm": "example" } ], "links": [ { "name": "example", "purposes": [ "oob" ] } ] }'. Applies each change through the reconciler
that owns it, or refuses the request with the reasons; never stores a
change nothing applies. Every edit is audited
(platform.hardware_plan.edited) and bumps the version. A change that
runs on another node (a wipe, a pool) is accepted as a node storage
intent, named in the change’s applied. Install-scope; gates on
compute:UpdateNodeHardwarePlan.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”Request Bodyrequired
Section titled “Request Bodyrequired”Change disk roles and link purposes. Every change is checked first; when any is refused, nothing is applied (409 HARDWARE_PLAN_EDIT_REFUSED, each refusal with its reason). The edits that apply:
- disk
unused->compute: registers the pool (the storage-pools flow); - disk
unused->ceph-osd: wiped and marked; the Ceph page offers it; - disk
ceph-osd->unused: unmarked, while it is not an OSD; - disk
compute->unused: the pool removed; refused while it holds volumes, spans other disks, has a storage class, or is the node’s first; - link purposes
storage/overlay/provider: through the underlay reconciler, with ’s checks (a provider link has no host address, a bond’s members are up).
Refused always: a system disk (a reinstall), oob and mgmt (a node
replace).
object
The plan version the edit was made against; a newer plan answers 409 CONFLICT.
object
Required for compute.
The pool’s class; defaults to the pool’s name.
The typed wipe confirmation, the disk’s key, required when the disk holds data and the change wipes it.
object
Responses
Section titled “Responses”Applied.
The plan after an edit, and what applied each change.
object
A node’s hardware plan: one record of what each disk and each link was installed as, next to what the node reports. Written once at install (the control plane imports the install’s record on its first start; a node installed before the plan gets one derived from what it has), shown in Admin > Metal (the Nodes page’s Disks and Network tabs) and edited there only where a reconciler applies the change.
object
Bumped by every edit. Send it back in a PATCH to refuse an edit made against an older plan.
install — the install’s own record. upgrade — a node installed
before the plan existed, derived once from its disks’ use, the install’s
Ceph candidate record, its NIC roles and node.yaml’s connections.
Every disk the plan knows, plus any disk the node reports that it does not (inPlan false).
One disk’s planned role next to what the node reports.
object
The disk’s WWN, or its /dev/disk/by-id path when it has none. Names the disk in a PATCH.
The /dev/disk/by-id path the plan records it by.
system — an OS disk (fixed at install). compute — a disk of the
compute pool pool. ceph-osd — marked for a day-2 Ceph bootstrap:
the Ceph page preselects it. unused — nothing.
A compute disk’s pool; on a system disk, the pool on its carved tail.
False for a disk the node reports that the plan does not know (added after the install); shown as unused.
What the node reports for a planned disk.
object
The role the node’s use of the disk shows (mounted is system, a pool’s PV compute, an OSD or a blank reserved disk ceph-osd).
What would be affected if this device died.
This is the field that makes a disk-health finding actionable. “nvme1n1 on stratum-02 is hot” is informational; “…and it backs osd.1” is what decides whether an operator can act tonight or must act now.
Absent when the device carries nothing known.
object
Cephadm daemons whose data lives on this device (e.g. osd.1, mon.stratum-01). A mon on the OS disk is included: losing that device takes the mon with it.
LVM volume groups with a physical volume on this device, including via a partition — a PV on /dev/nvme0n1p3 is attributed to nvme0n1, because the whole disk is what fails.
match — the node’s report agrees with the plan. mismatch — see
statusReasons. missing — the node does not report the disk.
The roles a PATCH can give the disk now. Empty with lockedReason when none.
The lifelines (oob, mgmt; locked) first, then the declared underlay links.
One planned link: an ethernet NIC, a bond, or a VLAN on either, with its purposes, next to what the node reports. Lifelines (the stage-1 oob NIC and the mgmt NIC) are kept by the plan and locked; every other link is a declared underlay connection.
object
The link’s name (the underlay connection’s, or the lifeline’s NetworkManager id, oob / stratum).
An ethernet NIC’s burned-in MAC, which keys it (a renamed NIC is still found). On a link
that is an underlay connection (a bond member, the provider NIC) it is the node inventory’s
permanentMac for the NIC the link names: a bond member’s own, never the bond’s.
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.
object
The purposes the node’s running configuration shows on the link (its observed roles mapped onto purposes).
pending-ceph: the link’s storage purpose is not in use
yet because Ceph is not set up; statusReasons says “Storage: not in
use yet (Ceph not set up)”. Neutral, not a warning: the same verdict
the underlay view’s purposeStatus and readiness give.
Set when the link’s purposes cannot be edited here (oob and mgmt are install-managed lifelines).
The newest 50 versions, newest first.
One plan version.
object
Who made it; stratum-installer for the install’s record.
One change of a plan version.
object
The disk’s key or the link’s name.
What applied it (a pool registration, a wipe, the underlay reconciler), with the node storage intent to follow when it runs on another node.
One change of a plan version.
object
The disk’s key or the link’s name.
What applied it (a pool registration, a wipe, the underlay reconciler), with the node storage intent to follow when it runs on another node.
Example
{ "plan": { "seededFrom": "install", "disks": [ { "role": "system", "origin": "install", "observed": { "usage": "available", "role": "system" }, "status": "match", "allowedRoles": [ "compute" ] } ], "links": [ { "kind": "ethernet", "bond": { "mode": "802.3ad", "lacpRate": "slow" }, "purposes": [ "oob" ], "origin": "install", "observedPurposes": [ "oob" ], "status": "match" } ], "history": [ { "changes": [ { "target": "disk" } ] } ] }, "applied": [ { "target": "disk" } ]}Bad request.
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" } ]}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" } ]}Resource not found.
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" } ]}Refused, incomplete, or the plan changed since version.
A 409 from PATCH /v1/admin/nodes/{nodeId}/hardware-plan. For
HARDWARE_PLAN_EDIT_REFUSED nothing was applied and details.refusals
says why each change was refused. For HARDWARE_PLAN_EDIT_INCOMPLETE every
change was accepted but one failed while it ran: details.applied are in
the plan, details.refusals holds the one that failed, details.plan is
the plan now. CONFLICT (the plan changed since version) carries no
details.
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
object
object
One change of a plan version.
object
The disk’s key or the link’s name.
What applied it (a pool registration, a wipe, the underlay reconciler), with the node storage intent to follow when it runs on another node.
A node’s hardware plan: one record of what each disk and each link was installed as, next to what the node reports. Written once at install (the control plane imports the install’s record on its first start; a node installed before the plan gets one derived from what it has), shown in Admin > Metal (the Nodes page’s Disks and Network tabs) and edited there only where a reconciler applies the change.
object
Bumped by every edit. Send it back in a PATCH to refuse an edit made against an older plan.
install — the install’s own record. upgrade — a node installed
before the plan existed, derived once from its disks’ use, the install’s
Ceph candidate record, its NIC roles and node.yaml’s connections.
Every disk the plan knows, plus any disk the node reports that it does not (inPlan false).
One disk’s planned role next to what the node reports.
object
The disk’s WWN, or its /dev/disk/by-id path when it has none. Names the disk in a PATCH.
The /dev/disk/by-id path the plan records it by.
system — an OS disk (fixed at install). compute — a disk of the
compute pool pool. ceph-osd — marked for a day-2 Ceph bootstrap:
the Ceph page preselects it. unused — nothing.
A compute disk’s pool; on a system disk, the pool on its carved tail.
False for a disk the node reports that the plan does not know (added after the install); shown as unused.
What the node reports for a planned disk.
object
The role the node’s use of the disk shows (mounted is system, a pool’s PV compute, an OSD or a blank reserved disk ceph-osd).
What would be affected if this device died.
This is the field that makes a disk-health finding actionable. “nvme1n1 on stratum-02 is hot” is informational; “…and it backs osd.1” is what decides whether an operator can act tonight or must act now.
Absent when the device carries nothing known.
object
Cephadm daemons whose data lives on this device (e.g. osd.1, mon.stratum-01). A mon on the OS disk is included: losing that device takes the mon with it.
LVM volume groups with a physical volume on this device, including via a partition — a PV on /dev/nvme0n1p3 is attributed to nvme0n1, because the whole disk is what fails.
match — the node’s report agrees with the plan. mismatch — see
statusReasons. missing — the node does not report the disk.
The roles a PATCH can give the disk now. Empty with lockedReason when none.
The lifelines (oob, mgmt; locked) first, then the declared underlay links.
One planned link: an ethernet NIC, a bond, or a VLAN on either, with its purposes, next to what the node reports. Lifelines (the stage-1 oob NIC and the mgmt NIC) are kept by the plan and locked; every other link is a declared underlay connection.
object
The link’s name (the underlay connection’s, or the lifeline’s NetworkManager id, oob / stratum).
An ethernet NIC’s burned-in MAC, which keys it (a renamed NIC is still found). On a link
that is an underlay connection (a bond member, the provider NIC) it is the node inventory’s
permanentMac for the NIC the link names: a bond member’s own, never the bond’s.
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.
object
The purposes the node’s running configuration shows on the link (its observed roles mapped onto purposes).
pending-ceph: the link’s storage purpose is not in use
yet because Ceph is not set up; statusReasons says “Storage: not in
use yet (Ceph not set up)”. Neutral, not a warning: the same verdict
the underlay view’s purposeStatus and readiness give.
Set when the link’s purposes cannot be edited here (oob and mgmt are install-managed lifelines).
The newest 50 versions, newest first.
One plan version.
object
Who made it; stratum-installer for the install’s record.
One change of a plan version.
object
The disk’s key or the link’s name.
What applied it (a pool registration, a wipe, the underlay reconciler), with the node storage intent to follow when it runs on another node.
Example
{ "issues": [ { "code": "ACTION_NOT_APPLICABLE", "action": "compute:CreateVpc" } ], "details": { "refusals": [ { "target": "disk" } ], "applied": [ { "target": "disk" } ], "plan": { "seededFrom": "install", "disks": [ { "role": "system", "origin": "install", "observed": { "usage": "available", "role": "system" }, "status": "match", "allowedRoles": [ "compute" ] } ], "links": [ { "kind": "ethernet", "bond": { "mode": "802.3ad", "lacpRate": "slow" }, "purposes": [ "oob" ], "origin": "install", "observedPurposes": [ "oob" ], "status": "match" } ], "history": [ { "changes": [ { "target": "disk" } ] } ] } }}