summaryrefslogtreecommitdiff
path: root/doc/source/contributor/hardware_managers.rst
blob: f0f78f7d52b2e1dddd4459480b583147ec543717 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
.. _Hardware Managers:

Hardware Managers
=================

Hardware managers are how IPA supports multiple different hardware platforms
in the same agent. Any action performed on hardware can be overridden by
deploying your own hardware manager.

IPA ships with :doc:`GenericHardwareManager </admin/hardware_managers>`, which
implements basic cleaning and deployment methods compatible with most hardware.

.. warning::
   Some functionality inherent in the stock hardware manager cleaning methods
   may be useful in custom hardware managers, but should not be inherently
   expected to also work in custom managers. Examples of this are clustered
   filesystem protections, and cleaning method fallback logic. Custom hardware
   manager maintainers should be mindful when overriding the stock methods.

How are methods executed on HardwareManagers?
---------------------------------------------
Methods that modify hardware are dispatched to each hardware manager in
priority order. When a method is dispatched, if a hardware manager does not
have a method by that name or raises `IncompatibleHardwareMethodError`, IPA
continues on to the next hardware manager. Any hardware manager that returns
a result from the method call is considered a success and its return value
passed on to whatever dispatched the method. If the method is unable to run
successfully on any hardware managers, `HardwareManagerMethodNotFound` is
raised.

Why build a custom HardwareManager?
-----------------------------------
Custom hardware managers allow you to include hardware-specific tools, files
and cleaning steps in the Ironic Python Agent. For example, you could include a
BIOS flashing utility and BIOS file in a custom ramdisk. Your custom
hardware manager could expose a cleaning step that calls the flashing utility
and flashes the packaged BIOS version (or even download it from a tested web
server).

