Merge "Implements the DRAC ManagementInterface for get/set boot device"

This commit is contained in:
Jenkins 2014-09-02 15:02:10 +00:00 committed by Gerrit Code Review
commit a6caeef289
11 changed files with 726 additions and 23 deletions

View File

@ -427,6 +427,11 @@ class DracOperationError(IronicException):
message = _('DRAC %(operation)s failed. Reason: %(error)s') message = _('DRAC %(operation)s failed. Reason: %(error)s')
class DracConfigJobCreationError(DracOperationError):
message = _('DRAC failed to create a configuration job. '
'Reason: %(error)s')
class FailedToGetSensorData(IronicException): class FailedToGetSensorData(IronicException):
message = _("Failed to get sensor data for node %(node)s. " message = _("Failed to get sensor data for node %(node)s. "
"Error: %(error)s") "Error: %(error)s")

View File

@ -17,15 +17,14 @@ DRAC Driver for remote system management using Dell Remote Access Card.
from ironic.common import exception from ironic.common import exception
from ironic.common.i18n import _ from ironic.common.i18n import _
from ironic.drivers import base from ironic.drivers import base
from ironic.drivers.modules.drac import management
from ironic.drivers.modules.drac import power from ironic.drivers.modules.drac import power
from ironic.drivers.modules import ipmitool
from ironic.drivers.modules import pxe from ironic.drivers.modules import pxe
from ironic.openstack.common import importutils from ironic.openstack.common import importutils
class PXEDracDriver(base.BaseDriver): class PXEDracDriver(base.BaseDriver):
"""Drac driver using PXE for deploy."""
"""Drac driver using PXE for deploy and ipmitool for management."""
def __init__(self): def __init__(self):
if not importutils.try_import('pywsman'): if not importutils.try_import('pywsman'):
@ -35,6 +34,4 @@ class PXEDracDriver(base.BaseDriver):
self.power = power.DracPower() self.power = power.DracPower()
self.deploy = pxe.PXEDeploy() self.deploy = pxe.PXEDeploy()
# NOTE(ifarkas): using ipmitool is a temporary solution. It will be self.management = management.DracManagement()
# replaced by the DracManagement interface.
self.management = ipmitool.IPMIManagement()

View File

@ -23,6 +23,7 @@ from ironic.common import exception
from ironic.common.i18n import _ from ironic.common.i18n import _
from ironic.drivers import base from ironic.drivers import base
from ironic.drivers.modules import agent from ironic.drivers.modules import agent
from ironic.drivers.modules.drac import management as drac_mgmt
from ironic.drivers.modules.drac import power as drac_power from ironic.drivers.modules.drac import power as drac_power
from ironic.drivers.modules import fake from ironic.drivers.modules import fake
from ironic.drivers.modules import iboot from ironic.drivers.modules import iboot
@ -143,3 +144,4 @@ class FakeDracDriver(base.BaseDriver):
self.power = drac_power.DracPower() self.power = drac_power.DracPower()
self.deploy = fake.FakeDeploy() self.deploy = fake.FakeDeploy()
self.management = drac_mgmt.DracManagement()

View File

