diff options
author | Jenkins <jenkins@review.openstack.org> | 2014-09-02 15:02:10 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2014-09-02 15:02:10 +0000 |
commit | a6caeef289d92edc941720bc7442e58ae9036cc1 (patch) | |
tree | d6374adbd96d3061838fbed986f232adb61c3aa4 /ironic/drivers/modules/drac | |
parent | 936d2631ebb8fa8443a987e1e0519e51b5482d29 (diff) | |
parent | 2eb3c12ce3fb2b2eb4408367f2c950724a098d93 (diff) | |
download | ironic-a6caeef289d92edc941720bc7442e58ae9036cc1.tar.gz |
Merge "Implements the DRAC ManagementInterface for get/set boot device"
Diffstat (limited to 'ironic/drivers/modules/drac')
-rw-r--r-- | ironic/drivers/modules/drac/common.py | 9 | ||||
-rw-r--r-- | ironic/drivers/modules/drac/management.py | 339 | ||||
-rw-r--r-- | ironic/drivers/modules/drac/resource_uris.py | 12 |
3 files changed, 358 insertions, 2 deletions
diff --git a/ironic/drivers/modules/drac/common.py b/ironic/drivers/modules/drac/common.py index dd61d615d..9413ed078 100644 --- a/ironic/drivers/modules/drac/common.py +++ b/ironic/drivers/modules/drac/common.py @@ -101,15 +101,20 @@ def get_wsman_client(node): return client -def find_xml(doc, item, namespace): +def find_xml(doc, item, namespace, find_all=False): """Find the first or all elements in a ElementTree object. :param doc: the element tree object. :param item: the element name. :param namespace: the namespace of the element. - :returns: The element object or None if the element is not found. + :param find_all: Boolean value, if True find all elements, if False + find only the first one. Defaults to False. + :returns: The element object if find_all is False or a list of + element objects if find_all is True. """ query = ('.//{%(namespace)s}%(item)s' % {'namespace': namespace, 'item': item}) + if find_all: + return doc.findall(query) return doc.find(query) diff --git a/ironic/drivers/modules/drac/management.py b/ironic/drivers/modules/drac/management.py new file mode 100644 index 000000000..ede5bf4df --- /dev/null +++ b/ironic/drivers/modules/drac/management.py @@ -0,0 +1,339 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2014 Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +DRAC Management Driver +""" + +from ironic.common import boot_devices +from ironic.common import exception +from ironic.common import i18n +from ironic.drivers import base +from ironic.drivers.modules.drac import common as drac_common +from ironic.drivers.modules.drac import resource_uris +from ironic.openstack.common import excutils +from ironic.openstack.common import importutils +from ironic.openstack.common import log as logging + +pywsman = importutils.try_import('pywsman') + +LOG = logging.getLogger(__name__) + +_ = i18n._ +_LE = i18n._LE + +_BOOT_DEVICES_MAP = { + boot_devices.DISK: 'HardDisk', + boot_devices.PXE: 'NIC', + boot_devices.CDROM: 'Optical', +} + +# IsNext constants +PERSISTENT = '1' # is the next boot config the system will use + +NOT_NEXT = '2' # is not the next boot config the system will use + +ONE_TIME_BOOT = '3' # is the next boot config the system will use, + # one time boot only + +# ReturnValue constants +RET_SUCCESS = '0' +RET_ERROR = '2' +RET_CREATED = '4096' + +FILTER_DIALECT = 'http://schemas.dmtf.org/wbem/cql/1/dsp0202.pdf' + + +def _get_next_boot_mode(node): + """Get the next boot mode. + + To see a list of supported boot modes see: http://goo.gl/aEsvUH + (Section 7.2) + + :param node: an ironic node object. + :raises: DracClientError on an error from pywsman library. + :returns: a dictionary containing: + + :instance_id: the instance id of the boot device. + :is_next: whether it's the next device to boot or not. One of + PERSISTENT, NOT_NEXT, ONE_TIME_BOOT constants. + + """ + client = drac_common.get_wsman_client(node) + options = pywsman.ClientOptions() + filter = pywsman.Filter() + filter_query = ('select * from DCIM_BootConfigSetting where IsNext=%s ' + 'or IsNext=%s' % (PERSISTENT, ONE_TIME_BOOT)) + filter.simple(FILTER_DIALECT, filter_query) + + try: + doc = client.wsman_enumerate(resource_uris.DCIM_BootConfigSetting, + options, filter) + except exception.DracClientError as exc: + with excutils.save_and_reraise_exception(): + LOG.error(_LE('DRAC driver failed to get next boot mode for ' + 'node %(node_uuid)s. Reason: %(error)s.'), + {'node_uuid': node.uuid, 'error': exc}) + + items = drac_common.find_xml(doc, 'DCIM_BootConfigSetting', + resource_uris.DCIM_BootConfigSetting, + find_all=True) + + # This list will have 2 items maximum, one for the persistent element + # and another one for the OneTime if set + boot_mode = None + for i in items: + instance_id = drac_common.find_xml(i, 'InstanceID', + resource_uris.DCIM_BootConfigSetting).text + is_next = drac_common.find_xml(i, 'IsNext', + resource_uris.DCIM_BootConfigSetting).text + + boot_mode = {'instance_id': instance_id, 'is_next': is_next} + # If OneTime is set we should return it, because that's + # where the next boot device is + if is_next == ONE_TIME_BOOT: + break + + return boot_mode + + +def _create_config_job(node): + """Create a configuration job. + + This method is used to apply the pending values created by + set_boot_device(). + + :param node: an ironic node object. + :raises: DracClientError on an error from pywsman library. + :raises: DracConfigJobCreationError on an error when creating the job. + + """ + client = drac_common.get_wsman_client(node) + options = pywsman.ClientOptions() + options.add_selector('CreationClassName', 'DCIM_BIOSService') + options.add_selector('Name', 'DCIM:BIOSService') + options.add_selector('SystemCreationClassName', 'DCIM_ComputerSystem') + options.add_selector('SystemName', 'DCIM:ComputerSystem') + options.add_property('Target', 'BIOS.Setup.1-1') + options.add_property('ScheduledStartTime', 'TIME_NOW') + doc = client.wsman_invoke(resource_uris.DCIM_BIOSService, + options, 'CreateTargetedConfigJob') + return_value = drac_common.find_xml(doc, 'ReturnValue', + resource_uris.DCIM_BIOSService).text + # NOTE(lucasagomes): Possible return values are: RET_ERROR for error + # or RET_CREATED job created (but changes will be + # applied after the reboot) + # Boot Management Documentation: http://goo.gl/aEsvUH (Section 8.4) + if return_value == RET_ERROR: + error_message = drac_common.find_xml(doc, 'Message', + resource_uris.DCIM_BIOSService).text + raise exception.DracConfigJobCreationError(error=error_message) + + +def _check_for_config_job(node): + """Check if a configuration job is already created. + + :param node: an ironic node object. + :raises: DracClientError on an error from pywsman library. + :raises: DracConfigJobCreationError if the job is already created. + + """ + client = drac_common.get_wsman_client(node) + options = pywsman.ClientOptions() + try: + doc = client.wsman_enumerate(resource_uris.DCIM_LifecycleJob, options) + except exception.DracClientError as exc: + with excutils.save_and_reraise_exception(): + LOG.error(_LE('DRAC driver failed to list the configuration jobs ' + 'for node %(node_uuid)s. Reason: %(error)s.'), + {'node_uuid': node.uuid, 'error': exc}) + + items = drac_common.find_xml(doc, 'DCIM_LifecycleJob', + resource_uris.DCIM_LifecycleJob, + find_all=True) + for i in items: + name = drac_common.find_xml(i, 'Name', resource_uris.DCIM_LifecycleJob) + if 'BIOS.Setup.1-1' not in name.text: + continue + + job_status = drac_common.find_xml(i, 'JobStatus', + resource_uris.DCIM_LifecycleJob).text + # If job is already completed or failed we can + # create another one. + # Job Control Documentation: http://goo.gl/o1dDD3 (Section 7.2.3.2) + if job_status.lower() not in ('completed', 'failed'): + job_id = drac_common.find_xml(i, 'InstanceID', + resource_uris.DCIM_LifecycleJob).text + reason = (_('Another job with ID "%s" is already created ' + 'to configure the BIOS. Wait until existing job ' + 'is completed or is cancelled') % job_id) + raise exception.DracConfigJobCreationError(error=reason) + + +class DracManagement(base.ManagementInterface): + + def get_properties(self): + return drac_common.COMMON_PROPERTIES + + def validate(self, task): + """Validate the driver-specific info supplied. + + This method validates whether the 'driver_info' property of the + supplied node contains the required information for this driver to + manage the node. + + :param task: a TaskManager instance containing the node to act on. + :raises: InvalidParameterValue if required driver_info attribute + is missing or invalid on the node. + + """ + return drac_common.parse_driver_info(task.node) + + def get_supported_boot_devices(self): + """Get a list of the supported boot devices. + + :returns: A list with the supported boot devices defined + in :mod:`ironic.common.boot_devices`. + + """ + return list(_BOOT_DEVICES_MAP.keys()) + + def set_boot_device(self, task, device, persistent=False): + """Set the boot device for a node. + + Set the boot device to use on next reboot of the node. + + :param task: a task from TaskManager. + :param device: the boot device, one of + :mod:`ironic.common.boot_devices`. + :param persistent: Boolean value. True if the boot device will + persist to all future boots, False if not. + Default: False. + :raises: DracClientError on an error from pywsman library. + :raises: InvalidParameterValue if an invalid boot device is + specified. + :raises: DracConfigJobCreationError on an error when creating the job. + + """ + # Check for an existing configuration job + _check_for_config_job(task.node) + + client = drac_common.get_wsman_client(task.node) + options = pywsman.ClientOptions() + filter = pywsman.Filter() + filter_query = ("select * from DCIM_BootSourceSetting where " + "InstanceID like '%%#%s%%'" % + _BOOT_DEVICES_MAP[device]) + filter.simple(FILTER_DIALECT, filter_query) + + try: + doc = client.wsman_enumerate(resource_uris.DCIM_BootSourceSetting, + options, filter) + except exception.DracClientError as exc: + with excutils.save_and_reraise_exception(): + LOG.error(_LE('DRAC driver failed to set the boot device ' + 'for node %(node_uuid)s. Can\'t find the ID ' + 'for the %(device)s type. Reason: %(error)s.'), + {'node_uuid': task.node.uuid, 'error': exc, + 'device': device}) + + instance_id = drac_common.find_xml(doc, 'InstanceID', + resource_uris.DCIM_BootSourceSetting).text + + source = 'OneTime' + if persistent: + source = drac_common.find_xml(doc, 'BootSourceType', + resource_uris.DCIM_BootSourceSetting).text + + # NOTE(lucasagomes): Don't ask me why 'BootSourceType' is set + # for 'InstanceID' and 'InstanceID' is set for 'source'! You + # know enterprisey... + options = pywsman.ClientOptions() + options.add_selector('InstanceID', source) + options.add_property('source', instance_id) + doc = client.wsman_invoke(resource_uris.DCIM_BootConfigSetting, + options, 'ChangeBootOrderByInstanceID') + return_value = drac_common.find_xml(doc, 'ReturnValue', + resource_uris.DCIM_BootConfigSetting).text + # NOTE(lucasagomes): Possible return values are: RET_ERROR for error, + # RET_SUCCESS for success or RET_CREATED job + # created (but changes will be applied after + # the reboot) + # Boot Management Documentation: http://goo.gl/aEsvUH (Section 8.7) + if return_value == RET_ERROR: + error_message = drac_common.find_xml(doc, 'Message', + resource_uris.DCIM_BootConfigSetting).text + raise exception.DracOperationError(operation='set_boot_device', + error=error_message) + # Create a configuration job + _create_config_job(task.node) + + def get_boot_device(self, task): + """Get the current boot device for a node. + + Returns the current boot device of the node. + + :param task: a task from TaskManager. + :raises: DracClientError on an error from pywsman library. + :returns: a dictionary containing: + + :boot_device: the boot device, one of + :mod:`ironic.common.boot_devices` or None if it is unknown. + :persistent: Whether the boot device will persist to all + future boots or not, None if it is unknown. + + """ + client = drac_common.get_wsman_client(task.node) + boot_mode = _get_next_boot_mode(task.node) + + persistent = boot_mode['is_next'] == PERSISTENT + instance_id = boot_mode['instance_id'] + + options = pywsman.ClientOptions() + filter = pywsman.Filter() + filter_query = ('select * from DCIM_BootSourceSetting where ' + 'PendingAssignedSequence=0 and ' + 'BootSourceType="%s"' % instance_id) + filter.simple(FILTER_DIALECT, filter_query) + + try: + doc = client.wsman_enumerate(resource_uris.DCIM_BootSourceSetting, + options, filter) + except exception.DracClientError as exc: + with excutils.save_and_reraise_exception(): + LOG.error(_LE('DRAC driver failed to get the current boot ' + 'device for node %(node_uuid)s. ' + 'Reason: %(error)s.'), + {'node_uuid': task.node.uuid, 'error': exc}) + + instance_id = drac_common.find_xml(doc, 'InstanceID', + resource_uris.DCIM_BootSourceSetting).text + boot_device = next((key for (key, value) in _BOOT_DEVICES_MAP.items() + if value in instance_id), None) + return {'boot_device': boot_device, 'persistent': persistent} + + def get_sensors_data(self, task): + """Get sensors data. + + :param task: a TaskManager instance. + :raises: FailedToGetSensorData when getting the sensor data fails. + :raises: FailedToParseSensorData when parsing sensor data fails. + :returns: returns a consistent format dict of sensor data grouped by + sensor type, which can be processed by Ceilometer. + + """ + raise NotImplementedError() diff --git a/ironic/drivers/modules/drac/resource_uris.py b/ironic/drivers/modules/drac/resource_uris.py index 69b1549c5..ea7445774 100644 --- a/ironic/drivers/modules/drac/resource_uris.py +++ b/ironic/drivers/modules/drac/resource_uris.py @@ -18,3 +18,15 @@ WS-Man API. DCIM_ComputerSystem = ('http://schemas.dell.com/wbem/wscim/1/cim-schema/2' '/DCIM_ComputerSystem') + +DCIM_BootSourceSetting = ('http://schemas.dell.com/wbem/wscim/1/cim-schema/2/' + 'DCIM_BootSourceSetting') + +DCIM_BootConfigSetting = ('http://schemas.dell.com/wbem/wscim/1/cim-schema/2/' + 'DCIM_BootConfigSetting') + +DCIM_BIOSService = ('http://schemas.dell.com/wbem/wscim/1/cim-schema/2/' + 'DCIM_BIOSService') + +DCIM_LifecycleJob = ('http://schemas.dell.com/wbem/wscim/1/cim-schema/2/' + 'DCIM_LifecycleJob') |