~ubuntu-branches/ubuntu/vivid/ironic/vivid-updates

« back to all changes in this revision

Viewing changes to ironic/drivers/modules/ilo/inspect.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short
  • Date: 2015-03-30 11:14:57 UTC
  • mfrom: (1.2.6)
  • Revision ID: package-import@ubuntu.com-20150330111457-kr4ju3guf22m4vbz
Tags: 2015.1~b3-0ubuntu1
* New upstream release.
  + d/control: 
    - Align with upstream dependencies.
    - Add dh-python to build-dependencies.
    - Add psmisc as a dependency. (LP: #1358820)
  + d/p/fix-requirements.patch: Rediffed.
  + d/ironic-conductor.init.in: Fixed typos in LSB headers,
    thanks to JJ Asghar. (LP: #1429962)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
 
4
#
 
5
#      http://www.apache.org/licenses/LICENSE-2.0
 
6
#
 
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
 
11
# under the License.
 
12
"""
 
13
iLO Inspect Interface
 
14
"""
 
15
from oslo_utils import importutils
 
16
import six
 
17
 
 
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
 
28
 
 
29
ilo_error = importutils.try_import('proliantutils.exception')
 
30
 
 
31
LOG = logging.getLogger(__name__)
 
32
 
 
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'}
 
37
 
 
38
 
 
39
def _create_ports_if_not_exist(node, macs):
 
40
    """Create ironic ports for the mac addresses.
 
41
 
 
42
    Creates ironic ports for the mac addresses returned with inspection
 
43
    or as requested by operator.
 
44
 
 
45
    :param node: node object.
 
46
    :param macs: A dictionary of port numbers to mac addresses
 
47
                 returned by node inspection.
 
48
 
 
49
    """
 
50
    node_id = node.id
 
51
    sql_dbapi = dbapi.get_instance()
 
52
    for mac in macs.values():
 
53
        port_dict = {'address': mac, 'node_id': node_id}
 
54
 
 
55
        try:
 
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,
 
62
                         'node': node.uuid})
 
63
 
 
64
 
 
65
def _get_essential_properties(node, ilo_object):
 
66
    """Inspects the node and get essential scheduling properties
 
67
 
 
68
    :param node: node object.
 
69
    :param ilo_object: an instance of proliantutils.ilo.IloClient
 
70
    :raises: HardwareInspectionFailure if any of the properties values
 
71
             are missing.
 
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>
 
77
 
 
78
    """
 
79
    try:
 
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)
 
85
    return result
 
86
 
 
87
 
 
88
def _validate(node, data):
 
89
    """Validate the received value against the supported keys in ironic.
 
90
 
 
91
    :param node: node object.
 
92
    :param data: the dictionary received by querying server.
 
93
    :raises: HardwareInspectionFailure
 
94
 
 
95
    """
 
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'])
 
100
            if missing_keys:
 
101
                error = (_(
 
102
                    "Server didn't return the key(s): %(key)s") %
 
103
                    {'key': ', '.join(missing_keys)})
 
104
                raise exception.HardwareInspectionFailure(error=error)
 
105
        else:
 
106
            error = (_("Essential properties are expected to be in dictionary "
 
107
                      "format, received %(properties)s from node "
 
108
                      "%(node)s.") % {"properties": data['properties'],
 
109
                                      'node': node.uuid})
 
110
            raise exception.HardwareInspectionFailure(error=error)
 
111
    else:
 
112
        error = (_("The node %s didn't return 'properties' as the key with "
 
113
                   "inspection.") % node.uuid)
 
114
        raise exception.HardwareInspectionFailure(error=error)
 
115
 
 
116
    if data.get('macs'):
 
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)
 
122
    else:
 
123
        error = (_("The node %s didn't return 'macs' as the key with "
 
124
                   "inspection.") % node.uuid)
 
125
        raise exception.HardwareInspectionFailure(error=error)
 
126
 
 
127
 
 
128
def _create_supported_capabilities_dict(capabilities):
 
129
    """Creates a capabilities dictionary from supported capabilities in ironic.
 
130
 
 
131
    :param capabilities: a dictionary of capabilities as returned by the
 
132
                         hardware.
 
133
    :returns: a dictionary of the capabilities supported by ironic
 
134
              and returned by hardware.
 
135
 
 
136
    """
 
137
    valid_cap = {}
 
138
    for key in CAPABILITIES_KEYS.intersection(capabilities):
 
139
        valid_cap[key] = capabilities.get(key)
 
140
    return valid_cap
 
141
 
 
142
 
 
143
def _update_capabilities(node, new_capabilities):
 
144
    """Add or update a capability to the capabilities string.
 
145
 
 
146
    This method adds/updates a given property to the node capabilities
 
147
    string.
 
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'
 
151
 
 
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.
 
160
 
 
161
    """
 
162
    cap_dict = {}
 
163
    node_capabilities = node.properties.get('capabilities')
 
164
    if node_capabilities:
 
165
        try:
 
166
            cap_dict = dict(x.split(':', 1)
 
167
                            for x in node_capabilities.split(','))
 
168
        except ValueError:
 
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)
 
179
    else:
 
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)])
 