@ -101,15 +101,20 @@ def get_wsman_client(node):
return client 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. """Find the first or all elements in a ElementTree object.
:param doc: the element tree object. :param doc: the element tree object.
:param item: the element name. :param item: the element name.
:param namespace: the namespace of the element. :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, query = ('.//{%(namespace)s}%(item)s' % {'namespace': namespace,
'item': item}) 'item': item})
if find_all:
return doc.findall(query)
return doc.find(query) return doc.find(query)

View File

@ -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()

View File

@ -18,3 +18,15 @@ WS-Man API.
DCIM_ComputerSystem = ('http://schemas.dell.com/wbem/wscim/1/cim-schema/2' DCIM_ComputerSystem = ('http://schemas.dell.com/wbem/wscim/1/cim-schema/2'
'/DCIM_ComputerSystem') '/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')

View File

@ -54,9 +54,9 @@ class DracClientTestCase(base.TestCase):
def test_wsman_enumerate_with_additional_pull(self, mock_client_pywsman): def test_wsman_enumerate_with_additional_pull(self, mock_client_pywsman):
mock_root = mock.Mock() mock_root = mock.Mock()
mock_root.string.side_effect = [test_utils.build_soap_xml( mock_root.string.side_effect = [test_utils.build_soap_xml(
{'item1': 'test1'}), [{'item1': 'test1'}]),
test_utils.build_soap_xml( test_utils.build_soap_xml(
{'item2': 'test2'})] [{'item2': 'test2'}])]
mock_xml = mock.Mock() mock_xml = mock.Mock()
mock_xml.root.return_value = mock_root mock_xml.root.return_value = mock_root
mock_xml.context.side_effect = [42, 42, None] mock_xml.context.side_effect = [42, 42, None]

View File

@ -17,6 +17,8 @@ Test class for common methods used by DRAC modules.
from xml.etree import ElementTree from xml.etree import ElementTree
from testtools.matchers import HasLength
from ironic.common import exception from ironic.common import exception
from ironic.drivers.modules.drac import common as drac_common from ironic.drivers.modules.drac import common as drac_common
from ironic.openstack.common import context from ironic.openstack.common import context
@ -116,3 +118,23 @@ class DracCommonMethodsTestCase(base.TestCase):
result = drac_common.find_xml(test_doc, 'test_element', namespace) result = drac_common.find_xml(test_doc, 'test_element', namespace)
self.assertEqual(value, result.text) self.assertEqual(value, result.text)
def test_find_xml_find_all(self):
namespace = 'http://fake'
value1 = 'fake_value1'
value2 = 'fake_value2'
test_doc = ElementTree.fromstring("""<Envelope xmlns:ns1="%(ns)s">
<Body>
<ns1:test_element>%(value1)s</ns1:test_element>
<ns1:cat>meow</ns1:cat>
<ns1:test_element>%(value2)s</ns1:test_element>
<ns1:dog>bark</ns1:dog>
</Body>
</Envelope>""" % {'ns': namespace, 'value1': value1,
'value2': value2})
result = drac_common.find_xml(test_doc, 'test_element',
namespace, find_all=True)
self.assertThat(result, HasLength(2))
result_text = [v.text for v in result]
self.assertEqual(sorted([value1, value2]), sorted(result_text))

View File

@ -0,0 +1,309 @@
# -*- 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.
"""
Test class for DRAC ManagementInterface
"""
import mock
from ironic.common import boot_devices
from ironic.common import exception
from ironic.drivers.modules.drac import client as drac_client
from ironic.drivers.modules.drac import common as drac_common
from ironic.drivers.modules.drac import management as drac_mgmt
from ironic.drivers.modules.drac import resource_uris
from ironic.openstack.common import context
from ironic.tests import base
from ironic.tests.conductor import utils as mgr_utils
from ironic.tests.db import utils as db_utils
from ironic.tests.drivers.drac import utils as test_utils
from ironic.tests.objects import utils as obj_utils
INFO_DICT = db_utils.get_test_drac_info()
def _mock_wsman_root(return_value):
"""Helper function to mock the root() from wsman client."""
mock_xml_root = mock.Mock()
mock_xml_root.string.return_value = return_value
mock_xml = mock.Mock()
mock_xml.context.return_value = None
mock_xml.root.return_value = mock_xml_root
return mock_xml
@mock.patch.object(drac_client, 'pywsman')
class DracManagementInternalMethodsTestCase(base.TestCase):
def setUp(self):
super(DracManagementInternalMethodsTestCase, self).setUp()
mgr_utils.mock_the_extension_manager(driver='fake_drac')
self.context = context.get_admin_context()
self.node = obj_utils.create_test_node(self.context,
driver='fake_drac',
driver_info=INFO_DICT)
def test__get_next_boot_mode(self, mock_client_pywsman):
result_xml = test_utils.build_soap_xml([{'DCIM_BootConfigSetting':
{'InstanceID': 'IPL',
'IsNext':
drac_mgmt.PERSISTENT}}],
resource_uris.DCIM_BootConfigSetting)
mock_xml = _mock_wsman_root(result_xml)
mock_pywsman = mock_client_pywsman.Client.return_value
mock_pywsman.enumerate.return_value = mock_xml
expected = {'instance_id': 'IPL', 'is_next': drac_mgmt.PERSISTENT}
result = drac_mgmt._get_next_boot_mode(self.node)
self.assertEqual(expected, result)
mock_pywsman.enumerate.assert_called_once_with(mock.ANY, mock.ANY,
resource_uris.DCIM_BootConfigSetting)
def test__get_next_boot_mode_onetime(self, mock_client_pywsman):
result_xml = test_utils.build_soap_xml([{'DCIM_BootConfigSetting':
{'InstanceID': 'IPL',
'IsNext':
drac_mgmt.PERSISTENT}},
{'DCIM_BootConfigSetting':
{'InstanceID': 'OneTime',
'IsNext':
drac_mgmt.ONE_TIME_BOOT}}],
resource_uris.DCIM_BootConfigSetting)
mock_xml = _mock_wsman_root(result_xml)
mock_pywsman = mock_client_pywsman.Client.return_value
mock_pywsman.enumerate.return_value = mock_xml
expected = {'instance_id': 'OneTime',
'is_next': drac_mgmt.ONE_TIME_BOOT}
result = drac_mgmt._get_next_boot_mode(self.node)
self.assertEqual(expected, result)
mock_pywsman.enumerate.assert_called_once_with(mock.ANY, mock.ANY,
resource_uris.DCIM_BootConfigSetting)
def test__check_for_config_job(self, mock_client_pywsman):
result_xml = test_utils.build_soap_xml([{'DCIM_LifecycleJob':
{'Name': 'fake'}}],
resource_uris.DCIM_LifecycleJob)
mock_xml = _mock_wsman_root(result_xml)
mock_pywsman = mock_client_pywsman.Client.return_value
mock_pywsman.enumerate.return_value = mock_xml
result = drac_mgmt._check_for_config_job(self.node)
self.assertIsNone(result)
mock_pywsman.enumerate.assert_called_once_with(mock.ANY, mock.ANY,
resource_uris.DCIM_LifecycleJob)
def test__check_for_config_job_already_exist(self, mock_client_pywsman):
result_xml = test_utils.build_soap_xml([{'DCIM_LifecycleJob':
{'Name': 'BIOS.Setup.1-1',
'JobStatus': 'scheduled',
'InstanceID': 'fake'}}],
resource_uris.DCIM_LifecycleJob)
mock_xml = _mock_wsman_root(result_xml)
mock_pywsman = mock_client_pywsman.Client.return_value
mock_pywsman.enumerate.return_value = mock_xml
self.assertRaises(exception.DracConfigJobCreationError,
drac_mgmt._check_for_config_job, self.node)
mock_pywsman.enumerate.assert_called_once_with(mock.ANY, mock.ANY,
resource_uris.DCIM_LifecycleJob)
def test__create_config_job(self, mock_client_pywsman):
result_xml = test_utils.build_soap_xml([{'ReturnValue':
drac_mgmt.RET_SUCCESS}],
resource_uris.DCIM_BIOSService)
mock_xml = _mock_wsman_root(result_xml)
mock_pywsman = mock_client_pywsman.Client.return_value
mock_pywsman.invoke.return_value = mock_xml
result = drac_mgmt._create_config_job(self.node)
self.assertIsNone(result)
mock_pywsman.invoke.assert_called_once_with(mock.ANY,
resource_uris.DCIM_BIOSService, 'CreateTargetedConfigJob')
def test__create_config_job_error(self, mock_client_pywsman):
result_xml = test_utils.build_soap_xml([{'ReturnValue':
drac_mgmt.RET_ERROR,
'Message': 'E_FAKE'}],
resource_uris.DCIM_BIOSService)
mock_xml = _mock_wsman_root(result_xml)
mock_pywsman = mock_client_pywsman.Client.return_value
mock_pywsman.invoke.return_value = mock_xml
self.assertRaises(exception.DracConfigJobCreationError,
drac_mgmt._create_config_job, self.node)
mock_pywsman.invoke.assert_called_once_with(mock.ANY,
resource_uris.DCIM_BIOSService, 'CreateTargetedConfigJob')
@mock.patch.object(drac_client, 'pywsman')
class DracManagementTestCase(base.TestCase):
def setUp(self):
super(DracManagementTestCase, self).setUp()
mgr_utils.mock_the_extension_manager(driver='fake_drac')
self.context = context.get_admin_context()
self.node = obj_utils.create_test_node(self.context,
driver='fake_drac',
driver_info=INFO_DICT)
self.driver = drac_mgmt.DracManagement()
self.task = mock.Mock()
self.task.node = self.node
def test_get_properties(self, mock_client_pywsman):
expected = drac_common.COMMON_PROPERTIES
self.assertEqual(expected, self.driver.get_properties())
def test_get_supported_boot_devices(self, mock_client_pywsman):
expected = [boot_devices.PXE, boot_devices.DISK, boot_devices.CDROM]
self.assertEqual(sorted(expected),
sorted(self.driver.get_supported_boot_devices()))
@mock.patch.object(drac_mgmt, '_get_next_boot_mode')
def test_get_boot_devices(self, mock_gnbm, mock_client_pywsman):
mock_gnbm.return_value = {'instance_id': 'OneTime',
'is_next': drac_mgmt.ONE_TIME_BOOT}
result_xml = test_utils.build_soap_xml([{'InstanceID': 'HardDisk'}],
resource_uris.DCIM_BootSourceSetting)
mock_xml = _mock_wsman_root(result_xml)
mock_pywsman = mock_client_pywsman.Client.return_value
mock_pywsman.enumerate.return_value = mock_xml
result = self.driver.get_boot_device(self.task)
expected = {'boot_device': boot_devices.DISK, 'persistent': False}
self.assertEqual(expected, result)
mock_pywsman.enumerate.assert_called_once_with(mock.ANY, mock.ANY,
resource_uris.DCIM_BootSourceSetting)
@mock.patch.object(drac_mgmt, '_get_next_boot_mode')
def test_get_boot_devices_persistent(self, mock_gnbm, mock_client_pywsman):
mock_gnbm.return_value = {'instance_id': 'IPL',
'is_next': drac_mgmt.PERSISTENT}
result_xml = test_utils.build_soap_xml([{'InstanceID': 'NIC'}],
resource_uris.DCIM_BootSourceSetting)
mock_xml = _mock_wsman_root(result_xml)
mock_pywsman = mock_client_pywsman.Client.return_value
mock_pywsman.enumerate.return_value = mock_xml
result = self.driver.get_boot_device(self.task)
expected = {'boot_device': boot_devices.PXE, 'persistent': True}
self.assertEqual(expected, result)
mock_pywsman.enumerate.assert_called_once_with(mock.ANY, mock.ANY,
resource_uris.DCIM_BootSourceSetting)
@mock.patch.object(drac_client.Client, 'wsman_enumerate')
@mock.patch.object(drac_mgmt, '_get_next_boot_mode')
def test_get_boot_devices_client_error(self, mock_gnbm, mock_we,
mock_client_pywsman):
mock_gnbm.return_value = {'instance_id': 'OneTime',
'is_next': drac_mgmt.ONE_TIME_BOOT}
mock_we.side_effect = exception.DracClientError('E_FAKE')
self.assertRaises(exception.DracClientError,
self.driver.get_boot_device, self.task)
mock_we.assert_called_once_with(resource_uris.DCIM_BootSourceSetting,
mock.ANY, mock.ANY)
@mock.patch.object(drac_mgmt, '_check_for_config_job')
@mock.patch.object(drac_mgmt, '_create_config_job')
def test_set_boot_device(self, mock_ccj, mock_cfcj, mock_client_pywsman):
result_xml_enum = test_utils.build_soap_xml([{'InstanceID': 'NIC'}],
resource_uris.DCIM_BootSourceSetting)
result_xml_invk = test_utils.build_soap_xml([{'ReturnValue':
drac_mgmt.RET_SUCCESS}],
resource_uris.DCIM_BootConfigSetting)
mock_xml_enum = _mock_wsman_root(result_xml_enum)
mock_xml_invk = _mock_wsman_root(result_xml_invk)
mock_pywsman = mock_client_pywsman.Client.return_value
mock_pywsman.enumerate.return_value = mock_xml_enum
mock_pywsman.invoke.return_value = mock_xml_invk
result = self.driver.set_boot_device(self.task, boot_devices.PXE)
self.assertIsNone(result)
mock_pywsman.enumerate.assert_called_once_with(mock.ANY, mock.ANY,
resource_uris.DCIM_BootSourceSetting)
mock_pywsman.invoke.assert_called_once_with(mock.ANY,
resource_uris.DCIM_BootConfigSetting,
'ChangeBootOrderByInstanceID')
mock_cfcj.assert_called_once_with(self.node)
mock_ccj.assert_called_once_with(self.node)
@mock.patch.object(drac_mgmt, '_check_for_config_job')
@mock.patch.object(drac_mgmt, '_create_config_job')
def test_set_boot_device_fail(self, mock_ccj, mock_cfcj,
mock_client_pywsman):
result_xml_enum = test_utils.build_soap_xml([{'InstanceID': 'NIC'}],
resource_uris.DCIM_BootSourceSetting)
result_xml_invk = test_utils.build_soap_xml([{'ReturnValue':
drac_mgmt.RET_ERROR,
'Message': 'E_FAKE'}],
resource_uris.DCIM_BootConfigSetting)
mock_xml_enum = _mock_wsman_root(result_xml_enum)
mock_xml_invk = _mock_wsman_root(result_xml_invk)
mock_pywsman = mock_client_pywsman.Client.return_value
mock_pywsman.enumerate.return_value = mock_xml_enum
mock_pywsman.invoke.return_value = mock_xml_invk
self.assertRaises(exception.DracOperationError,
self.driver.set_boot_device, self.task,
boot_devices.PXE)
mock_pywsman.enumerate.assert_called_once_with(mock.ANY, mock.ANY,
resource_uris.DCIM_BootSourceSetting)
mock_pywsman.invoke.assert_called_once_with(mock.ANY,
resource_uris.DCIM_BootConfigSetting,
'ChangeBootOrderByInstanceID')
mock_cfcj.assert_called_once_with(self.node)
self.assertFalse(mock_ccj.called)
@mock.patch.object(drac_client.Client, 'wsman_enumerate')
@mock.patch.object(drac_mgmt, '_check_for_config_job')
def test_set_boot_device_client_error(self, mock_cfcj, mock_we,
mock_client_pywsman):
mock_we.side_effect = exception.DracClientError('E_FAKE')
self.assertRaises(exception.DracClientError,
self.driver.set_boot_device, self.task,
boot_devices.PXE)
mock_we.assert_called_once_with(resource_uris.DCIM_BootSourceSetting,
mock.ANY, mock.ANY)
def test_get_sensors_data(self, mock_client_pywsman):
self.assertRaises(NotImplementedError,
self.driver.get_sensors_data, self.task)

View File

@ -46,7 +46,7 @@ class DracPowerInternalMethodsTestCase(base.TestCase):
self.node = self.dbapi.create_node(db_node) self.node = self.dbapi.create_node(db_node)
def test__get_power_state(self, mock_power_pywsman, mock_client_pywsman): def test__get_power_state(self, mock_power_pywsman, mock_client_pywsman):
result_xml = test_utils.build_soap_xml({'EnabledState': '2'}, result_xml = test_utils.build_soap_xml([{'EnabledState': '2'}],
resource_uris.DCIM_ComputerSystem) resource_uris.DCIM_ComputerSystem)
mock_xml_root = mock.Mock() mock_xml_root = mock.Mock()
mock_xml_root.string.return_value = result_xml mock_xml_root.string.return_value = result_xml
@ -65,7 +65,7 @@ class DracPowerInternalMethodsTestCase(base.TestCase):
mock.ANY, resource_uris.DCIM_ComputerSystem) mock.ANY, resource_uris.DCIM_ComputerSystem)
def test__set_power_state(self, mock_power_pywsman, mock_client_pywsman): def test__set_power_state(self, mock_power_pywsman, mock_client_pywsman):
result_xml = test_utils.build_soap_xml({'ReturnValue': '0'}, result_xml = test_utils.build_soap_xml([{'ReturnValue': '0'}],
resource_uris.DCIM_ComputerSystem) resource_uris.DCIM_ComputerSystem)
mock_xml_root = mock.Mock() mock_xml_root = mock.Mock()
mock_xml_root.string.return_value = result_xml mock_xml_root.string.return_value = result_xml
@ -92,8 +92,8 @@ class DracPowerInternalMethodsTestCase(base.TestCase):
def test__set_power_state_fail(self, mock_power_pywsman, def test__set_power_state_fail(self, mock_power_pywsman,
mock_client_pywsman): mock_client_pywsman):
result_xml = test_utils.build_soap_xml({'ReturnValue': '1', result_xml = test_utils.build_soap_xml([{'ReturnValue': '1',
'Message': 'error message'}, 'Message': 'error message'}],
resource_uris.DCIM_ComputerSystem) resource_uris.DCIM_ComputerSystem)
mock_xml_root = mock.Mock() mock_xml_root = mock.Mock()
mock_xml_root.string.return_value = result_xml mock_xml_root.string.return_value = result_xml

View File

@ -21,26 +21,38 @@ from xml.etree import ElementTree
def build_soap_xml(items, namespace=None): def build_soap_xml(items, namespace=None):
"""Build a SOAP XML. """Build a SOAP XML.
:param items: a dictionary where key is the element name and the :param items: a list of dictionaries where key is the element name
value is the element text. and the value is the element text.
:param namespace: the namespace for the elements, None for no :param namespace: the namespace for the elements, None for no
namespace. Defaults to None namespace. Defaults to None
:returns: a XML string. :returns: a XML string.
""" """
soap_namespace = "http://www.w3.org/2003/05/soap-envelope"
envelope_element = ElementTree.Element("{%s}Envelope" % soap_namespace)
body_element = ElementTree.Element("{%s}Body" % soap_namespace)
for i in items: def _create_element(name, value=None):
xml_string = i xml_string = name
if namespace: if namespace:
xml_string = "{%(namespace)s}%(item)s" % {'namespace': namespace, xml_string = "{%(namespace)s}%(item)s" % {'namespace': namespace,
'item': xml_string} 'item': xml_string}
element = ElementTree.Element(xml_string) element = ElementTree.Element(xml_string)
element.text = items[i] element.text = value
body_element.append(element) return element
soap_namespace = "http://www.w3.org/2003/05/soap-envelope"
envelope_element = ElementTree.Element("{%s}Envelope" % soap_namespace)
body_element = ElementTree.Element("{%s}Body" % soap_namespace)
for item in items:
for i in item:
insertion_point = _create_element(i)
if isinstance(item[i], dict):
for j, value in item[i].items():
insertion_point.append(_create_element(j, value))
else:
insertion_point.text = item[i]
body_element.append(insertion_point)
envelope_element.append(body_element) envelope_element.append(body_element)
return ElementTree.tostring(envelope_element) return ElementTree.tostring(envelope_element)