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

Reverse-spec · Services

ironic — Scenarios

End-to-end scenarios — ironic — branch stable/2026.2

Branch stable/2026.2 @ 39ce09ec3b0d — repository https://opendev.org/openstack/ironic — analysed on 2026-10-29

The scenarios use the configuration OpenKeel intends to ship: redfish hardware type, redfish-virtual-media boot, direct deploy (IPA writes the image), flat or neutron network interface, JSON-RPC between API and conductor. Variants for ipmi + ipxe are noted where they differ.

1. Enrolment, verification, inspection and cleaning

An operator registers a machine and makes it available for scheduling.

sequenceDiagram
  autonumber
  actor Op as Operator
  participant API as ironic-api
  participant C as ironic-conductor (ring owner)
  participant DB as MariaDB
  participant BMC as BMC (Redfish)
  participant IPA as IPA (ramdisk)
  Op->>API: POST /v1/nodes {driver: redfish, driver_info: {redfish_address,...}}
  API->>C: RPC call create_node
  C->>DB: INSERT nodes (provision_state=enroll)
  API-->>Op: 201 Created
  Op->>API: PUT /v1/nodes/{id}/states/provision {target: manage}
  API->>C: RPC call do_provisioning_action(manage)
  C->>DB: UPDATE nodes SET provision_state=verifying, reservation=C
  API-->>Op: 202
  C->>BMC: GET /redfish/v1/Systems/{id} (power.validate + get_power_state, verify steps)
  alt BMC reachable
    C->>DB: provision_state=manageable, power_state=power off
  else credentials or address wrong
    C->>DB: provision_state=enroll, last_error
  end
  Op->>API: PUT .../states/provision {target: inspect}
  API->>C: RPC call inspect_hardware
  C->>BMC: build deploy ISO, VirtualMedia.InsertMedia, boot override cd, Reset On
  C->>DB: provision_state=inspect wait
  IPA->>API: POST /v1/continue_inspection {inventory,...}
  API->>C: RPC call continue_inspection
  C->>DB: hooks + inspection rules: properties, ports, traits; INSERT node_inventory
  C->>BMC: eject media, power off
  C->>DB: provision_state=manageable
  Op->>API: PUT .../states/provision {target: provide}
  API->>C: RPC call do_provisioning_action(provide)
  C->>DB: provision_state=cleaning (target available)
  C->>BMC: boot IPA by virtual media
  C->>DB: clean wait
  IPA->>API: GET /v1/lookup?addresses=... → token
  IPA->>API: POST /v1/heartbeat/{id}
  API->>C: RPC call heartbeat
  C->>IPA: POST /v1/commands clean.get_clean_steps, then clean.execute_clean_step (erase_devices_metadata)
  IPA-->>C: status SUCCEEDED (next heartbeat)
  C->>BMC: eject, power off
  C->>DB: provision_state=available

Diagnostics: stuck in verifying → BMC address/credentials (last_error, GET …/validate); inspect wait then inspect failed after 30 min → the ramdisk did not boot or cannot reach the API ([DEFAULT]/ironic_api_url / [service_catalog] seen by the ramdisk, provisioning network routing); clean failed → last_error names the step, IPA logs are stored when [agent]/deploy_logs_collect allows it.

2. Nova boots a bare metal instance

Nova's ironic virt driver (nova:nova/virt/ironic/driver.py) uses openstacksdk with microversion negotiation; Placement already picked the node through its CUSTOM_<RESOURCE_CLASS> inventory.

