OpenKeel: the sovereign French Cloud solution of the inherent group, with a European reach Discover inherent

Reverse-spec · Services

ironic — Interactions

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| net

1. RPC between API and conductors

1.1 Transports

[DEFAULT]/rpc_transportWireAddressingSource
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 ringironic/common/rpc.py:36, ironic/conductor/rpcapi.py:196
json-rpcHTTP(S) POST to http://<conductor host>:<[json_rpc]/port> (8089) with JSON-RPC 2.0 bodies; optional Keystone or HTTP basic auththe per-host topic ironic.conductor_manager.<host>[:port] is parsed back into host and portironic/common/json_rpc/client.py:83, ironic/common/json_rpc/server.py:97
nonein-process call (all-in-one ironic process)API and conductor in the same processironic/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 the conductors table rows that are online (ironic/db/sqlalchemy/api.py:1573) with tooz.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_interval seconds (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 -->

MethodModeClient (ironic/conductor/rpcapi.py)Server handlerArguments
create_nodecallironic/conductor/rpcapi.py:345ironic/conductor/manager.py:125node_obj
update_nodecallironic/conductor/rpcapi.py:371ironic/conductor/manager.py:188node_obj, reset_interfaces
change_node_power_statecallironic/conductor/rpcapi.py:392ironic/conductor/manager.py:321node_id, new_state, timeout
change_node_boot_modecallironic/conductor/rpcapi.py:412ironic/conductor/manager.py:386node_id, new_state
change_node_secure_bootcallironic/conductor/rpcapi.py:432ironic/conductor/manager.py:415node_id, new_state
vendor_passthrucallironic/conductor/rpcapi.py:469ironic/conductor/manager.py:445node_id, driver_method, http_method, info
driver_vendor_passthrucallironic/conductor/rpcapi.py:514ironic/conductor/manager.py:529driver_name, driver_method, http_method, info
get_node_vendor_passthru_methodscallironic/conductor/rpcapi.py:530ironic/conductor/manager.py:614node_id
get_driver_vendor_passthru_methodscallironic/conductor/rpcapi.py:552ironic/conductor/manager.py:635driver_name
do_node_deploycallironic/conductor/rpcapi.py:581ironic/conductor/manager.py:917node_id, rebuild, configdrive, deploy_steps
do_node_tear_downcallironic/conductor/rpcapi.py:601ironic/conductor/manager.py:1064node_id
do_provisioning_actioncallironic/conductor/rpcapi.py:619ironic/conductor/manager.py:1458node_id, action
continue_node_cleancastironic/conductor/rpcapi.py:633ironic/conductor/manager.py:1413node_id
continue_node_deploycastironic/conductor/rpcapi.py:647ironic/conductor/manager.py:964node_id
continue_node_servicecastironic/conductor/rpcapi.py:661ironic/conductor/manager.py:1018node_id
validate_driver_interfacescallironic/conductor/rpcapi.py:675ironic/conductor/manager.py:2264node_id
destroy_nodecallironic/conductor/rpcapi.py:691ironic/conductor/manager.py:2328node_id
get_console_informationcallironic/conductor/rpcapi.py:705ironic/conductor/manager.py:2586node_id
set_console_modecallironic/conductor/rpcapi.py:723ironic/conductor/manager.py:2615node_id, enabled
create_portcallironic/conductor/rpcapi.py:739ironic/conductor/manager.py:2691port_obj
update_portcallironic/conductor/rpcapi.py:755ironic/networking/manager.py:329switch_id, port_name, description, mode, native_vlan, allowed_vlans, default_vlan, lag_name
update_portgroupcallironic/conductor/rpcapi.py:772ironic/conductor/manager.py:2811portgroup_obj
destroy_portgroupcallironic/conductor/rpcapi.py:787ironic/conductor/manager.py:2466portgroup
get_driver_propertiescallironic/conductor/rpcapi.py:801ironic/conductor/manager.py:2970driver_name
set_boot_devicecallironic/conductor/rpcapi.py:826ironic/conductor/manager.py:3173node_id, device, persistent
get_boot_devicecallironic/conductor/rpcapi.py:852ironic/conductor/manager.py:3203node_id
inject_nmicallironic/conductor/rpcapi.py:872ironic/conductor/manager.py:3234node_id
get_supported_boot_devicescallironic/conductor/rpcapi.py:893ironic/conductor/manager.py:3259node_id
set_indicator_statecallironic/conductor/rpcapi.py:918ironic/conductor/manager.py:3286node_id, component, indicator, state
get_indicator_statecallironic/conductor/rpcapi.py:944ironic/conductor/manager.py:3320node_id, component, indicator
get_supported_indicatorscallironic/conductor/rpcapi.py:976ironic/conductor/manager.py:3350node_id, component
inspect_hardwarecallironic/conductor/rpcapi.py:996ironic/conductor/manager.py:3414node_id
destroy_portcallironic/conductor/rpcapi.py:1009ironic/conductor/manager.py:2435port
set_target_raid_configcallironic/conductor/rpcapi.py:1031ironic/conductor/manager.py:3483node_id, target_raid_config
get_raid_logical_disk_propertiescallironic/conductor/rpcapi.py:1056ironic/conductor/manager.py:3519driver_name
do_node_cleancallironic/conductor/rpcapi.py:1084ironic/conductor/manager.py:1340node_id, clean_steps, disable_ramdisk
heartbeatcallironic/conductor/rpcapi.py:1120ironic/conductor/manager.py:3554node_id, callback_url, agent_version, agent_token, agent_verify_ca, agent_status, agent_status_message
object_class_action_versionscallironic/conductor/rpcapi.py:1146ironic/conductor/manager.py:3771objname, objmethod, object_versions, args, kwargs
object_actioncallironic/conductor/rpcapi.py:1172ironic/conductor/manager.py:3798objinst, objmethod, args, kwargs
object_backport_versionscallironic/conductor/rpcapi.py:1197ironic/conductor/manager.py:3830objinst, object_versions
destroy_volume_connectorcallironic/conductor/rpcapi.py:1216ironic/conductor/manager.py:2517connector
update_volume_connectorcallironic/conductor/rpcapi.py:1243ironic/conductor/manager.py:2891connector
destroy_volume_targetcallironic/conductor/rpcapi.py:1258ironic/conductor/manager.py:2552target
update_volume_targetcallironic/conductor/rpcapi.py:1283ironic/conductor/manager.py:2935target
vif_attachcallironic/conductor/rpcapi.py:1301ironic/conductor/manager.py:3666node_id, vif_info
vif_detachcallironic/conductor/rpcapi.py:1317ironic/conductor/manager.py:3713node_id, vif_id
vif_listcallironic/conductor/rpcapi.py:1333ironic/conductor/manager.py:3640node_id
do_node_rescuecallironic/conductor/rpcapi.py:1352ironic/conductor/manager.py:674node_id, rescue_password
do_node_unrescuecallironic/conductor/rpcapi.py:1370ironic/conductor/manager.py:785node_id
add_node_traitscallironic/conductor/rpcapi.py:1387ironic/conductor/manager.py:3856node_id, traits, replace
remove_node_traitscallironic/conductor/rpcapi.py:1403ironic/conductor/manager.py:3886node_id, traits
create_allocationcallironic/conductor/rpcapi.py:1414ironic/conductor/manager.py:3915allocation
destroy_allocationcallironic/conductor/rpcapi.py:1426ironic/conductor/manager.py:3958allocation
get_node_with_tokencallironic/conductor/rpcapi.py:1439ironic/conductor/manager.py:4013node_id
continue_inspectioncallironic/conductor/rpcapi.py:1453ironic/conductor/manager.py:4250node_id, inventory, plugin_data
do_node_servicecallironic/conductor/rpcapi.py:1474ironic/conductor/manager.py:4302node_id, service_steps, disable_ramdisk
attach_virtual_mediacallironic/conductor/rpcapi.py:1501ironic/conductor/manager.py:4401node_id, device_type, image_url, image_download_source
detach_virtual_mediacallironic/conductor/rpcapi.py:1527ironic/conductor/manager.py:4447node_id, device_types
get_virtual_mediacallironic/conductor/rpcapi.py:1546ironic/conductor/manager.py:4376node_id
update_portgroup_physical_networkcallironic/conductor/rpcapi.py:1559ironic/conductor/manager.py:2489portgroup, new_physical_network

Networking service (ironic-networking, topic ironic.networking_manager):

MethodModeClientArguments
update_portcallironic/networking/rpcapi.py:131switch_id, port_name, description, mode, native_vlan, allowed_vlans, lag_name, default_vlan
reset_portcallironic/networking/rpcapi.py:168switch_id, port_name, native_vlan, allowed_vlans, default_vlan
get_switchescallironic/networking/rpcapi.py:187
update_lagcallironic/networking/rpcapi.py:219switch_ids, lag_name, description, mode, native_vlan, aggregation_mode, allowed_vlans, default_vlan
delete_lagcallironic/networking/rpcapi.py:246switch_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).

