Merge "Add iRMC Driver and its iRMC Power module"

This commit is contained in:
Jenkins 2015-01-28 16:45:31 +00:00 committed by Gerrit Code Review
commit 00d58f9042
14 changed files with 697 additions and 0 deletions

View File

@ -87,3 +87,44 @@ SeaMicro driver
:maxdepth: 1 :maxdepth: 1
../drivers/seamicro ../drivers/seamicro
iRMC
----
The iRMC driver enables PXE Deploy to control power via ServerView Common
Command Interface (SCCI).
Software Requirements
^^^^^^^^^^^^^^^^^^^^^
- Install `python-scciclient package <https://pypi.python.org/pypi/python-scciclient>`_
Enabling the iRMC Driver
^^^^^^^^^^^^^^^^^^^^^^^^
- Add ``pxe_irmc`` to the list of ``enabled_drivers in``
``/etc/ironic/ironic.conf``
- Ironic Conductor must be restarted for the new driver to be loaded.
Ironic Node Configuration
^^^^^^^^^^^^^^^^^^^^^^^^^
Nodes are configured for iRMC with PXE Deploy by setting the Ironic node
object's ``driver`` property to be ``pxe_irmc``. Further configuration values
are added to ``driver_info``:
- ``irmc_address``: hostname or IP of iRMC
- ``irmc_username``: username for iRMC with administrator privileges
- ``irmc_password``: password for irmc_username
- ``irmc_port``: port number of iRMC (optional, either 80 or 443. defalut 443)
- ``irmc_auth_method``: authentication method for iRMC (optional, either
'basic' or 'digest'. default is 'basic')
Supported Platforms
^^^^^^^^^^^^^^^^^^^
This driver supports FUJITSU PRIMERGY BX S4 or RX S8 servers and above.
- PRIMERGY BX920 S4
- PRIMERGY BX924 S4
- PRIMERGY RX300 S8

View File

@ -900,6 +900,24 @@
#min_command_interval=5 #min_command_interval=5
[irmc]
#
# Options defined in ironic.drivers.modules.irmc.common
#
# Port to be used for iRMC operations, either 80 or 443
# (integer value)
#port=443
# Authentication method to be used for iRMC operations, either
# "basic" or "digest" (string value)
#auth_method=basic
# Timeout (in seconds) for iRMC operations (integer value)
#client_timeout=60
[keystone_authtoken] [keystone_authtoken]
# #

4
ironic/common/exception.py Normal file → Executable file
View File

@ -501,3 +501,7 @@ class SNMPFailure(IronicException):
class FileSystemNotSupported(IronicException): class FileSystemNotSupported(IronicException):
message = _("Failed to create a file system. " message = _("Failed to create a file system. "
"File system %(fs)s is not supported.") "File system %(fs)s is not supported.")
class IRMCOperationError(IronicException):
message = _('iRMC %(operation)s failed. Reason: %(error)s')

13
ironic/drivers/fake.py Normal file → Executable file
View File

@ -31,6 +31,7 @@ from ironic.drivers.modules.ilo import management as ilo_management
from ironic.drivers.modules.ilo import power as ilo_power from ironic.drivers.modules.ilo import power as ilo_power
from ironic.drivers.modules import ipminative from ironic.drivers.modules import ipminative
from ironic.drivers.modules import ipmitool from ironic.drivers.modules import ipmitool
from ironic.drivers.modules.irmc import power as irmc_power
from ironic.drivers.modules import pxe from ironic.drivers.modules import pxe
from ironic.drivers.modules import seamicro from ironic.drivers.modules import seamicro
from ironic.drivers.modules import snmp from ironic.drivers.modules import snmp
@ -171,3 +172,15 @@ class FakeSNMPDriver(base.BaseDriver):
reason=_("Unable to import pysnmp library")) reason=_("Unable to import pysnmp library"))
self.power = snmp.SNMPPower() self.power = snmp.SNMPPower()
self.deploy = fake.FakeDeploy() self.deploy = fake.FakeDeploy()
class FakeIRMCDriver(base.BaseDriver):
"""Fake iRMC driver."""
def __init__(self):
if not importutils.try_import('scciclient'):
raise exception.DriverLoadError(
driver=self.__class__.__name__,
reason=_("Unable to import python-scciclient library"))
self.power = irmc_power.IRMCPower()
self.deploy = fake.FakeDeploy()

View File

View File