187
 
 
188
 
 
189
def _get_macs_for_desired_ports(node, macs):
 
190
    """Get the dict of MACs which are desired by the operator.
 
191
 
 
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.
 
195
 
 
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
 
201
    MAC address like:
 
202
    {'Port 1': 'aa:aa:aa:aa:aa:aa', 'Port 2': 'bb:bb:bb:bb:bb:bb'}
 
203
 
 
204
    Possible scenarios:
 
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
 
209
                                                invalid inputs.
 
210
    'inspect_ports' == 'none' : doesnt do any action with the
 
211
                                inspected mac addresses.
 
212
 
 
213
    This method is not called if 'inspect_ports' == 'none', hence the
 
214
    scenario is not covered under this method.
 
215
 
 
216
    :param node: a node object.
 
217
    :param macs: a dictionary of MAC addresses returned by the hardware
 
218
                 with inspection.
 
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']
 
224
 
 
225
    """
 
226
    driver_info = node.driver_info
 
227
    desired_macs = str(driver_info.get('inspect_ports'))
 
228
 
 
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
 
233
    else:
 
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(','))]
 
240
 
 
241
        # Check if the given input is valid or not. Return all the
 
242
        # requested macs.
 
243
        non_existing_ports = []
 
244
        for port_number in desired_macs_list:
 
245
            mac_address = macs.get(port_number)
 
246
            if mac_address:
 
247
                to_be_created_macs[port_number] = mac_address
 
248
            else:
 
249
                non_existing_ports.append(port_number)
 
250
 
 
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 "
 
254
                       "node %(node)s")
 
255
                       % {'ports': non_existing_ports, 'node': node.uuid})
 
256
            raise exception.HardwareInspectionFailure(error=error)
 
257
 
 
258
    return to_be_created_macs
 
259
 
 
260
 
 
261
def _get_capabilities(node, ilo_object):
 
262
    """inspects hardware and gets additional capabilities.
 
263
 
 
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'
 
268
               or None.
 
269
 
 
270
    """
 
271
    capabilities = None
 
272
    try:
 
273
        capabilities = ilo_object.get_server_capabilities()
 
274
    except ilo_error.IloError:
 
275
        LOG.debug(("Node %s did not return any additional capabilities."),
 
276
                   node.uuid)
 
277
 
 
278
    return capabilities
 
279
 
 
280
 
 
281
class IloInspect(base.InspectInterface):
 
282
 
 
283
    def get_properties(self):
 
284
        d = ilo_common.REQUIRED_PROPERTIES.copy()
 
285
        d.update(ilo_common.INSPECT_PROPERTIES)
 
286
        return d
 
287
 
 
288
    def validate(self, task):
 
289
        """Check that 'driver_info' contains required ILO credentials.
 
290
 
 
291
        Validates whether the 'driver_info' property of the supplied
 
292
        task's node contains the required credentials information.
 
293
 
 
294
        :param task: a task from TaskManager.
 
295
        :raises: InvalidParameterValue if required iLO parameters
 
296
                 are not valid.
 
297
        :raises: MissingParameterValue if a required parameter is missing.
 
298
        :raises: InvalidParameterValue if invalid input provided.
 
299
 
 
300
        """
 
301
        node = task.node
 
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})
 
315
 
 
316
    def inspect_hardware(self, task):
 
317
        """Inspect hardware to get the hardware properties.
 
318
 
 
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.
 
325
 
 
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.
 
331
 
 
332
        """
 
333
        power_turned_on = False
 
334
        ilo_object = ilo_common.get_ilo_object(task.node)
 
335
        try:
 
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")
 
339
                           % task.node.uuid)
 
340
            raise exception.IloOperationError(operation=operation,
 
341
                                              error=ilo_exception)
 
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
 
347
 
 
348
        # get the essential properties and update the node properties
 
349
        # with it.
 
350
 
 
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
 
359
 
 
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)
 
365
        if capabilities:
 
366
            valid_cap = _create_supported_capabilities_dict(capabilities)
 
367
            capabilities = _update_capabilities(task.node, valid_cap)
 
368
            if capabilities:
 
369
                node_properties['capabilities'] = capabilities
 
370
                task.node.properties = node_properties
 
371
 
 
372
        task.node.save()
 
373
 
 
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'.
 
381
 
 
382
        driver_info = task.node.driver_info
 
383
        if (driver_info['inspect_ports']).lower() != 'none':
 
384
            macs_input_given = (
 
385
                _get_macs_for_desired_ports(task.node, result['macs']))
 
386
 
 
387
            if macs_input_given:
 
388
                # Create ports only for the requested ports.
 
389
                _create_ports_if_not_exist(task.node, macs_input_given)
 
390
 
 
391
        LOG.debug(("Node properties for %(node)s are updated as "
 
392
                   "%(properties)s"),
 
393
                   {'properties': inspected_properties,
 
394
                    'node': task.node.uuid})
 
395
 
 
396
        LOG.info(_LI("Node %s inspected."), task.node.uuid)
 
397
        if power_turned_on:
 
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."),
 
401
                         task.node.uuid)
 
402
        return states.MANAGEABLE