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')
class DracConfigJobCreationError(DracOperationError):
message = _('DRAC failed to create a configuration job. '
'Reason: %(error)s')
class FailedToGetSensorData(IronicException):
message = _("Failed to get sensor data for node %(node)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.i18n import _
from ironic.drivers import base
from ironic.drivers.modules.drac import management
from ironic.drivers.modules.drac import power
from ironic.drivers.modules import ipmitool
from ironic.drivers.modules import pxe
from ironic.openstack.common import importutils
class PXEDracDriver(base.BaseDriver):
"""Drac driver using PXE for deploy and ipmitool for management."""
"""Drac driver using PXE for deploy."""
def __init__(self):
if not importutils.try_import('pywsman'):
@ -35,6 +34,4 @@ class PXEDracDriver(base.BaseDriver):
self.power = power.DracPower()
self.deploy = pxe.PXEDeploy()
# NOTE(ifarkas): using ipmitool is a temporary solution. It will be
# replaced by the DracManagement interface.
self.management = ipmitool.IPMIManagement()
self.management = management.DracManagement()

View File

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

View File

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

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')
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):
mock_root = mock.Mock()
mock_root.string.side_effect = [test_utils.build_soap_xml(
{'item1': 'test1'}),
[{'item1': 'test1'}]),
test_utils.build_soap_xml(
{'item2': 'test2'})]
[{'item2': 'test2'}])]
mock_xml = mock.Mock()
mock_xml.root.return_value = mock_root
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 testtools.matchers import HasLength
from ironic.common import exception
from ironic.drivers.modules.drac import common as drac_common
from ironic.openstack.common import context
@ -116,3 +118,23 @@ class DracCommonMethodsTestCase(base.TestCase):
result = drac_common.find_xml(test_doc, 'test_element', namespace)
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)
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)
mock_xml_root = mock.Mock()
mock_xml_root.string.return_value = result_xml
@ -65,7 +65,7 @@ class DracPowerInternalMethodsTestCase(base.TestCase):
mock.ANY, resource_uris.DCIM_ComputerSystem)
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)
mock_xml_root = mock.Mock()
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,
mock_client_pywsman):
result_xml = test_utils.build_soap_xml({'ReturnValue': '1',
'Message': 'error message'},
result_xml = test_utils.build_soap_xml([{'ReturnValue': '1',
'Message': 'error message'}],
resource_uris.DCIM_ComputerSystem)
mock_xml_root = mock.Mock()
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):
"""Build a SOAP XML.
:param items: a dictionary where key is the element name and the
value is the element text.
:param items: a list of dictionaries where key is the element name
and the value is the element text.
:param namespace: the namespace for the elements, None for no
namespace. Defaults to None
: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:
xml_string = i
def _create_element(name, value=None):
xml_string = name
if namespace:
xml_string = "{%(namespace)s}%(item)s" % {'namespace': namespace,
'item': xml_string}
element = ElementTree.Element(xml_string)
element.text = items[i]
body_element.append(element)
element.text = value
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)
return ElementTree.tostring(envelope_element)