How can I build a custom HardwareManager?
-----------------------------------------
In general, custom HardwareManagers should subclass hardware.HardwareManager.
Subclassing hardware.GenericHardwareManager should only be considered if the
aim is to raise the priority of all methods of the GenericHardwareManager.
The only required method is evaluate_hardware_support(), which should return
one of the enums in hardware.HardwareSupport. Hardware support determines
which hardware manager is executed first for a given function (see: "`How are
methods executed on HardwareManagers?`_" for more info). Common methods you
may want to implement are ``list_hardware_info()``, to add additional hardware
the GenericHardwareManager is unable to identify and ``erase_devices()``, to
erase devices in ways other than ATA secure erase or shredding.

Some reusable functions are provided by :ironic-lib-doc:`ironic-lib
<reference/api/modules.html>`, its IPA is relatively stable.

The examples_ directory has two example hardware managers that can be copied
and adapter for your use case.

.. _examples: https://opendev.org/openstack/ironic-python-agent/src/branch/master/examples

Custom HardwareManagers and Cleaning
------------------------------------
One of the reasons to build a custom hardware manager is to expose extra steps
in :ironic-doc:`Ironic Cleaning </admin/cleaning.html>`. A node will perform
a set of cleaning steps any time the node is deleted by a tenant or moved from
``manageable`` state to ``available`` state. Ironic will query
IPA for a list of clean steps that should be executed on the node. IPA
will dispatch a call to `get_clean_steps()` on all available hardware managers
and then return the combined list to Ironic.

To expose extra clean steps, the custom hardware manager should have a function
named `get_clean_steps()` which returns a list of dictionaries. The
dictionaries should be in the form:

.. code-block:: python

    def get_clean_steps(self, node, ports):
        return [
            {
                # A function on the custom hardware manager
                'step': 'upgrade_firmware',
                # An integer priority. Largest priorities are executed first
                'priority': 10,
                # Should always be the deploy interface
                'interface': 'deploy',
                # Request the node to be rebooted out of band by Ironic when
                # the step completes successfully
                'reboot_requested': False
            }
        ]

Then, you should create functions which match each of the `step` keys in
the clean steps you return. The functions will take two parameters: `node`,
a dictionary representation of the Ironic node, and `ports`, a list of
dictionary representations of the Ironic ports attached to `node`.

When a clean step is executed in IPA, the `step` key will be sent to the
hardware managers in hardware support order, using
`hardware.dispatch_to_managers()`. For each hardware manager, if the manager
has a function matching the `step` key, it will be executed. If the function
returns a value (including None), that value is returned to Ironic and no
further managers are called. If the function raises
`IncompatibleHardwareMethodError`, the next manager will be called. If the
function raises any other exception, the command will be considered failed,
the command result's error message will be set to the exception's error
message, and no further managers will be called. An example step:

.. code-block:: python

    def upgrade_firmware(self, node, ports):
        if self._device_exists():
            # Do the upgrade
            return 'upgraded firmware'
        else:
            raise errors.IncompatibleHardwareMethodError()

If the step has args, you need to add them to argsinfo and provide the
function with extra parameters.

.. code-block:: python

    def get_clean_steps(self, node, ports):
        return [
            {
                # A function on the custom hardware manager
                'step': 'upgrade_firmware',
                # An integer priority. Largest priorities are executed first
                'priority': 10,
                # Should always be the deploy interface
                'interface': 'deploy',
                # Arguments that can be required or optional.
                'argsinfo': {
                    'firmware_url': {
                        'description': 'Url for firmware',
                        'required': True,
                    },
                }
                # Request the node to be rebooted out of band by Ironic when
                # the step completes successfully
                'reboot_requested': False
            }
        ]

.. code-block:: python

    def upgrade_firmware(self, node, ports, firmware_url):
        if self._device_exists():
            # Do the upgrade
            return 'upgraded firmware'
        else:
            raise errors.IncompatibleHardwareMethodError()

.. note::

    If two managers return steps with the same `step` key, the priority will
    be set to whichever manager has a higher hardware support level and then
    use the higher priority in the case of a tie.

In some cases, it may be necessary to create a customized cleaning step to
take a particular pattern of behavior. Those doing such work may want to
leverage file system safety checks, which are part of the stock hardware
managers.

.. code-block:: python

    def custom_erase_devices(self, node, ports):
        for dev in determine_my_devices_to_erase():
            hardware.safety_check_block_device(node, dev.name)
            my_special_cleaning(dev.name)

Custom HardwareManagers and Deploying
-------------------------------------

Starting with the Victoria release cycle, :ironic-doc:`deployment
<admin/node-deployment.html>` can be customized similarly to `cleaning
<Custom HardwareManagers and Cleaning>`_. A hardware manager can define *deploy
steps* that may be run during deployment by exposing a ``get_deploy_steps``
call.

There are two kinds of deploy steps:

#. Steps that need to be run automatically must have a non-zero priority and
   cannot take required arguments. For example:

   .. code-block:: python

    def get_deploy_steps(self, node, ports):
        return [
            {
                # A function on the custom hardware manager
                'step': 'upgrade_firmware',
                # An integer priority. Largest priorities are executed first
                'priority': 10,
                # Should always be the deploy interface
                'interface': 'deploy',
            }
        ]

    # A deploy steps looks the same as a clean step.
    def upgrade_firmware(self, node, ports):
        if self._device_exists():
            # Do the upgrade
            return 'upgraded firmware'
        else:
            raise errors.IncompatibleHardwareMethodError()

   Priority should be picked based on when exactly in the process the step will
   run. See :ironic-doc:`agent step priorities
   <admin/node-deployment.html#agent-steps>` for guidance.

#. Steps that will be requested via :ironic-doc:`deploy templates
   <admin/node-deployment.html#deploy-templates>` should have a priority of 0
   and may take both required and optional arguments that will be provided via
   the deploy templates. For example:

   .. code-block:: python

    def get_deploy_steps(self, node, ports):
        return [
            {
                # A function on the custom hardware manager
                'step': 'write_a_file',
                # Steps with priority 0 don't run by default.
                'priority': 0,
                # Should be the deploy interface, unless there is driver-side
                # support for another interface (as it is for RAID).
                'interface': 'deploy',
                # Arguments that can be required or optional.
                'argsinfo': {
                    'path': {
                        'description': 'Path to file',
                        'required': True,
                    },
                    'content': {
                        'description': 'Content of the file',
                        'required': True,
                    },
                    'mode': {
                        'description': 'Mode of the file, defaults to 0644',
                        'required': False,
                    },
                }
            }
        ]

    def write_a_file(self, node, ports, path, contents, mode=0o644):
        pass  # Mount the disk, write a file.

Versioning
~~~~~~~~~~
Each hardware manager has a name and a version. This version is used during
cleaning to ensure the same version of the agent is used to on a node through
the entire process. If the version changes, cleaning is restarted from the
beginning to ensure consistent cleaning operations and to make
updating the agent in production simpler.

You can set the version of your hardware manager by creating a class variable
named 'HARDWARE_MANAGER_VERSION', which should be a string. The default value
is '1.0'. You should change this version string any time you update your
hardware manager. You can also change the name your hardware manager presents
by creating a class variable called HARDWARE_MANAGER_NAME, which is a string.
The name defaults to the class name. Currently IPA only compares version as a
string; any version change whatsoever will induce cleaning to restart.

Priority
~~~~~~~~
A hardware manager has a single overall priority, which should be based on how
well it supports a given piece of hardware. At load time, IPA executes
`evaluate_hardware_support()` on each hardware manager. This method should
return an int representing hardware manager priority, based on what it detects
about the platform it's running on. Suggested values are included in the
`HardwareSupport` class. Returning a value of 0 aka `HardwareSupport.NONE`,
will prevent the hardware manager from being used. IPA will never ship a
hardware manager with a priority higher than 3, aka
`HardwareSupport.SERVICE_PROVIDER`.