An Ironic deployment will be composed of the following components:
An admin-only RESTful API service, by which privileged users, such as cloud operators and other services within the cloud control plane, may interact with the managed bare metal servers.
A Conductor service, which does the bulk of the work. Functionality is exposed via the API service. The Conductor and API services communicate via RPC.
A Database and DB API for storing the state of the Conductor and Drivers.
A Deployment Ramdisk or Deployment Agent, which provide control over the hardware which is not available remotely to the Conductor. A ramdisk should be built which contains one of these agents, eg. with diskimage-builder. This ramdisk can be booted on-demand.
- NOTE: The agent is never run inside a tenant instance.
The internal driver API provides a consistent interface between the Conductor service and the driver implementations. A driver is defined by a class inheriting from the BaseDriver class, defining certain interfaces; each interface is an instance of the relevant driver module.
For example, a fake driver class might look like this:
class FakePower(base.PowerInterface):
def get_properties(self):
return {}
def validate(self, task):
pass
def get_power_state(self, task):
return states.NOSTATE
def set_power_state(self, task, power_state):
pass
def reboot(self, task):
pass
class FakeDriver(base.BaseDriver):
def __init__(self):
self.power = FakePower()
There are three categories of driver interfaces:
Drivers may run their own periodic tasks, i.e. actions run repeatedly after a certain amount of time. Such task is created by decorating a method on the driver itself or on any interface with driver_periodic_task decorator, e.g.
class FakePower(base.PowerInterface):
@base.driver_periodic_task(spacing=42)
def task(self, manager, context):
pass # do something
class FakeDriver(base.BaseDriver):
def __init__(self):
self.power = FakePower()
@base.driver_periodic_task(spacing=42)
def task2(self, manager, context):
pass # do something
@base.driver_periodic_task(parallel=False)
def blocking_task(self, manager, context):
pass # do something fast, this blocks other tasks from starting!
Here the spacing argument is a period in seconds for a given periodic task. For example ‘spacing=5’ means every 5 seconds.
The parallel argument may be passed to driver_periodic_task and defaults to True. If False, this task will be run in the periodic task loop, rather than a separate greenthread. This should be used with caution, as it will cause all other periodic tasks to be blocked from starting while the non-parallel task is running. Long running tasks, especially any tasks that make a remote call (to a BMC, HTTP, etc.) must be parallelized.
Note
By default periodic task names are derived from method names, so they should be unique within a Python module. Use name argument to driver_periodic_task to override automatically generated name.
Each Conductor registers itself in the database upon start-up, and periodically updates the timestamp of its record. Contained within this registration is a list of the drivers which this Conductor instance supports. This allows all services to maintain a consistent view of which Conductors and which drivers are available at all times.
Based on their respective driver, all nodes are mapped across the set of available Conductors using a consistent hashing algorithm. Node-specific tasks are dispatched from the API tier to the appropriate conductor using conductor-specific RPC channels. As Conductor instances join or leave the cluster, nodes may be remapped to different Conductors, thus triggering various driver actions such as take-over or clean-up.