Interactions — ironic — branch stable/2026.2
Branch
stable/2026.2@39ce09ec3b0d— repository https://opendev.org/openstack/ironic — analysed on 2026-10-29
Ironic talks to five kinds of systems: its database, its RPC transport between API and conductors, other OpenStack services over REST, the ramdisk agent (ironic-python-agent, IPA) over HTTP, and the hardware itself (BMCs over Redfish or IPMI, the provisioning network over DHCP/TFTP/HTTP). The last two have no equivalent in the other OpenKeel services and drive most of the Kubernetes design of openkeel-ironic (07 § 9).
flowchart LR
subgraph clients
nova[nova-compute ironic driver]
cli[openstack baremetal / SDK]
end
api[ironic-api] -->|SQL| db[(MariaDB)]
cond[ironic-conductor] -->|SQL| db
api -->|RPC call/cast: oslo.messaging or JSON-RPC| cond
nova -->|REST /v1 + token| api
cli -->|REST /v1| api
ipa[IPA in ramdisk] -->|REST lookup / heartbeat / continue_inspection| api
cond -->|REST :9999 /v1/commands| ipa
cond -->|Redfish HTTPS / IPMI UDP 623| bmc[(BMC)]
cond -->|REST| glance[Glance] & swift[Swift] & neutron[Neutron] & cinder[Cinder]
cond -->|REST os-server-external-events| novaapi[Nova API]
cond -->|DHCP options via Neutron or dnsmasq files| dhcp[(DHCP/TFTP)]
cond -->|files under http_root| http[(HTTP server)]
net[ironic-networking] -->|switch drivers| sw[(switches)]
cond -->|RPC| net1. RPC between API and conductors
1.1 Transports
[DEFAULT]/rpc_transport | Wire | Addressing | Source |
|---|---|---|---|
oslo (default) | AMQP (RabbitMQ) through oslo.messaging, exchange ironic (control_exchange) | topic ironic.conductor_manager, server = conductor host; a call to a node goes to the conductor chosen by the hash ring | ironic/common/rpc.py:36, ironic/conductor/rpcapi.py:196 |
json-rpc | HTTP(S) POST to http://<conductor host>:<[json_rpc]/port> (8089) with JSON-RPC 2.0 bodies; optional Keystone or HTTP basic auth | the per-host topic ironic.conductor_manager.<host>[:port] is parsed back into host and port | ironic/common/json_rpc/client.py:83, ironic/common/json_rpc/server.py:97 |
none | in-process call (all-in-one ironic process) | API and conductor in the same process | ironic/conductor/rpcapi.py:42 |
JSON-RPC is present on this branch and is the natural choice for OpenKeel (no broker, one Service per conductor pod, see 07 § 9.2). Since RPC 1.58 a conductor using JSON-RPC registers itself as host:port (ironic/conductor/base_manager.py:115), so the hash ring entries are directly the JSON-RPC endpoints.
1.2 Routing (hash ring)
- One ring per
"<conductor_group>:<hardware_type>"built from theconductorstable rows that are online (ironic/db/sqlalchemy/api.py:1573) withtooz.hashring.HashRing(hosts, partitions=2**[DEFAULT]/hash_partition_exponent, hash_function=[DEFAULT]/hash_ring_algorithm)— 32 partitions, md5 by default (ironic/common/hash_ring.py:68). - A node is sent to
ring.get_nodes(node.uuid)(ironic/conductor/rpcapi.py:247). Rings are cached[DEFAULT]/hash_ring_reset_intervalseconds (15) and rebuilt when the driver/group is missing. - Requests not tied to a node (driver properties, create allocation) go to a random live conductor (
ironic/conductor/rpcapi.py:278,ironic/conductor/rpcapi.py:288). - Vectors (
_rewrite/hash-ring-vectors_stable-2026.2.json) let a rewrite check it maps nodes exactly like Ironic; this matters only during a mixed Python/Go migration (a Go-only deployment just needs a consistent rule).
1.3 RPC methods (conductor API version 1.62)
All are call except continue_node_clean, continue_node_deploy, continue_node_service (cast). Arguments are serialised versioned objects (IronicObjectSerializer); results are objects or plain JSON.
<!-- GEN:rpc-table -->
| Method | Mode | Client (ironic/conductor/rpcapi.py) | Server handler | Arguments |
|---|---|---|---|---|
create_node | call | ironic/conductor/rpcapi.py:345 | ironic/conductor/manager.py:125 | node_obj |
update_node | call | ironic/conductor/rpcapi.py:371 | ironic/conductor/manager.py:188 | node_obj, reset_interfaces |
change_node_power_state | call | ironic/conductor/rpcapi.py:392 | ironic/conductor/manager.py:321 | node_id, new_state, timeout |
change_node_boot_mode | call | ironic/conductor/rpcapi.py:412 | ironic/conductor/manager.py:386 | node_id, new_state |
change_node_secure_boot | call | ironic/conductor/rpcapi.py:432 | ironic/conductor/manager.py:415 | node_id, new_state |
vendor_passthru | call | ironic/conductor/rpcapi.py:469 | ironic/conductor/manager.py:445 | node_id, driver_method, http_method, info |
driver_vendor_passthru | call | ironic/conductor/rpcapi.py:514 | ironic/conductor/manager.py:529 | driver_name, driver_method, http_method, info |
get_node_vendor_passthru_methods | call | ironic/conductor/rpcapi.py:530 | ironic/conductor/manager.py:614 | node_id |
get_driver_vendor_passthru_methods | call | ironic/conductor/rpcapi.py:552 | ironic/conductor/manager.py:635 | driver_name |
do_node_deploy | call | ironic/conductor/rpcapi.py:581 | ironic/conductor/manager.py:917 | node_id, rebuild, configdrive, deploy_steps |
do_node_tear_down | call | ironic/conductor/rpcapi.py:601 | ironic/conductor/manager.py:1064 | node_id |
do_provisioning_action | call | ironic/conductor/rpcapi.py:619 | ironic/conductor/manager.py:1458 | node_id, action |
continue_node_clean | cast | ironic/conductor/rpcapi.py:633 | ironic/conductor/manager.py:1413 | node_id |
continue_node_deploy | cast | ironic/conductor/rpcapi.py:647 | ironic/conductor/manager.py:964 | node_id |
continue_node_service | cast | ironic/conductor/rpcapi.py:661 | ironic/conductor/manager.py:1018 | node_id |
validate_driver_interfaces | call | ironic/conductor/rpcapi.py:675 | ironic/conductor/manager.py:2264 | node_id |
destroy_node | call | ironic/conductor/rpcapi.py:691 | ironic/conductor/manager.py:2328 | node_id |
get_console_information | call | ironic/conductor/rpcapi.py:705 | ironic/conductor/manager.py:2586 | node_id |
set_console_mode | call | ironic/conductor/rpcapi.py:723 | ironic/conductor/manager.py:2615 | node_id, enabled |
create_port | call | ironic/conductor/rpcapi.py:739 | ironic/conductor/manager.py:2691 | port_obj |
update_port | call | ironic/conductor/rpcapi.py:755 | ironic/networking/manager.py:329 | switch_id, port_name, description, mode, native_vlan, allowed_vlans, default_vlan, lag_name |
update_portgroup | call | ironic/conductor/rpcapi.py:772 | ironic/conductor/manager.py:2811 | portgroup_obj |
destroy_portgroup | call | ironic/conductor/rpcapi.py:787 | ironic/conductor/manager.py:2466 | portgroup |
get_driver_properties | call | ironic/conductor/rpcapi.py:801 | ironic/conductor/manager.py:2970 | driver_name |
set_boot_device | call | ironic/conductor/rpcapi.py:826 | ironic/conductor/manager.py:3173 | node_id, device, persistent |
get_boot_device | call | ironic/conductor/rpcapi.py:852 | ironic/conductor/manager.py:3203 | node_id |
inject_nmi | call | ironic/conductor/rpcapi.py:872 | ironic/conductor/manager.py:3234 | node_id |
get_supported_boot_devices | call | ironic/conductor/rpcapi.py:893 | ironic/conductor/manager.py:3259 | node_id |
set_indicator_state | call | ironic/conductor/rpcapi.py:918 | ironic/conductor/manager.py:3286 | node_id, component, indicator, state |
get_indicator_state | call | ironic/conductor/rpcapi.py:944 | ironic/conductor/manager.py:3320 | node_id, component, indicator |
get_supported_indicators | call | ironic/conductor/rpcapi.py:976 | ironic/conductor/manager.py:3350 | node_id, component |
inspect_hardware | call | ironic/conductor/rpcapi.py:996 | ironic/conductor/manager.py:3414 | node_id |
destroy_port | call | ironic/conductor/rpcapi.py:1009 | ironic/conductor/manager.py:2435 | port |
set_target_raid_config | call | ironic/conductor/rpcapi.py:1031 | ironic/conductor/manager.py:3483 | node_id, target_raid_config |
get_raid_logical_disk_properties | call | ironic/conductor/rpcapi.py:1056 | ironic/conductor/manager.py:3519 | driver_name |
do_node_clean | call | ironic/conductor/rpcapi.py:1084 | ironic/conductor/manager.py:1340 | node_id, clean_steps, disable_ramdisk |
heartbeat | call | ironic/conductor/rpcapi.py:1120 | ironic/conductor/manager.py:3554 | node_id, callback_url, agent_version, agent_token, agent_verify_ca, agent_status, agent_status_message |
object_class_action_versions | call | ironic/conductor/rpcapi.py:1146 | ironic/conductor/manager.py:3771 | objname, objmethod, object_versions, args, kwargs |
object_action | call | ironic/conductor/rpcapi.py:1172 | ironic/conductor/manager.py:3798 | objinst, objmethod, args, kwargs |
object_backport_versions | call | ironic/conductor/rpcapi.py:1197 | ironic/conductor/manager.py:3830 | objinst, object_versions |
destroy_volume_connector | call | ironic/conductor/rpcapi.py:1216 | ironic/conductor/manager.py:2517 | connector |
update_volume_connector | call | ironic/conductor/rpcapi.py:1243 | ironic/conductor/manager.py:2891 | connector |
destroy_volume_target | call | ironic/conductor/rpcapi.py:1258 | ironic/conductor/manager.py:2552 | target |
update_volume_target | call | ironic/conductor/rpcapi.py:1283 | ironic/conductor/manager.py:2935 | target |
vif_attach | call | ironic/conductor/rpcapi.py:1301 | ironic/conductor/manager.py:3666 | node_id, vif_info |
vif_detach | call | ironic/conductor/rpcapi.py:1317 | ironic/conductor/manager.py:3713 | node_id, vif_id |
vif_list | call | ironic/conductor/rpcapi.py:1333 | ironic/conductor/manager.py:3640 | node_id |
do_node_rescue | call | ironic/conductor/rpcapi.py:1352 | ironic/conductor/manager.py:674 | node_id, rescue_password |
do_node_unrescue | call | ironic/conductor/rpcapi.py:1370 | ironic/conductor/manager.py:785 | node_id |
add_node_traits | call | ironic/conductor/rpcapi.py:1387 | ironic/conductor/manager.py:3856 | node_id, traits, replace |
remove_node_traits | call | ironic/conductor/rpcapi.py:1403 | ironic/conductor/manager.py:3886 | node_id, traits |
create_allocation | call | ironic/conductor/rpcapi.py:1414 | ironic/conductor/manager.py:3915 | allocation |
destroy_allocation | call | ironic/conductor/rpcapi.py:1426 | ironic/conductor/manager.py:3958 | allocation |
get_node_with_token | call | ironic/conductor/rpcapi.py:1439 | ironic/conductor/manager.py:4013 | node_id |
continue_inspection | call | ironic/conductor/rpcapi.py:1453 | ironic/conductor/manager.py:4250 | node_id, inventory, plugin_data |
do_node_service | call | ironic/conductor/rpcapi.py:1474 | ironic/conductor/manager.py:4302 | node_id, service_steps, disable_ramdisk |
attach_virtual_media | call | ironic/conductor/rpcapi.py:1501 | ironic/conductor/manager.py:4401 | node_id, device_type, image_url, image_download_source |
detach_virtual_media | call | ironic/conductor/rpcapi.py:1527 | ironic/conductor/manager.py:4447 | node_id, device_types |
get_virtual_media | call | ironic/conductor/rpcapi.py:1546 | ironic/conductor/manager.py:4376 | node_id |
update_portgroup_physical_network | call | ironic/conductor/rpcapi.py:1559 | ironic/conductor/manager.py:2489 | portgroup, new_physical_network |
Networking service (ironic-networking, topic ironic.networking_manager):
| Method | Mode | Client | Arguments |
|---|---|---|---|
update_port | call | ironic/networking/rpcapi.py:131 | switch_id, port_name, description, mode, native_vlan, allowed_vlans, lag_name, default_vlan |
reset_port | call | ironic/networking/rpcapi.py:168 | switch_id, port_name, native_vlan, allowed_vlans, default_vlan |
get_switches | call | ironic/networking/rpcapi.py:187 | |
update_lag | call | ironic/networking/rpcapi.py:219 | switch_ids, lag_name, description, mode, native_vlan, aggregation_mode, allowed_vlans, default_vlan |
delete_lag | call | ironic/networking/rpcapi.py:246 | switch_ids, lag_name |
<!-- /GEN:rpc-table -->
1.4 Sample frames
oslo.messaging call (do_node_deploy), AMQP body on exchange ironic, routing key ironic.conductor_manager.<host>:
{"oslo.version": "2.0",
"oslo.message": "{\"method\": \"do_node_deploy\", \"args\": {\"node_id\": \"1be26c0b-03f2-4d2e-ae87-c02d7f33c123\", \"rebuild\": false, \"configdrive\": null, \"deploy_steps\": null}, \"version\": \"1.52\", \"namespace\": null, \"_msg_id\": \"6b2…\", \"_reply_q\": \"reply_7f3…\", \"_unique_id\": \"a1c…\", \"_context_request_id\": \"req-…\", \"_context_project_id\": \"…\", \"_context_roles\": [\"admin\"], \"_context_system_scope\": \"all\"}"}
JSON-RPC (same call) — POST http://ironic-conductor-0.ironic-conductor:8089/ (ironic/common/json_rpc/client.py:218):
{"jsonrpc": "2.0", "method": "do_node_deploy", "id": "req-…",
"params": {"context": {"user_id": "…", "project_id": "…", "roles": ["admin"], "request_id": "req-…", "system_scope": "all"},
"node_id": "1be26c0b-03f2-4d2e-ae87-c02d7f33c123", "rebuild": false, "configdrive": null,
"deploy_steps": null, "rpc.version": "1.52"}}
Reply {"jsonrpc": "2.0", "result": null, "id": "req-…"}; error {"jsonrpc": "2.0", "id": "req-…", "error": {"code": 409, "message": "Node … is locked by host …", "data": {"class": "ironic.common.exception.NodeLocked"}}} (ironic/common/json_rpc/server.py:163). A cast has no id and gets 204.
1.5 Networking service RPC
ironic-networking (new process, ironic/command/networking.py:39) listens on topic ironic.networking_manager (ironic/common/rpc.py:37) and drives physical switches through switch drivers (generic-switch wraps networking-generic-switch, noop), for the ironic-networking network interface. Its five calls (update_port, reset_port, get_switches, update_lag, delete_lag) are in the table above. It needs switch credentials in a config file (ironic/networking/manager.py:111).
2. Notifications
Versioned notifications on [oslo_messaging_notifications] (driver noop by default): baremetal.<object>.<action>.<status> with object ∈ node, port, portgroup, chassis, volume connector/target, deploy template, allocation, runbook, and statuses start/end/success/error (ironic/conductor/notification_utils.py:65). Provision transitions emit baremetal.node.provision_set.{start,end,success,error} computed from stable/unstable states (ironic/conductor/task_manager.py:508); power changes baremetal.node.power_set.* and power_state_corrected.success. Sensor data (hardware.ipmi.metrics) is sent when [sensor_data]/send_sensor_data (ironic/common/rpc.py:142). Nobody in the OpenKeel client set consumes them; Ceilometer-style consumers would.
3. Database
21 tables (ironic/db/sqlalchemy/models.py); the migrated schema equals the models except one index name (_rewrite/schema-diff_stable-2026.2.md).
| Table | Role | Written by | Read by |
|---|---|---|---|
nodes (ironic/db/sqlalchemy/models.py:167) | the node: identity, driver and interfaces, driver_info (BMC credentials, secret), instance_info, driver_internal_info (agent URL, agent token, step lists and indexes), provision/power states, reservation (lock holder hostname), conductor_affinity, owner/lessee | API (create via conductor), conductor | API, conductor, periodic tasks |
ports, portgroups | NICs, local_link_connection, internal_info.tenant_vif_port_id | conductor | both |
conductors, conductor_hardware_interfaces | live conductors (hostname, group, updated_at heartbeat, online) and their enabled interfaces | conductor at start and every heartbeat_interval | API (hash ring, /v1/drivers), conductors (offline detection) |
node_traits, node_tags | traits (Nova/Placement scheduling, deploy templates) | conductor | API, conductor |
allocations | allocation requests | API (create), conductor | both |
deploy_templates, deploy_template_steps, runbooks, runbook_steps, runbook_traits | step catalogues | API | conductor |
inspection_rules, node_inventory | inspection rules; stored inventory and plugin data | API / conductor | conductor / API |
bios_settings, firmware_information | cached BIOS settings and firmware versions | conductor | API |
node_history | events and errors per node | conductor | API |
volume_connectors, volume_targets | boot from volume | API via conductor | conductor (Cinder) |
chassis | grouping | API | API |
Notable transactions
- Node lock —
UPDATE nodes SET reservation=:host WHERE id=:id AND reservation IS NULL;rowcount != 1→NodeLocked(ironic/db/sqlalchemy/api.py:752); releaseUPDATE … SET reservation=NULL WHERE id=:id AND reservation=:host(ironic/db/sqlalchemy/api.py:793). Retried byTaskManager(ironic/conductor/task_manager.py:343). This is the only concurrency control on nodes: there is no optimistic version column. - Conductor heartbeat —
UPDATE conductors SET updated_at=now(), online=:online WHERE hostname=:hostevery 10 s (ironic/db/sqlalchemy/api.py:1524); a conductor is offline whenupdated_atis older than[conductor]/heartbeat_timeout(60 s),ironic/db/sqlalchemy/api.py:1596. - Startup —
clear_node_reservations_for_conductorreleases locks held under this hostname by a previous run (ironic/db/sqlalchemy/api.py:1538); then stuck transient states are failed (05). - Allocation takeover —
UPDATE allocations SET conductor_affinity=:new WHERE id=:id AND conductor_affinity=:old(ironic/db/sqlalchemy/api.py:2584). - Lookup by MAC — join
ports→nodesonaddress IN (...); more than one node → 404 (ironic/db/sqlalchemy/api.py:1736).
4. Outbound REST calls
| Target | Calls | When | Authentication | Errors handled | Defined at |
|---|---|---|---|---|---|
| Keystone | GET /v3/auth/tokens (keystonemiddleware) | every API request with a token | service user [keystone_authtoken] | 401 to the client | ironic/api/app.py:135 |
| Glance | GET /v2/images/{id}, GET /v2/images/{id}/file, members | deploy: image properties, checksum, download into the conductor image cache; Swift temp URL generation for image_download_source=swift | [glance] service auth or user token | ImageNotFound 404, ImageNotAuthorized, ImageDownloadFailed | ironic/common/glance_service/image_service.py:166, ironic/common/glance_service/image_service.py:193, ironic/common/glance_service/image_service.py:322 |
| Swift | PUT/GET/DELETE /v1/<account>/<container>/<object>, temp URL HMAC | configdrive storage ([deploy]/configdrive_use_object_store), Redfish virtual media ISOs ([redfish]/use_swift), Glance-backed temp URLs | [swift] service auth; temp URL key [glance]/swift_temp_url_key | SwiftOperationError | ironic/common/swift.py:41, ironic/common/images.py:628 |
| Neutron | GET /v2.0/networks, POST/PUT/DELETE /v2.0/ports, GET /v2.0/agents, GET /v2.0/segments | provisioning/cleaning/rescuing/inspection/servicing networks: create ports on those networks for the node, update tenant ports with binding:host_id=<node uuid>, binding:vnic_type=baremetal, binding:profile.local_link_information (switch id/port) | [neutron] service auth | NetworkError (500) | ironic/common/neutron.py:342, ironic/drivers/modules/network/common.py:289 |
| Neutron (DHCP) | PUT /v2.0/ports/{id} with extra_dhcp_opts (PXE/iPXE boot file, next-server) | pxe/ipxe boot with the neutron DHCP provider | as above | FailedToUpdateDHCPOptOnPort | ironic/dhcp/neutron.py:37 |
| Nova | POST /v2.1/os-server-external-events {"events": [{"name": "power-update", "server_uuid", "tag": "POWER_ON"/"POWER_OFF"}]}, microversion 2.76 | power sync found the hardware in a different state and did not force it back | [nova] service auth | logged only | ironic/common/nova.py:91 |
| Cinder | attach/detach volumes, initialize connection | cinder storage interface (boot from volume) | [cinder] | StorageError | ironic/drivers/modules/storage/cinder.py |
| Placement | none | — | — | — | Ironic does not call Placement; Nova's ironic driver publishes one resource provider per node with CUSTOM_<resource_class>=1 (nova:nova/virt/ironic/driver.py:874) |
5. The ramdisk agent (ironic-python-agent)
Two directions, both HTTP(S) on the provisioning network:
| Direction | Endpoint | Purpose | Defined at |
|---|---|---|---|
| IPA → Ironic API | GET /v1/lookup, POST /v1/heartbeat/{uuid}, POST /v1/continue_inspection | find the node, get the token and config, report liveness and the agent URL, send inventory | ironic/api/controllers/v1/ramdisk.py:143, ipa:ironic_python_agent/ironic_api_client.py:159 |
| Conductor → IPA | POST <agent_url>/v1/commands/?wait=<bool>&agent_token=<token> {"name": "<ext>.<command>", "params": {...}}; GET <agent_url>/v1/commands/?agent_token= (status of all commands) | run steps and deploy actions in the ramdisk | ironic/drivers/modules/agent_client.py:200, ironic/drivers/modules/agent_client.py:315 |
Commands sent by the conductor (ironic/drivers/modules/agent_client.py): image.install_bootloader, clean.get_clean_steps, clean.execute_clean_step, deploy.get_deploy_steps, deploy.execute_deploy_step (the write_image step), service.get_service_steps, service.execute_service_step, standby.get_partition_uuids, standby.power_off, standby.reboot, standby.sync, log.collect_system_logs, rescue.finalize_rescue, system.lockdown. The agent serves them at ipa:ironic_python_agent/api/app.py:140.
Timeouts and retries: each POST times out after [agent]/command_timeout (60 s); connection errors are retried [agent]/max_command_attempts times (3); a polled command is checked every command_wait_interval (6 s) up to command_wait_attempts (100). On timeout the conductor asks the agent for the status of the last command before declaring failure (ironic/drivers/modules/agent_client.py:420). TLS: IPA can generate a certificate and send it as agent_verify_ca in the heartbeat (auto-TLS); [agent]/require_tls (default true) makes the conductor reject plain-HTTP callback_urls (ironic/conductor/manager.py:3554).
Sample command:
POST https://10.10.0.23:9999/v1/commands/?wait=false&agent_token=4LpX… HTTP/1.1
Content-Type: application/json
{"name": "deploy.execute_deploy_step",
"params": {"step": {"interface": "deploy", "step": "write_image", "args": {"image_info": {"id": "…", "urls": ["http://10.10.0.2:8080/images/…"], "disk_format": "raw", "checksum": "…"}, "configdrive": "…"}, "priority": 80},
"node": {…}, "ports": [{…}], "deploy_version": "…"}}
Answer: {"id": "…", "command_name": "execute_deploy_step", "command_status": "RUNNING", "command_result": null, "command_error": null}; later GET /v1/commands/ shows SUCCEEDED or FAILED with command_error.
6. Hardware: BMCs and the provisioning network
| Interface (shipped by OpenKeel) | Protocol | Library / binary | What is sent | Defined at |
|---|---|---|---|---|
redfish power | Redfish over HTTPS to driver_info.redfish_address | sushy (session cached per BMC, ironic/drivers/modules/redfish/utils.py:285) | GET /redfish/v1/Systems/<id> (PowerState), POST …/Actions/ComputerSystem.Reset {"ResetType": "On"|"ForceOff"|"GracefulShutdown"|"ForceRestart"|"GracefulRestart"} | ironic/drivers/modules/redfish/power.py:118, ironic/drivers/modules/redfish/power.py:132 |
redfish management | Redfish | sushy | PATCH /redfish/v1/Systems/<id> {"Boot": {"BootSourceOverrideTarget", "BootSourceOverrideEnabled", "BootSourceOverrideMode"}}, secure boot, indicators, NMI, firmware update through UpdateService | ironic/drivers/modules/redfish/management.py:330 |
redfish-virtual-media boot | Redfish VirtualMedia.InsertMedia {"Image": "<http url of ISO>"} then boot override to cd | sushy; ISO built by the conductor (ironic/drivers/modules/image_utils.py:445) and published under [deploy]/http_url or in Swift | the deploy ISO embeds kernel, ramdisk and kernel parameters (ipa-api-url, token pre-generated for virtual media) | ironic/drivers/modules/redfish/boot.py:884, ironic/drivers/modules/redfish/boot.py:272 |
redfish-https boot | Redfish HTTP boot (BootSourceOverrideTarget=UefiHttp, HttpBootUri) | sushy | the BMC fetches the ISO itself over HTTPS | ironic/drivers/modules/redfish/boot.py:1361 |
ipmi power / management | IPMI 2.0 (RMCP+ "lanplus", UDP 623) | ipmitool binary, one process per command (ironic/drivers/modules/ipmitool.py:645); commands throttled to one per [ipmi]/min_command_interval (5 s) per BMC | power status|on|off|cycle|reset|soft, chassis bootdev pxe|disk|cdrom|bios [options=persistent,efiboot] | ironic/drivers/modules/ipmitool.py:792, ironic/drivers/modules/ipmitool.py:1211 |
pxe / ipxe boot | DHCP options + TFTP (pxe) or HTTP (ipxe) | the conductor writes per-node config files under [pxe]/tftp_root (/tftpboot) and [deploy]/http_root (/httpboot) and sets DHCP options through the DHCP provider (neutron, dnsmasq hosts files, none) | boot file name, next-server, iPXE script boot.ipxe | ironic/drivers/modules/pxe_base.py:138, ironic/dhcp/neutron.py:37, ironic/dhcp/dnsmasq.py:27 |
| image serving | HTTP | a web server (nginx/httpd) serving [deploy]/http_root; images converted to raw and cached by the conductor (ironic/drivers/modules/image_cache.py) | IPA downloads image_info.urls | ironic/drivers/modules/agent.py:148 |
7. Other local effects
- Files: per-node PXE/iPXE configs, ISO images, image cache (master images + per-node hard links), configdrive temporary files, IPA log tarballs (
[agent]/deploy_logs_local_path). - Processes:
ipmitool,socat(serial console proxy),qemu-img(image conversion),mkisofs/xorriso(ISO), container runtimes for graphical consoles (ironic/console/container/*). - Privileges: none via privsep for the shipped interfaces; the conductor needs write access to
tftp_root/http_rootand network reachability of the BMCs and the provisioning network.
Source: reverse-spec-ironic/03-interactions_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.