1
# Licensed under the Apache License, Version 2.0 (the "License"); you may
2
# not use this file except in compliance with the License. You may obtain
3
# a copy of the License at
5
# http://www.apache.org/licenses/LICENSE-2.0
7
# Unless required by applicable law or agreed to in writing, software
8
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
# License for the specific language governing permissions and limitations
15
from oslo_utils import importutils
18
from ironic.common import exception
19
from ironic.common.i18n import _
20
from ironic.common.i18n import _LI
21
from ironic.common.i18n import _LW
22
from ironic.common import states
23
from ironic.conductor import utils as conductor_utils
24
from ironic.db import api as dbapi
25
from ironic.drivers import base
26
from ironic.drivers.modules.ilo import common as ilo_common
27
from ironic.openstack.common import log as logging
29
ilo_error = importutils.try_import('proliantutils.exception')
31
LOG = logging.getLogger(__name__)
33
ESSENTIAL_PROPERTIES_KEYS = {'memory_mb', 'local_gb', 'cpus', 'cpu_arch'}
34
CAPABILITIES_KEYS = {'BootMode', 'secure_boot', 'rom_firmware_version',
35
'ilo_firmware_version', 'server_model', 'max_raid_level',
36
'pci_gpu_devices', 'sr_iov_devices', 'nic_capacity'}
39
def _create_ports_if_not_exist(node, macs):
40
"""Create ironic ports for the mac addresses.
42
Creates ironic ports for the mac addresses returned with inspection
43
or as requested by operator.
45
:param node: node object.
46
:param macs: A dictionary of port numbers to mac addresses
47
returned by node inspection.
51
sql_dbapi = dbapi.get_instance()
52
for mac in macs.values():
53
port_dict = {'address': mac, 'node_id': node_id}
56
sql_dbapi.create_port(port_dict)
57
LOG.info(_LI("Port created for MAC address %(address)s for node "
58
"%(node)s"), {'address': mac, 'node': node.uuid})
59
except exception.MACAlreadyExists:
60
LOG.warn(_LW("Port already exists for MAC address %(address)s "
61
"for node %(node)s"), {'address': mac,
65
def _get_essential_properties(node, ilo_object):
66
"""Inspects the node and get essential scheduling properties
68
:param node: node object.
69
:param ilo_object: an instance of proliantutils.ilo.IloClient
70
:raises: HardwareInspectionFailure if any of the properties values
72
:returns: The dictionary containing properties and MAC data.
73
The dictionary possible keys are 'properties' and 'macs'.
74
The 'properties' should contain keys as in
75
ESSENTIAL_PROPERTIES_KEYS. The 'macs' is a dictionary
76
containing key:value pairs of <port_numbers:mac_addresses>
80
# Retrieve the mandatory properties from hardware
81
result = ilo_object.get_essential_properties()
82
except ilo_error.IloError as e:
83
raise exception.HardwareInspectionFailure(error=e)
84
_validate(node, result)
88
def _validate(node, data):
89
"""Validate the received value against the supported keys in ironic.
91
:param node: node object.
92
:param data: the dictionary received by querying server.
93
:raises: HardwareInspectionFailure
96
if data.get('properties'):
97
if isinstance(data['properties'], dict):
98
valid_keys = ESSENTIAL_PROPERTIES_KEYS
99
missing_keys = valid_keys - set(data['properties'])
102
"Server didn't return the key(s): %(key)s") %
103
{'key': ', '.join(missing_keys)})
104
raise exception.HardwareInspectionFailure(error=error)
106
error = (_("Essential properties are expected to be in dictionary "
107
"format, received %(properties)s from node "
108
"%(node)s.") % {"properties": data['properties'],
110
raise exception.HardwareInspectionFailure(error=error)
112
error = (_("The node %s didn't return 'properties' as the key with "
113
"inspection.") % node.uuid)
114
raise exception.HardwareInspectionFailure(error=error)
117
if not isinstance(data['macs'], dict):
118
error = (_("Node %(node)s didn't return MACs %(macs)s "
119
"in dictionary format.")
120
% {"macs": data['macs'], 'node': node.uuid})
121
raise exception.HardwareInspectionFailure(error=error)
123
error = (_("The node %s didn't return 'macs' as the key with "
124
"inspection.") % node.uuid)
125
raise exception.HardwareInspectionFailure(error=error)
128
def _create_supported_capabilities_dict(capabilities):
129
"""Creates a capabilities dictionary from supported capabilities in ironic.
131
:param capabilities: a dictionary of capabilities as returned by the
133
:returns: a dictionary of the capabilities supported by ironic
134
and returned by hardware.
138
for key in CAPABILITIES_KEYS.intersection(capabilities):
139
valid_cap[key] = capabilities.get(key)
143
def _update_capabilities(node, new_capabilities):
144
"""Add or update a capability to the capabilities string.
146
This method adds/updates a given property to the node capabilities
148
Currently the capabilities are recorded as a string in
149
properties/capabilities of a Node. It's of the below format:
150
properties/capabilities='boot_mode:bios,boot_option:local'
152
:param node: Node object.
153
:param new_capabilities: the dictionary of capabilities returned
154
by baremetal with inspection.
155
:returns: The capability string after adding/updating the
156
node_capabilities with new_capabilities
157
:raises: InvalidParameterValue, if node_capabilities is malformed.
158
:raises: HardwareInspectionFailure, if inspected capabilities
159
are not in dictionary format.
163
node_capabilities = node.properties.get('capabilities')
164
if node_capabilities:
166
cap_dict = dict(x.split(':', 1)
167
for x in node_capabilities.split(','))
169
# Capabilities can be filled by operator. ValueError can
170
# occur in malformed capabilities like:
171
# properties/capabilities='boot_mode:bios,boot_option'.
172
msg = (_("Node %(node)s has invalid capabilities string "
173
"%(capabilities), unable to modify the node "
174
"properties['capabilities'] string")
175
% {'node': node.uuid, 'capabilities': node_capabilities})
176
raise exception.InvalidParameterValue(msg)
177
if isinstance(new_capabilities, dict):
178
cap_dict.update(new_capabilities)
180
msg = (_("The expected format of capabilities from inspection "
181
"is dictionary while node %(node)s returned "
182
"%(capabilities)s.") % {'node': node.uuid,
183
'capabilities': new_capabilities})
184
raise exception.HardwareInspectionFailure(error=msg)
185
return ','.join(['%(key)s:%(value)s' % {'key': key, 'value': value}
186
for key, value in six.iteritems(cap_dict)])
189
def _get_macs_for_desired_ports(node, macs):
190
"""Get the dict of MACs which are desired by the operator.
192
Get the MACs for desired ports.
193
Returns a dictionary of MACs associated with the ports specified
194
in the node's driver_info/inspect_ports.
196
The driver_info field is expected to be populated with
197
comma-separated port numbers like driver_info/inspect_ports='1,2'.
198
In this case the inspection is expected to create ironic ports
199
only for these two ports.
200
The proliantutils is expected to return key value pair for each
202
{'Port 1': 'aa:aa:aa:aa:aa:aa', 'Port 2': 'bb:bb:bb:bb:bb:bb'}
205
'inspect_ports' == 'all' : creates ports for all inspected MACs
206
'inspect_ports' == <valid_port_numbers>: creates ports for
207
requested port numbers.
208
'inspect_ports' == <mix_of_valid_invalid> : raise error for
210
'inspect_ports' == 'none' : doesnt do any action with the
211
inspected mac addresses.
213
This method is not called if 'inspect_ports' == 'none', hence the
214
scenario is not covered under this method.
216
:param node: a node object.
217
:param macs: a dictionary of MAC addresses returned by the hardware
219
:returns: a dictionary of port numbers and MAC addresses with only
220
the MACs requested by operator in
221
node.driver_info['inspect_ports']
222
:raises: HardwareInspectionFailure for the non-existing ports
223
requested in node.driver_info['inspect_ports']
226
driver_info = node.driver_info
227
desired_macs = str(driver_info.get('inspect_ports'))
229
# If the operator has given 'all' just return all the macs
230
# returned by inspection.
231
if desired_macs.lower() == 'all':
232
to_be_created_macs = macs
234
to_be_created_macs = {}
235
# The list should look like ['Port 1', 'Port 2'] as
236
# iLO returns port numbers like this.
237
desired_macs_list = [
238
'Port %s' % port_number
239
for port_number in (desired_macs.split(','))]
241
# Check if the given input is valid or not. Return all the
243
non_existing_ports = []
244
for port_number in desired_macs_list:
245
mac_address = macs.get(port_number)
247
to_be_created_macs[port_number] = mac_address
249
non_existing_ports.append(port_number)
251
# It is possible that operator has given a wrong input by mistake.
252
if non_existing_ports:
253
error = (_("Could not find requested ports %(ports)s on the "
255
% {'ports': non_existing_ports, 'node': node.uuid})
256
raise exception.HardwareInspectionFailure(error=error)
258
return to_be_created_macs
261
def _get_capabilities(node, ilo_object):
262
"""inspects hardware and gets additional capabilities.
264
:param node: Node object.
265
:param ilo_object: an instance of ilo drivers.
266
:returns : a string of capabilities like
267
'key1:value1,key2:value2,key3:value3'
273
capabilities = ilo_object.get_server_capabilities()
274
except ilo_error.IloError:
275
LOG.debug(("Node %s did not return any additional capabilities."),
281
class IloInspect(base.InspectInterface):
283
def get_properties(self):
284
d = ilo_common.REQUIRED_PROPERTIES.copy()
285
d.update(ilo_common.INSPECT_PROPERTIES)
288
def validate(self, task):
289
"""Check that 'driver_info' contains required ILO credentials.
291
Validates whether the 'driver_info' property of the supplied
292
task's node contains the required credentials information.
294
:param task: a task from TaskManager.
295
:raises: InvalidParameterValue if required iLO parameters
297
:raises: MissingParameterValue if a required parameter is missing.
298
:raises: InvalidParameterValue if invalid input provided.
302
driver_info = ilo_common.parse_driver_info(node)
303
if 'inspect_ports' not in driver_info:
304
raise exception.MissingParameterValue(_(
305
"Missing 'inspect_ports' parameter in node's driver_info."))
306
value = driver_info['inspect_ports']
307
if (value.lower() != 'all' and value.lower() != 'none'
308
and not all(s.isdigit() for s in value.split(','))):
309
raise exception.InvalidParameterValue(_(
310
"inspect_ports can accept either comma separated "
311
"port numbers, or a single port number, or 'all' "
312
"or 'none'. %(value)s given for node %(node)s "
313
"driver_info['inspect_ports']")
314
% {'value': value, 'node': node})
316
def inspect_hardware(self, task):
317
"""Inspect hardware to get the hardware properties.
319
Inspects hardware to get the essential and additional hardware
320
properties. It fails if any of the essential properties
321
are not received from the node or if 'inspect_ports' is
322
not provided in driver_info.
323
It doesnt fail if node fails to return any capabilities as
324
the capabilities differ from hardware to hardware mostly.
326
:param task: a TaskManager instance.
327
:raises: HardwareInspectionFailure if essential properties
328
could not be retrieved successfully.
329
:raises: IloOperationError if system fails to get power state.
330
:returns: The resulting state of inspection.
333
power_turned_on = False
334
ilo_object = ilo_common.get_ilo_object(task.node)
336
state = task.driver.power.get_power_state(task)
337
except exception.IloOperationError as ilo_exception:
338
operation = (_("Inspecting hardware (get_power_state) on %s")
340
raise exception.IloOperationError(operation=operation,
342
if state != states.POWER_ON:
343
LOG.info(_LI("The node %s is not powered on. Powering on the "
344
"node for inspection."), task.node.uuid)
345
conductor_utils.node_power_action(task, states.POWER_ON)
346
power_turned_on = True
348
# get the essential properties and update the node properties
351
inspected_properties = {}
352
result = _get_essential_properties(task.node, ilo_object)
353
properties = result['properties']
354
for known_property in ESSENTIAL_PROPERTIES_KEYS:
355
inspected_properties[known_property] = properties[known_property]
356
node_properties = task.node.properties
357
node_properties.update(inspected_properties)
358
task.node.properties = node_properties
360
# Inspect the hardware for additional hardware capabilities.
361
# Since additional hardware capabilities may not apply to all the
362
# hardwares, the method inspect_hardware() doesn't raise an error
363
# for these capabilities.
364
capabilities = _get_capabilities(task.node, ilo_object)
366
valid_cap = _create_supported_capabilities_dict(capabilities)
367
capabilities = _update_capabilities(task.node, valid_cap)
369
node_properties['capabilities'] = capabilities
370
task.node.properties = node_properties
374
# Get the desired node inputs from the driver_info and create ports
375
# as requested. It doesnt delete the ports because there is
376
# no way for the operator to know which all MACs are associated
377
# with the node and which are not. The proliantutils can
378
# return only embedded NICs mac addresses and not the STANDUP NIC
379
# cards. The port creation code is not excercised if
380
# 'inspect_ports' == 'none'.
382
driver_info = task.node.driver_info
383
if (driver_info['inspect_ports']).lower() != 'none':
385
_get_macs_for_desired_ports(task.node, result['macs']))
388
# Create ports only for the requested ports.
389
_create_ports_if_not_exist(task.node, macs_input_given)
391
LOG.debug(("Node properties for %(node)s are updated as "
393
{'properties': inspected_properties,
394
'node': task.node.uuid})
396
LOG.info(_LI("Node %s inspected."), task.node.uuid)
398
conductor_utils.node_power_action(task, states.POWER_OFF)
399
LOG.info(_LI("The node %s was powered on for inspection. "
400
"Powered off the node as inspection completed."),
402
return states.MANAGEABLE