@ -0,0 +1,148 @@
#
# 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.
"""
Common functionalities shared between different iRMC modules.
"""
from oslo.config import cfg
from oslo.utils import importutils
from ironic.common import exception
from ironic.common.i18n import _
from ironic.openstack.common import log as logging
scci = importutils.try_import('scciclient.irmc.scci')
opts = [
cfg.IntOpt('port',
default=443,
help='Port to be used for iRMC operations, either 80 or 443'),
cfg.StrOpt('auth_method',
default='basic',
help='Authentication method to be used for iRMC operations, ' +
'either "basic" or "digest"'),
cfg.IntOpt('client_timeout',
default=60,
help='Timeout (in seconds) for iRMC operations'),
]
CONF = cfg.CONF
CONF.register_opts(opts, group='irmc')
LOG = logging.getLogger(__name__)
REQUIRED_PROPERTIES = {
'irmc_address': _("IP address or hostname of the iRMC. Required."),
'irmc_username': _("Username for the iRMC with administrator privileges. "
"Required."),
'irmc_password': _("Password for irmc_username. Required.")
}
OPTIONAL_PROPERTIES = {
'port': _("Port to be used for irmc operations either 80 or 443. "
"Optional. The default value is 443"),
'auth_method': _("Authentication method for iRMC operations "
"either 'basic' or 'digest'. "
"Optional. The default value is 'digest'"),
'client_timeout': _("Timeout (in seconds) for iRMC operations. "
"Optional. The default value is 60.")
}
COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy()
COMMON_PROPERTIES.update(OPTIONAL_PROPERTIES)
def parse_driver_info(node):
"""Gets the specific Node driver info.
This method validates whether the 'driver_info' property of the
supplied node contains the required information for this driver.
:param node: an ironic node object.
:returns: a dict containing information from driver_info
and default values.
:raises: InvalidParameterValue if invalid value is contained
in the 'driver_info' property.
:raises: MissingParameterValue if some mandatory key is missing
in the 'driver_info' property.
"""
info = node.driver_info
missing_info = [key for key in REQUIRED_PROPERTIES if not info.get(key)]
if missing_info:
raise exception.MissingParameterValue(_(
"Missing the following iRMC parameters in node's"
" driver_info: %s.") % missing_info)
req = {key: value for key, value in info.iteritems()
if key in REQUIRED_PROPERTIES}
opt = {'irmc_' + param: info.get('irmc_' + param, CONF.irmc.get(param))
for param in OPTIONAL_PROPERTIES}
d_info = dict(list(req.items()) + list(opt.items()))
error_msgs = []
if (d_info['irmc_auth_method'].lower() not in ('basic', 'digest')):
error_msgs.append(
_("'%s' has unsupported value.") % 'irmc_auth_method')
if d_info['irmc_port'] not in (80, 443):
error_msgs.append(
_("'%s' has unsupported value.") % 'irmc_port')
if not isinstance(d_info['irmc_client_timeout'], int):
error_msgs.append(
_("'%s' is not integer type.") % 'irmc_client_timeout')
if error_msgs:
msg = (_("The following type errors were encountered while parsing "
"driver_info:\n%s") % "\n".join(error_msgs))
raise exception.InvalidParameterValue(msg)
return d_info
def get_irmc_client(node):
"""Gets an iRMC SCCI client.
Given an ironic node object, this method gives back a iRMC SCCI client
to do operations on the iRMC.
:param node: an ironic node object.
:returns: scci_cmd partial function which takes a SCCI command param.
:raises: InvalidParameterValue on invalid inputs.
:raises: MissingParameterValue if some mandatory information
is missing on the node
"""
driver_info = parse_driver_info(node)
scci_client = scci.get_client(
driver_info['irmc_address'],
driver_info['irmc_username'],
driver_info['irmc_password'],
port=driver_info['irmc_port'],
auth_method=driver_info['irmc_auth_method'],
client_timeout=driver_info['irmc_client_timeout'])
return scci_client
def update_ipmi_properties(task):
"""Update ipmi properties to node driver_info
:param task: a task from TaskManager.
"""
node = task.node
info = node.driver_info
# updating ipmi credentials
info['ipmi_address'] = info.get('irmc_address')
info['ipmi_username'] = info.get('irmc_username')
info['ipmi_password'] = info.get('irmc_password')
# saving ipmi credentials to task object
task.node.driver_info = info

View File