TableRoleWritten byRead 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/lesseeAPI (create via conductor), conductorAPI, conductor, periodic tasks
ports, portgroupsNICs, local_link_connection, internal_info.tenant_vif_port_idconductorboth
conductors, conductor_hardware_interfaceslive conductors (hostname, group, updated_at heartbeat, online) and their enabled interfacesconductor at start and every heartbeat_intervalAPI (hash ring, /v1/drivers), conductors (offline detection)
node_traits, node_tagstraits (Nova/Placement scheduling, deploy templates)conductorAPI, conductor
allocationsallocation requestsAPI (create), conductorboth
deploy_templates, deploy_template_steps, runbooks, runbook_steps, runbook_traitsstep cataloguesAPIconductor
inspection_rules, node_inventoryinspection rules; stored inventory and plugin dataAPI / conductorconductor / API
bios_settings, firmware_informationcached BIOS settings and firmware versionsconductorAPI
node_historyevents and errors per nodeconductorAPI
volume_connectors, volume_targetsboot from volumeAPI via conductorconductor (Cinder)
chassisgroupingAPIAPI

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); release UPDATE … SET reservation=NULL WHERE id=:id AND reservation=:host (ironic/db/sqlalchemy/api.py:793). Retried by TaskManager (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=:host every 10 s (ironic/db/sqlalchemy/api.py:1524); a conductor is offline when updated_at is older than [conductor]/heartbeat_timeout (60 s), ironic/db/sqlalchemy/api.py:1596.
  • Startup — clear_node_reservations_for_conductor releases 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 → nodes on address IN (...); more than one node → 404 (ironic/db/sqlalchemy/api.py:1736).

4. Outbound REST calls

TargetCallsWhenAuthenticationErrors handledDefined at
KeystoneGET /v3/auth/tokens (keystonemiddleware)every API request with a tokenservice user [keystone_authtoken]401 to the clientironic/api/app.py:135
GlanceGET /v2/images/{id}, GET /v2/images/{id}/file, membersdeploy: image properties, checksum, download into the conductor image cache; Swift temp URL generation for image_download_source=swift[glance] service auth or user tokenImageNotFound 404, ImageNotAuthorized, ImageDownloadFailedironic/common/glance_service/image_service.py:166, ironic/common/glance_service/image_service.py:193, ironic/common/glance_service/image_service.py:322
SwiftPUT/GET/DELETE /v1/<account>/<container>/<object>, temp URL HMACconfigdrive 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_keySwiftOperationErrorironic/common/swift.py:41, ironic/common/images.py:628
NeutronGET /v2.0/networks, POST/PUT/DELETE /v2.0/ports, GET /v2.0/agents, GET /v2.0/segmentsprovisioning/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 authNetworkError (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 provideras aboveFailedToUpdateDHCPOptOnPortironic/dhcp/neutron.py:37
NovaPOST /v2.1/os-server-external-events {"events": [{"name": "power-update", "server_uuid", "tag": "POWER_ON"/"POWER_OFF"}]}, microversion 2.76power sync found the hardware in a different state and did not force it back[nova] service authlogged onlyironic/common/nova.py:91
Cinderattach/detach volumes, initialize connectioncinder storage interface (boot from volume)[cinder]StorageErrorironic/drivers/modules/storage/cinder.py
Placementnone———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:

DirectionEndpointPurposeDefined at
IPA → Ironic APIGET /v1/lookup, POST /v1/heartbeat/{uuid}, POST /v1/continue_inspectionfind the node, get the token and config, report liveness and the agent URL, send inventoryironic/api/controllers/v1/ramdisk.py:143, ipa:ironic_python_agent/ironic_api_client.py:159
Conductor → IPAPOST <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 ramdiskironic/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)ProtocolLibrary / binaryWhat is sentDefined at
redfish powerRedfish over HTTPS to driver_info.redfish_addresssushy (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 managementRedfishsushyPATCH /redfish/v1/Systems/<id> {"Boot": {"BootSourceOverrideTarget", "BootSourceOverrideEnabled", "BootSourceOverrideMode"}}, secure boot, indicators, NMI, firmware update through UpdateServiceironic/drivers/modules/redfish/management.py:330
redfish-virtual-media bootRedfish VirtualMedia.InsertMedia {"Image": "<http url of ISO>"} then boot override to cdsushy; ISO built by the conductor (ironic/drivers/modules/image_utils.py:445) and published under [deploy]/http_url or in Swiftthe 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 bootRedfish HTTP boot (BootSourceOverrideTarget=UefiHttp, HttpBootUri)sushythe BMC fetches the ISO itself over HTTPSironic/drivers/modules/redfish/boot.py:1361
ipmi power / managementIPMI 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 BMCpower 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 bootDHCP 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.ipxeironic/drivers/modules/pxe_base.py:138, ironic/dhcp/neutron.py:37, ironic/dhcp/dnsmasq.py:27
image servingHTTPa 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.urlsironic/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_root and 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.