summaryrefslogtreecommitdiff
path: root/ironic/drivers/modules/drac
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2014-09-02 15:02:10 +0000
committerGerrit Code Review <review@openstack.org>2014-09-02 15:02:10 +0000
commita6caeef289d92edc941720bc7442e58ae9036cc1 (patch)
treed6374adbd96d3061838fbed986f232adb61c3aa4 /ironic/drivers/modules/drac
parent936d2631ebb8fa8443a987e1e0519e51b5482d29 (diff)
parent2eb3c12ce3fb2b2eb4408367f2c950724a098d93 (diff)
downloadironic-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.py9
-rw-r--r--ironic/drivers/modules/drac/management.py339
-rw-r--r--ironic/drivers/modules/drac/resource_uris.py12
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')