@ -0,0 +1,133 @@
#
# 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.
"""
iRMC Power Driver using the Base Server Profile
"""
from oslo.config import cfg
from oslo.utils import importutils
from ironic.common import exception
from ironic.common.i18n import _
from ironic.common.i18n import _LE
from ironic.common import states
from ironic.conductor import task_manager
from ironic.drivers import base
from ironic.drivers.modules import ipmitool
from ironic.drivers.modules.irmc import common as irmc_common
from ironic.openstack.common import log as logging
scci = importutils.try_import('scciclient.irmc.scci')
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
if scci:
STATES_MAP = {states.POWER_OFF: scci.POWER_OFF,
states.POWER_ON: scci.POWER_ON,
states.REBOOT: scci.POWER_RESET}
def _set_power_state(task, target_state):
"""Turns the server power on/off or do a reboot.
:param task: a TaskManager instance containing the node to act on.
:param target_state: target state of the node.
:raises: InvalidParameterValue if an invalid power state was specified.
:raises: MissingParameterValue if some mandatory information
is missing on the node
:raises: IRMCOperationError on an error from SCCI
"""
node = task.node
irmc_client = irmc_common.get_irmc_client(node)
try:
irmc_client(STATES_MAP[target_state])
except KeyError:
msg = _("_set_power_state called with invalid power state "
"'%s'") % target_state
raise exception.InvalidParameterValue(msg)
except scci.SCCIClientError as irmc_exception:
LOG.error(_LE("iRMC set_power_state failed to set state to %(tstate)s "
" for node %(node_id)s with error: %(error)s"),
{'tstate': target_state, 'node_id': node.uuid,
'error': irmc_exception})
operation = _('iRMC set_power_state')
raise exception.IRMCOperationError(operation=operation,
error=irmc_exception)
class IRMCPower(base.PowerInterface):
"""Interface for power-related actions."""
def get_properties(self):
"""Return the properties of the interface.
:returns: dictionary of <property name>:<property description> entries.
"""
return irmc_common.COMMON_PROPERTIES
def validate(self, task):
"""Validate the driver-specific Node power info.
This method validates whether the 'driver_info' property of the
supplied node contains the required information for this driver to
manage the power state of 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.
:raises: MissingParameterValue if a required parameter is missing.
"""
irmc_common.parse_driver_info(task.node)
def get_power_state(self, task):
"""Return the power state of the task's node.
:param task: a TaskManager instance containing the node to act on.
:returns: a power state. One of :mod:`ironic.common.states`.
:raises: InvalidParameterValue if required ipmi parameters are missing.
:raises: MissingParameterValue if a required parameter is missing.
:raises: IPMIFailure on an error from ipmitool (from _power_status
call).
"""
irmc_common.update_ipmi_properties(task)
ipmi_power = ipmitool.IPMIPower()
return ipmi_power.get_power_state(task)
@task_manager.require_exclusive_lock
def set_power_state(self, task, power_state):
"""Set the power state of the task's node.
:param task: a TaskManager instance containing the node to act on.
:param power_state: Any power state from :mod:`ironic.common.states`.
:raises: InvalidParameterValue if an invalid power state was specified.
:raises: MissingParameterValue if some mandatory information
is missing on the node
:raises: IRMCOperationError if failed to set the power state.
"""
_set_power_state(task, power_state)
@task_manager.require_exclusive_lock
def reboot(self, task):
"""Perform a hard reboot of the task's node.
:param task: a TaskManager instance containing the node to act on.
:raises: InvalidParameterValue if an invalid power state was specified.
:raises: IRMCOperationError if failed to set the power state.
"""
_set_power_state(task, states.REBOOT)

View File

@ -28,6 +28,7 @@ from ironic.drivers.modules.ilo import management as ilo_management
from ironic.drivers.modules.ilo import power as ilo_power from ironic.drivers.modules.ilo import power as ilo_power
from ironic.drivers.modules import ipminative from ironic.drivers.modules import ipminative
from ironic.drivers.modules import ipmitool from ironic.drivers.modules import ipmitool
from ironic.drivers.modules.irmc import power as irmc_power
from ironic.drivers.modules import pxe from ironic.drivers.modules import pxe
from ironic.drivers.modules import seamicro from ironic.drivers.modules import seamicro
from ironic.drivers.modules import snmp from ironic.drivers.modules import snmp
@ -187,3 +188,24 @@ class PXEAndSNMPDriver(base.BaseDriver):
# PDUs have no boot device management capability. # PDUs have no boot device management capability.
# Only PXE as a boot device is supported. # Only PXE as a boot device is supported.
self.management = None self.management = None
class PXEAndIRMCDriver(base.BaseDriver):
"""PXE + iRMC driver using SCCI.
This driver implements the `core` functionality using
:class:`ironic.drivers.modules.irmc.power.IRMCPower` for power management
:class:`ironic.drivers.modules.pxe.PXEDeploy` for image deployment.
"""
def __init__(self):
if not importutils.try_import('scciclient'):
raise exception.DriverLoadError(
driver=self.__class__.__name__,
reason=_("Unable to import python-scciclient library"))
self.power = irmc_power.IRMCPower()
self.console = ipmitool.IPMIShellinaboxConsole()
self.deploy = pxe.PXEDeploy()
self.management = ipmitool.IPMIManagement()
self.vendor = pxe.VendorPassthru()