sequenceDiagram
  autonumber
  participant N as nova-compute (ironic driver)
  participant API as ironic-api
  participant C as ironic-conductor
  participant G as Glance
  participant NE as Neutron
  participant BMC as BMC
  participant IPA as IPA
  N->>API: PATCH /v1/nodes/{id} instance_uuid (retry_on_conflict=False)
  alt another instance already reserved the node
    API-->>N: 409 InstanceAssociated → reschedule
  end
  N->>API: PATCH /v1/nodes/{id} [/instance_info/image_source, root_gb, capabilities, traits, ...]
  N->>API: POST /v1/nodes/{id}/vifs {id: neutron port} (per port)
  API->>C: RPC call vif_attach
  C->>NE: (flat) record VIF in port.internal_info; (neutron) bind later
  N->>API: GET /v1/nodes/{id}/validate
  N->>API: PUT /v1/nodes/{id}/states/provision {target: active, configdrive: ...}
  API->>C: RPC call do_node_deploy
  C->>G: GET image metadata
  C->>C: deploy steps = driver steps + deploy templates (instance traits) + user steps
  C->>NE: (neutron) create port on provisioning network, binding:host_id=node
  C->>BMC: build ISO with IPA + params, InsertMedia, boot cd, Reset
  C-->>API: node deploying → wait call-back
  API-->>N: 202
  IPA->>API: GET /v1/lookup → node + agent_token
  IPA->>API: POST /v1/heartbeat/{id} {callback_url, agent_token, agent_version}
  API->>C: RPC call heartbeat
  C->>IPA: POST /v1/commands deploy.execute_deploy_step write_image (image URL served by conductor HTTP or Swift temp URL)
  IPA->>G: (image_download_source=swift) GET temp URL / else GET conductor HTTP
  loop each heartbeat
    C->>IPA: GET /v1/commands (status)
  end
  C->>IPA: image.install_bootloader (UEFI), standby.power_off / sync
  C->>NE: remove provisioning port; bind tenant ports (binding:profile local_link_information)
  C->>BMC: eject media, boot override disk, power on
  C->>C: provision_state=active
  loop nova polls
    N->>API: GET /v1/nodes/{id}?fields=...
  end
  N->>N: instance ACTIVE

Diagnostics: 409 on the first PATCH is normal contention; deploy failed with "Timeout reached while waiting for callback" means no heartbeat for 30 min (ramdisk did not boot, provisioning network, [agent]/require_tls with a plain-HTTP agent); "Invalid or missing agent token" in conductor logs means a second lookup or a stale ramdisk; tenant network errors appear as NetworkError in last_error.

3. Instance deletion and automated cleaning

sequenceDiagram
  autonumber
  participant N as nova-compute
  participant API as ironic-api
  participant C as ironic-conductor
  participant NE as Neutron
  participant BMC as BMC
  participant IPA as IPA
  N->>API: PUT .../states/provision {target: deleted}
  API->>C: RPC call do_node_tear_down
  C->>BMC: power off
  C->>NE: unbind tenant ports
  C->>C: deleting → cleaning (target available)
  API-->>N: 202
  N->>API: DELETE /v1/nodes/{id}/vifs/{vif} (each)
  N->>API: PATCH instance_uuid=null, instance_info={}
  C->>BMC: boot IPA (virtual media)
  IPA->>API: lookup, heartbeat
  C->>IPA: clean steps (erase_devices_metadata by default)
  alt success
    C->>C: available (Nova's provider reserved until then)
  else failure
    C->>C: clean failed + last_error (operator: manage, fix, provide)
  end

4. Power state drift

sequenceDiagram
  autonumber
  participant C as ironic-conductor (_sync_power_states, 120 s)
  participant DB as MariaDB
  participant BMC as BMC
  participant NA as Nova API
  C->>DB: nodes mapped to me, maintenance=false
  C->>BMC: get_power_state
  alt BMC error 3 times
    C->>DB: maintenance=true, fault=power failure
  else state differs, force_power_state_during_sync=true
    C->>BMC: set power to DB state
  else state differs, force disabled
    C->>DB: power_state=observed
    C->>NA: POST /os-server-external-events power-update (2.76)
  end

5. Conductor failure

sequenceDiagram
  autonumber
  participant C1 as conductor-0 (dies)
  participant C2 as conductor-1
  participant DB as MariaDB
  C1-xDB: no more UPDATE conductors.updated_at
  Note over C2: after heartbeat_timeout (60 s) C1 is offline; ring rebuilt within 15 s
  C2->>DB: _check_orphan_nodes: release reservations of C1, deploying→deploy failed, cleaning→clean failed+maintenance
  C2->>DB: _sync_local_state: active nodes now mine → _do_takeover (re-create boot configs), conductor_affinity=C2
  C2->>DB: _check_orphan_allocations: take over C1's allocations
  Note over C1: on restart: clear own reservations, _correct_stuck_states fails nodes still transient

Diagnostics: nodes failing with "The deploying state can't be resumed by conductor X" after a restart are expected; frequent takeovers mean conductors flap (DB latency vs heartbeat_timeout).

Source: reverse-spec-ironic/04-scenarios_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.