Merge "Implements the DRAC ManagementInterface for get/set boot device"
This commit is contained in:
commit
a6caeef289
@ -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")
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
339
ironic/drivers/modules/drac/management.py
Normal file
339
ironic/drivers/modules/drac/management.py
Normal 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()
|
@ -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')
|
||||
|
@ -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]
|
||||
|
@ -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))
|
||||
|
309
ironic/tests/drivers/drac/test_management.py
Normal file
309
ironic/tests/drivers/drac/test_management.py
Normal 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)
|
@ -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
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user