View File

@ -104,6 +104,16 @@ def get_test_drac_info():
} }
def get_test_irmc_info():
return {
"irmc_address": "1.2.3.4",
"irmc_username": "admin0",
"irmc_password": "fake0",
"irmc_port": 80,
"irmc_auth_method": "digest",
}
def get_test_agent_instance_info(): def get_test_agent_instance_info():
return { return {
'image_source': 'fake-image', 'image_source': 'fake-image',

View File

View File

@ -0,0 +1,132 @@
#
# 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 common methods used by iRMC modules.
"""
import mock
from oslo.config import cfg
from ironic.common import exception
from ironic.conductor import task_manager
from ironic.drivers.modules.irmc import common as irmc_common
from ironic.tests.conductor import utils as mgr_utils
from ironic.tests.db import base as db_base
from ironic.tests.db import utils as db_utils
from ironic.tests.objects import utils as obj_utils
CONF = cfg.CONF
class IRMCValidateParametersTestCase(db_base.DbTestCase):
def setUp(self):
super(IRMCValidateParametersTestCase, self).setUp()
self.node = obj_utils.create_test_node(
self.context,
driver='fake_irmc',
driver_info=db_utils.get_test_irmc_info())
def test_parse_driver_info(self):
info = irmc_common.parse_driver_info(self.node)
self.assertIsNotNone(info.get('irmc_address'))
self.assertIsNotNone(info.get('irmc_username'))
self.assertIsNotNone(info.get('irmc_password'))
self.assertIsNotNone(info.get('irmc_client_timeout'))
self.assertIsNotNone(info.get('irmc_port'))
self.assertIsNotNone(info.get('irmc_auth_method'))
def test_parse_driver_info_missing_address(self):
del self.node.driver_info['irmc_address']
self.assertRaises(exception.MissingParameterValue,
irmc_common.parse_driver_info, self.node)
def test_parse_driver_info_missing_username(self):
del self.node.driver_info['irmc_username']
self.assertRaises(exception.MissingParameterValue,
irmc_common.parse_driver_info, self.node)
def test_parse_driver_info_missing_password(self):
del self.node.driver_info['irmc_password']
self.assertRaises(exception.MissingParameterValue,
irmc_common.parse_driver_info, self.node)
def test_parse_driver_info_invalid_timeout(self):
self.node.driver_info['irmc_client_timeout'] = 'qwe'
self.assertRaises(exception.InvalidParameterValue,
irmc_common.parse_driver_info, self.node)
def test_parse_driver_info_invalid_port(self):
self.node.driver_info['irmc_port'] = 'qwe'
self.assertRaises(exception.InvalidParameterValue,
irmc_common.parse_driver_info, self.node)
def test_parse_driver_info_invalid_auth_method(self):
self.node.driver_info['irmc_auth_method'] = 'qwe'
self.assertRaises(exception.InvalidParameterValue,
irmc_common.parse_driver_info, self.node)
def test_parse_driver_info_missing_multiple_params(self):
del self.node.driver_info['irmc_password']
del self.node.driver_info['irmc_address']
try:
irmc_common.parse_driver_info(self.node)
self.fail("parse_driver_info did not throw exception.")
except exception.MissingParameterValue as e:
self.assertIn('irmc_password', str(e))
self.assertIn('irmc_address', str(e))
class IRMCCommonMethodsTestCase(db_base.DbTestCase):
def setUp(self):
super(IRMCCommonMethodsTestCase, self).setUp()
mgr_utils.mock_the_extension_manager(driver="fake_irmc")
self.info = db_utils.get_test_irmc_info()
self.node = obj_utils.create_test_node(
self.context,
driver='fake_irmc',
driver_info=self.info)
@mock.patch.object(irmc_common, 'scci')
def test_get_irmc_client(self, mock_scci):
self.info['irmc_port'] = 80
self.info['irmc_auth_method'] = 'digest'
self.info['irmc_client_timeout'] = 60
mock_scci.get_client.return_value = 'get_client'
returned_mock_scci_get_client = irmc_common.get_irmc_client(self.node)
mock_scci.get_client.assert_called_with(
self.info['irmc_address'],
self.info['irmc_username'],
self.info['irmc_password'],
port=self.info['irmc_port'],
auth_method=self.info['irmc_auth_method'],
client_timeout=self.info['irmc_client_timeout'])
self.assertEqual('get_client', returned_mock_scci_get_client)
def test_update_ipmi_properties(self):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
ipmi_info = {
"ipmi_address": "1.2.3.4",
"ipmi_username": "admin0",
"ipmi_password": "fake0",
}
task.node.driver_info = self.info
irmc_common.update_ipmi_properties(task)
actual_info = task.node.driver_info
expected_info = dict(self.info, **ipmi_info)
self.assertEqual(expected_info, actual_info)

View File

@ -0,0 +1,154 @@
#
# 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 iRMC Power Driver
"""
import mock
from oslo.config import cfg
from ironic.common import exception
from ironic.common import states
from ironic.conductor import task_manager
from ironic.drivers.modules.irmc import common as irmc_common
from ironic.drivers.modules.irmc import power as irmc_power
from ironic.tests.conductor import utils as mgr_utils
from ironic.tests.db import base as db_base
from ironic.tests.db import utils as db_utils
from ironic.tests.objects import utils as obj_utils
INFO_DICT = db_utils.get_test_irmc_info()
CONF = cfg.CONF
@mock.patch.object(irmc_common, 'get_irmc_client')
class IRMCPowerInternalMethodsTestCase(db_base.DbTestCase):
def setUp(self):
super(IRMCPowerInternalMethodsTestCase, self).setUp()
mgr_utils.mock_the_extension_manager(driver='fake_irmc')
driver_info = INFO_DICT
self.node = db_utils.create_test_node(
driver='fake_irmc',
driver_info=driver_info,
instance_uuid='instance_uuid_123')
def test__set_power_state_power_on_ok(self,
get_irmc_client_mock):
irmc_client = get_irmc_client_mock.return_value
target_state = states.POWER_ON
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
irmc_power._set_power_state(task, target_state)
irmc_client.assert_called_once_with(irmc_power.scci.POWER_ON)
def test__set_power_state_power_off_ok(self,
get_irmc_client_mock):
irmc_client = get_irmc_client_mock.return_value
target_state = states.POWER_OFF
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
irmc_power._set_power_state(task, target_state)
irmc_client.assert_called_once_with(irmc_power.scci.POWER_OFF)
def test__set_power_state_power_reboot_ok(self,
get_irmc_client_mock):
irmc_client = get_irmc_client_mock.return_value
target_state = states.REBOOT
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
irmc_power._set_power_state(task, target_state)
irmc_client.assert_called_once_with(irmc_power.scci.POWER_RESET)
def test__set_power_state_invalid_target_state(self,
get_irmc_client_mock):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
self.assertRaises(exception.InvalidParameterValue,
irmc_power._set_power_state,
task,
states.ERROR)
def test__set_power_state_scci_exception(self,
get_irmc_client_mock):
irmc_client = get_irmc_client_mock.return_value
irmc_client.side_effect = Exception()
irmc_power.scci.SCCIClientError = Exception
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
self.assertRaises(exception.IRMCOperationError,
irmc_power._set_power_state,
task,
states.POWER_ON)
class IRMCPowerTestCase(db_base.DbTestCase):
def setUp(self):
super(IRMCPowerTestCase, self).setUp()
driver_info = INFO_DICT
mgr_utils.mock_the_extension_manager(driver="fake_irmc")
self.node = obj_utils.create_test_node(self.context,
driver='fake_irmc',
driver_info=driver_info)
def test_get_properties(self):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
properties = task.driver.get_properties()
for prop in irmc_common.COMMON_PROPERTIES:
self.assertIn(prop, properties)
@mock.patch.object(irmc_common, 'parse_driver_info')
def test_validate(self, mock_drvinfo):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
task.driver.power.validate(task)
mock_drvinfo.assert_called_once_with(task.node)
@mock.patch.object(irmc_common, 'parse_driver_info')
def test_validate_fail(self, mock_drvinfo):
side_effect = exception.InvalidParameterValue("Invalid Input")
mock_drvinfo.side_effect = side_effect
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
self.assertRaises(exception.InvalidParameterValue,
task.driver.power.validate,
task)
@mock.patch('ironic.drivers.modules.irmc.power.ipmitool.IPMIPower')
def test_get_power_state(self, mock_IPMIPower):
ipmi_power = mock_IPMIPower.return_value
ipmi_power.get_power_state.return_value = states.POWER_ON
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
self.assertEqual(states.POWER_ON,
task.driver.power.get_power_state(task))
ipmi_power.get_power_state.assert_called_once_with(task)
@mock.patch.object(irmc_power, '_set_power_state')
def test_set_power_state(self, mock_set_power):
mock_set_power.return_value = states.POWER_ON
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.driver.power.set_power_state(task, states.POWER_ON)
mock_set_power.assert_called_once_with(task, states.POWER_ON)
@mock.patch.object(irmc_power, '_set_power_state')
@mock.patch.object(irmc_power.IRMCPower, 'get_power_state')
def test_reboot(self, mock_get_power, mock_set_power):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.driver.power.reboot(task)
mock_set_power.assert_called_once_with(task, states.REBOOT)

20
ironic/tests/drivers/third_party_driver_mocks.py Normal file → Executable file
View File

@ -26,6 +26,7 @@ Current list of mocked libraries:
- ipminative - ipminative
- proliantutils - proliantutils
- pysnmp - pysnmp
- scciclient
""" """
import sys import sys
@ -141,3 +142,22 @@ if not pysnmp:
# external library has been mocked # external library has been mocked
if 'ironic.drivers.modules.snmp' in sys.modules: if 'ironic.drivers.modules.snmp' in sys.modules:
reload(sys.modules['ironic.drivers.modules.snmp']) reload(sys.modules['ironic.drivers.modules.snmp'])
# attempt to load the external 'scciclient' library, which is required by
# the optional drivers.modules.irmc module
scciclient = importutils.try_import('scciclient')
if not scciclient:
mock_scciclient = mock.MagicMock()
sys.modules['scciclient'] = mock_scciclient
sys.modules['scciclient.irmc'] = mock_scciclient.irmc
sys.modules['scciclient.irmc.scci'] = mock.MagicMock(
POWER_OFF=mock.sentinel.POWER_OFF,
POWER_ON=mock.sentinel.POWER_ON,
POWER_RESET=mock.sentinel.POWER_RESET)
# if anything has loaded the iRMC driver yet, reload it now that the
# external library has been mocked
if 'ironic.drivers.modules.irmc' in sys.modules:
reload(sys.modules['ironic.drivers.modules.irmc'])

2
setup.cfg Normal file → Executable file
View File

@ -49,6 +49,7 @@ ironic.drivers =
fake_ilo = ironic.drivers.fake:FakeIloDriver fake_ilo = ironic.drivers.fake:FakeIloDriver
fake_drac = ironic.drivers.fake:FakeDracDriver fake_drac = ironic.drivers.fake:FakeDracDriver
fake_snmp = ironic.drivers.fake:FakeSNMPDriver fake_snmp = ironic.drivers.fake:FakeSNMPDriver
fake_irmc = ironic.drivers.fake:FakeIRMCDriver
iscsi_ilo = ironic.drivers.ilo:IloVirtualMediaIscsiDriver iscsi_ilo = ironic.drivers.ilo:IloVirtualMediaIscsiDriver
pxe_ipmitool = ironic.drivers.pxe:PXEAndIPMIToolDriver pxe_ipmitool = ironic.drivers.pxe:PXEAndIPMIToolDriver
pxe_ipminative = ironic.drivers.pxe:PXEAndIPMINativeDriver pxe_ipminative = ironic.drivers.pxe:PXEAndIPMINativeDriver
@ -58,6 +59,7 @@ ironic.drivers =
pxe_ilo = ironic.drivers.pxe:PXEAndIloDriver pxe_ilo = ironic.drivers.pxe:PXEAndIloDriver
pxe_drac = ironic.drivers.drac:PXEDracDriver pxe_drac = ironic.drivers.drac:PXEDracDriver
pxe_snmp = ironic.drivers.pxe:PXEAndSNMPDriver pxe_snmp = ironic.drivers.pxe:PXEAndSNMPDriver
pxe_irmc = ironic.drivers.pxe:PXEAndIRMCDriver
ironic.database.migration_backend = ironic.database.migration_backend =
sqlalchemy = ironic.db.sqlalchemy.migration sqlalchemy = ironic.db.sqlalchemy.migration