Merge "Add iRMC Driver and its iRMC Power module"
This commit is contained in:
commit
00d58f9042
@ -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
|
||||||
|
@ -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
4
ironic/common/exception.py
Normal file → Executable 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
13
ironic/drivers/fake.py
Normal file → Executable 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()
|
||||||
|
0
ironic/drivers/modules/irmc/__init__.py
Normal file
0
ironic/drivers/modules/irmc/__init__.py
Normal file
148
ironic/drivers/modules/irmc/common.py
Normal file
148
ironic/drivers/modules/irmc/common.py
Normal 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
|
133
ironic/drivers/modules/irmc/power.py
Normal file
133
ironic/drivers/modules/irmc/power.py
Normal 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)
|
@ -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()
|
||||||
|
@ -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',
|
||||||
|
0
ironic/tests/drivers/irmc/__init__.py
Normal file
0
ironic/tests/drivers/irmc/__init__.py
Normal file
132
ironic/tests/drivers/irmc/test_common.py
Normal file
132
ironic/tests/drivers/irmc/test_common.py
Normal 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)
|
154
ironic/tests/drivers/irmc/test_power.py
Normal file
154
ironic/tests/drivers/irmc/test_power.py
Normal 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
20
ironic/tests/drivers/third_party_driver_mocks.py
Normal file → Executable 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
2
setup.cfg
Normal file → Executable 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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user