Add hardware inspection module for iRMC driver
This module enables iRMC out-of-band hardware inspection for FUJITSU PRIMERGY bare metal nodes having iRMC S4 and beyond. Change-Id: I8f406a9beb3fd3c01b15f764211ffd18494464f6 Closes-Bug: #1525108
This commit is contained in:
parent
e668b5e85b
commit
37590a8633
@ -1283,6 +1283,20 @@
|
||||
# (string value)
|
||||
#sensor_method=ipmitool
|
||||
|
||||
# SNMP protocol version, either "v1", "v2c" or "v3" (string
|
||||
# value)
|
||||
#snmp_version=v2c
|
||||
|
||||
# SNMP port (port value)
|
||||
#snmp_port=161
|
||||
|
||||
# SNMP community. Required for versions "v1" and "v2c" (string
|
||||
# value)
|
||||
#snmp_community=public
|
||||
|
||||
# SNMP security name. Required for version "v3" (string value)
|
||||
#snmp_security=<None>
|
||||
|
||||
|
||||
[keystone]
|
||||
|
||||
|
@ -37,6 +37,7 @@ from ironic.drivers.modules.ilo import power as ilo_power
|
||||
from ironic.drivers.modules import inspector
|
||||
from ironic.drivers.modules import ipminative
|
||||
from ironic.drivers.modules import ipmitool
|
||||
from ironic.drivers.modules.irmc import inspect as irmc_inspect
|
||||
from ironic.drivers.modules.irmc import management as irmc_management
|
||||
from ironic.drivers.modules.irmc import power as irmc_power
|
||||
from ironic.drivers.modules import iscsi_deploy
|
||||
@ -210,6 +211,7 @@ class FakeIRMCDriver(base.BaseDriver):
|
||||
self.power = irmc_power.IRMCPower()
|
||||
self.deploy = fake.FakeDeploy()
|
||||
self.management = irmc_management.IRMCManagement()
|
||||
self.inspect = irmc_inspect.IRMCInspect()
|
||||
|
||||
|
||||
class FakeVirtualBoxDriver(base.BaseDriver):
|
||||
|
@ -24,6 +24,7 @@ from ironic.drivers import base
|
||||
from ironic.drivers.modules import agent
|
||||
from ironic.drivers.modules import ipmitool
|
||||
from ironic.drivers.modules.irmc import boot
|
||||
from ironic.drivers.modules.irmc import inspect
|
||||
from ironic.drivers.modules.irmc import management
|
||||
from ironic.drivers.modules.irmc import power
|
||||
from ironic.drivers.modules import iscsi_deploy
|
||||
@ -50,6 +51,7 @@ class IRMCVirtualMediaIscsiDriver(base.BaseDriver):
|
||||
self.console = ipmitool.IPMIShellinaboxConsole()
|
||||
self.management = management.IRMCManagement()
|
||||
self.vendor = iscsi_deploy.VendorPassthru()
|
||||
self.inspect = inspect.IRMCInspect()
|
||||
|
||||
|
||||
class IRMCVirtualMediaAgentDriver(base.BaseDriver):
|
||||
@ -74,3 +76,4 @@ class IRMCVirtualMediaAgentDriver(base.BaseDriver):
|
||||
self.console = ipmitool.IPMIShellinaboxConsole()
|
||||
self.management = management.IRMCManagement()
|
||||
self.vendor = agent.AgentVendorInterface()
|
||||
self.inspect = inspect.IRMCInspect()
|
||||
|
@ -15,6 +15,7 @@
|
||||
"""
|
||||
Common functionalities shared between different iRMC modules.
|
||||
"""
|
||||
import six
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
@ -41,6 +42,18 @@ opts = [
|
||||
default='ipmitool',
|
||||
help=_('Sensor data retrieval method, either '
|
||||
'"ipmitool" or "scci"')),
|
||||
cfg.StrOpt('snmp_version',
|
||||
default='v2c',
|
||||
choices=['v1', 'v2c', 'v3'],
|
||||
help=_('SNMP protocol version, either "v1", "v2c" or "v3"')),
|
||||
cfg.PortOpt('snmp_port',
|
||||
default=161,
|
||||
help=_('SNMP port')),
|
||||
cfg.StrOpt('snmp_community',
|
||||
default='public',
|
||||
help=_('SNMP community. Required for versions "v1" and "v2c"')),
|
||||
cfg.StrOpt('snmp_security',
|
||||
help=_('SNMP security name. Required for version "v3"')),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
@ -65,6 +78,14 @@ OPTIONAL_PROPERTIES = {
|
||||
'irmc_sensor_method': _("Sensor data retrieval method; either "
|
||||
"'ipmitool' or 'scci'. The default value is "
|
||||
"'ipmitool'. Optional."),
|
||||
'irmc_snmp_version': _("SNMP protocol version; either 'v1', 'v2c', or "
|
||||
"'v3'. The default value is 'v2c'. Optional."),
|
||||
'irmc_snmp_port': _("SNMP port. The default is 161. Optional."),
|
||||
'irmc_snmp_community': _("SNMP community required for versions 'v1' and "
|
||||
"'v2c'. The default value is 'public'. "
|
||||
"Optional."),
|
||||
'irmc_snmp_security': _("SNMP security name required for version 'v3'. "
|
||||
"Optional."),
|
||||
}
|
||||
|
||||
COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy()
|
||||
@ -97,7 +118,7 @@ def parse_driver_info(node):
|
||||
# corresponding config names don't have 'irmc_' prefix
|
||||
opt = {param: info.get(param, CONF.irmc.get(param[len('irmc_'):]))
|
||||
for param in OPTIONAL_PROPERTIES}
|
||||
d_info = dict(list(req.items()) + list(opt.items()))
|
||||
d_info = dict(req, **opt)
|
||||
|
||||
error_msgs = []
|
||||
if (d_info['irmc_auth_method'].lower() not in ('basic', 'digest')):
|
||||
@ -112,6 +133,25 @@ def parse_driver_info(node):
|
||||
if d_info['irmc_sensor_method'].lower() not in ('ipmitool', 'scci'):
|
||||
error_msgs.append(
|
||||
_("'irmc_sensor_method' has unsupported value."))
|
||||
if d_info['irmc_snmp_version'].lower() not in ('v1', 'v2c', 'v3'):
|
||||
error_msgs.append(
|
||||
_("'irmc_snmp_version' has unsupported value."))
|
||||
if not isinstance(d_info['irmc_snmp_port'], int):
|
||||
error_msgs.append(
|
||||
_("'irmc_snmp_port' is not integer type."))
|
||||
if (d_info['irmc_snmp_version'].lower() in ('v1', 'v2c') and
|
||||
d_info['irmc_snmp_community'] and
|
||||
not isinstance(d_info['irmc_snmp_community'], six.string_types)):
|
||||
error_msgs.append(
|
||||
_("'irmc_snmp_community' is not string type."))
|
||||
if d_info['irmc_snmp_version'].lower() == 'v3':
|
||||
if d_info['irmc_snmp_security']:
|
||||
if not isinstance(d_info['irmc_snmp_security'], six.string_types):
|
||||
error_msgs.append(
|
||||
_("'irmc_snmp_security' is not string type."))
|
||||
else:
|
||||
error_msgs.append(
|
||||
_("'irmc_snmp_security' has to be set for SNMP version 3."))
|
||||
if error_msgs:
|
||||
msg = (_("The following type errors were encountered while parsing "
|
||||
"driver_info:\n%s") % "\n".join(error_msgs))
|
||||
@ -145,7 +185,7 @@ def get_irmc_client(node):
|
||||
|
||||
|
||||
def update_ipmi_properties(task):
|
||||
"""Update ipmi properties to node driver_info
|
||||
"""Update ipmi properties to node driver_info.
|
||||
|
||||
:param task: A task from TaskManager.
|
||||
"""
|
||||
|
187
ironic/drivers/modules/irmc/inspect.py
Normal file
187
ironic/drivers/modules/irmc/inspect.py
Normal file
@ -0,0 +1,187 @@
|
||||
# Copyright 2015 FUJITSU LIMITED
|
||||
#
|
||||
# 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 Inspect Interface
|
||||
"""
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import importutils
|
||||
|
||||
from ironic.common import exception
|
||||
from ironic.common.i18n import _
|
||||
from ironic.common.i18n import _LI
|
||||
from ironic.common.i18n import _LW
|
||||
from ironic.common import states
|
||||
from ironic.drivers import base
|
||||
from ironic.drivers.modules.irmc import common as irmc_common
|
||||
from ironic.drivers.modules import snmp
|
||||
from ironic import objects
|
||||
|
||||
scci = importutils.try_import('scciclient.irmc.scci')
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
"""
|
||||
SC2.mib: sc2UnitNodeClass returns NIC type.
|
||||
|
||||
sc2UnitNodeClass OBJECT-TYPE
|
||||
SYNTAX INTEGER
|
||||
{
|
||||
unknown(1),
|
||||
primary(2),
|
||||
secondary(3),
|
||||
management-blade(4),
|
||||
secondary-remote(5),
|
||||
secondary-remote-backup(6),
|
||||
baseboard-controller(7)
|
||||
}
|
||||
ACCESS read-only
|
||||
STATUS mandatory
|
||||
DESCRIPTION "Management node class:
|
||||
primary: local operating system interface
|
||||
secondary: local management controller LAN interface
|
||||
management-blade: management blade interface (in a blade server
|
||||
chassis)
|
||||
secondary-remote: remote management controller (in an RSB
|
||||
concentrator environment)
|
||||
secondary-remote-backup: backup remote management controller
|
||||
baseboard-controller: local baseboard management controller (BMC)"
|
||||
::= { sc2ManagementNodes 8 }
|
||||
"""
|
||||
NODE_CLASS_OID_VALUE = {
|
||||
'unknown': 1,
|
||||
'primary': 2,
|
||||
'secondary': 3,
|
||||
'management-blade': 4,
|
||||
'secondary-remote': 5,
|
||||
'secondary-remote-backup': 6,
|
||||
'baseboard-controller': 7
|
||||
}
|
||||
|
||||
NODE_CLASS_OID = '1.3.6.1.4.1.231.2.10.2.2.10.3.1.1.8.1'
|
||||
|
||||
"""
|
||||
SC2.mib: sc2UnitNodeMacAddress returns NIC MAC address
|
||||
|
||||
sc2UnitNodeMacAddress OBJECT-TYPE
|
||||
SYNTAX PhysAddress
|
||||
ACCESS read-only
|
||||
STATUS mandatory
|
||||
DESCRIPTION "Management node hardware (MAC) address"
|
||||
::= { sc2ManagementNodes 9 }
|
||||
"""
|
||||
MAC_ADDRESS_OID = '1.3.6.1.4.1.231.2.10.2.2.10.3.1.1.9.1'
|
||||
|
||||
|
||||
def _get_mac_addresses(node):
|
||||
"""Get mac addresses of the node.
|
||||
|
||||
:param node: node object.
|
||||
:raises: SNMPFailure if SNMP operation failed.
|
||||
:returns: a list of mac addresses.
|
||||
"""
|
||||
d_info = irmc_common.parse_driver_info(node)
|
||||
snmp_client = snmp.SNMPClient(d_info['irmc_address'],
|
||||
d_info['irmc_snmp_port'],
|
||||
d_info['irmc_snmp_version'],
|
||||
d_info['irmc_snmp_community'],
|
||||
d_info['irmc_snmp_security'])
|
||||
|
||||
node_classes = snmp_client.get_next(NODE_CLASS_OID)
|
||||
mac_addresses = snmp_client.get_next(MAC_ADDRESS_OID)
|
||||
|
||||
return [a for c, a in zip(node_classes, mac_addresses)
|
||||
if c == NODE_CLASS_OID_VALUE['primary']]
|
||||
|
||||
|
||||
def _inspect_hardware(node):
|
||||
"""Inspect the node and get hardware information.
|
||||
|
||||
:param node: node object.
|
||||
:raises: HardwareInspectionFailure, if unable to get essential
|
||||
hardware properties.
|
||||
:returns: a pair of dictionary and list, the dictionary contains
|
||||
keys as in IRMCInspect.ESSENTIAL_PROPERTIES and its inspected
|
||||
values, the list contains mac addresses.
|
||||
"""
|
||||
try:
|
||||
report = irmc_common.get_irmc_report(node)
|
||||
props = scci.get_essential_properties(
|
||||
report, IRMCInspect.ESSENTIAL_PROPERTIES)
|
||||
macs = _get_mac_addresses(node)
|
||||
except (scci.SCCIInvalidInputError,
|
||||
scci.SCCIClientError,
|
||||
exception.SNMPFailure) as e:
|
||||
error = (_("Inspection failed for node %(node_id)s "
|
||||
"with the following error: %(error)s") %
|
||||
{'node_id': node.uuid, 'error': e})
|
||||
raise exception.HardwareInspectionFailure(error=error)
|
||||
|
||||
return (props, macs)
|
||||
|
||||
|
||||
class IRMCInspect(base.InspectInterface):
|
||||
"""Interface for out of band inspection."""
|
||||
|
||||
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 inspection information.
|
||||
|
||||
This method validates whether the 'driver_info' property of the
|
||||
supplied node contains the required information for this driver.
|
||||
|
||||
: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 inspect_hardware(self, task):
|
||||
"""Inspect hardware.
|
||||
|
||||
Inspect hardware to obtain the essential hardware properties and
|
||||
mac addresses.
|
||||
|
||||
:param task: a task from TaskManager.
|
||||
:raises: HardwareInspectionFailure, if hardware inspection failed.
|
||||
:returns: states.MANAGEABLE, if hardware inspection succeeded.
|
||||
"""
|
||||
node = task.node
|
||||
(props, macs) = _inspect_hardware(node)
|
||||
node.properties = dict(node.properties, **props)
|
||||
node.save()
|
||||
|
||||
for mac in macs:
|
||||
try:
|
||||
new_port = objects.Port(task.context,
|
||||
address=mac, node_id=node.id)
|
||||
new_port.create()
|
||||
LOG.info(_LI("Port created for MAC address %(address)s "
|
||||
"for node %(node_uuid)s during inspection"),
|
||||
{'address': mac, 'node_uuid': node.uuid})
|
||||
except exception.MACAlreadyExists:
|
||||
LOG.warning(_LW("Port already existed for MAC address "
|
||||
"%(address)s for node %(node_uuid)s "
|
||||
"during inspection"),
|
||||
{'address': mac, 'node_uuid': node.uuid})
|
||||
|
||||
LOG.info(_LI("Node %s inspected"), node.uuid)
|
||||
return states.MANAGEABLE
|
@ -175,6 +175,34 @@ class SNMPClient(object):
|
||||
name, val = var_binds[0]
|
||||
return val
|
||||
|
||||
def get_next(self, oid):
|
||||
"""Use PySNMP to perform an SNMP GET NEXT operation on a table object.
|
||||
|
||||
:param oid: The OID of the object to get.
|
||||
:raises: SNMPFailure if an SNMP request fails.
|
||||
:returns: A list of values of the requested table object.
|
||||
"""
|
||||
try:
|
||||
results = self.cmd_gen.nextCmd(self._get_auth(),
|
||||
self._get_transport(),
|
||||
oid)
|
||||
except snmp_error.PySnmpError as e:
|
||||
raise exception.SNMPFailure(operation="GET_NEXT", error=e)
|
||||
|
||||
error_indication, error_status, error_index, var_bind_table = results
|
||||
|
||||
if error_indication:
|
||||
# SNMP engine-level error.
|
||||
raise exception.SNMPFailure(operation="GET_NEXT",
|
||||
error=error_indication)
|
||||
|
||||
if error_status:
|
||||
# SNMP PDU error.
|
||||
raise exception.SNMPFailure(operation="GET_NEXT",
|
||||
error=error_status.prettyPrint())
|
||||
|
||||
return [val for row in var_bind_table for name, val in row]
|
||||
|
||||
def set(self, oid, value):
|
||||
"""Use PySNMP to perform an SNMP SET operation on a single object.
|
||||
|
||||
|
@ -38,6 +38,7 @@ from ironic.drivers.modules.ilo import vendor as ilo_vendor
|
||||
from ironic.drivers.modules import inspector
|
||||
from ironic.drivers.modules import ipminative
|
||||
from ironic.drivers.modules import ipmitool
|
||||
from ironic.drivers.modules.irmc import inspect as irmc_inspect
|
||||
from ironic.drivers.modules.irmc import management as irmc_management
|
||||
from ironic.drivers.modules.irmc import power as irmc_power
|
||||
from ironic.drivers.modules import iscsi_deploy
|
||||
@ -263,6 +264,7 @@ class PXEAndIRMCDriver(base.BaseDriver):
|
||||
self.deploy = iscsi_deploy.ISCSIDeploy()
|
||||
self.management = irmc_management.IRMCManagement()
|
||||
self.vendor = iscsi_deploy.VendorPassthru()
|
||||
self.inspect = irmc_inspect.IRMCInspect()
|
||||
|
||||
|
||||
class PXEAndVirtualBoxDriver(base.BaseDriver):
|
||||
|
@ -48,6 +48,10 @@ class IRMCValidateParametersTestCase(db_base.DbTestCase):
|
||||
self.assertIsNotNone(info.get('irmc_port'))
|
||||
self.assertIsNotNone(info.get('irmc_auth_method'))
|
||||
self.assertIsNotNone(info.get('irmc_sensor_method'))
|
||||
self.assertIsNotNone(info.get('irmc_snmp_version'))
|
||||
self.assertIsNotNone(info.get('irmc_snmp_port'))
|
||||
self.assertIsNotNone(info.get('irmc_snmp_community'))
|
||||
self.assertFalse(info.get('irmc_snmp_security'))
|
||||
|
||||
def test_parse_driver_option_default(self):
|
||||
self.node.driver_info = {
|
||||
@ -107,6 +111,34 @@ class IRMCValidateParametersTestCase(db_base.DbTestCase):
|
||||
self.assertIn('irmc_password', str(e))
|
||||
self.assertIn('irmc_address', str(e))
|
||||
|
||||
def test_parse_driver_info_invalid_snmp_version(self):
|
||||
self.node.driver_info['irmc_snmp_version'] = 'v3x'
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
irmc_common.parse_driver_info, self.node)
|
||||
|
||||
def test_parse_driver_info_invalid_snmp_port(self):
|
||||
self.node.driver_info['irmc_snmp_port'] = '161'
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
irmc_common.parse_driver_info, self.node)
|
||||
|
||||
def test_parse_driver_info_invalid_snmp_community(self):
|
||||
self.node.driver_info['irmc_snmp_version'] = 'v2c'
|
||||
self.node.driver_info['irmc_snmp_community'] = 100
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
irmc_common.parse_driver_info, self.node)
|
||||
|
||||
def test_parse_driver_info_invalid_snmp_security(self):
|
||||
self.node.driver_info['irmc_snmp_version'] = 'v3'
|
||||
self.node.driver_info['irmc_snmp_security'] = 100
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
irmc_common.parse_driver_info, self.node)
|
||||
|
||||
def test_parse_driver_info_empty_snmp_security(self):
|
||||
self.node.driver_info['irmc_snmp_version'] = 'v3'
|
||||
self.node.driver_info['irmc_snmp_security'] = ''
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
irmc_common.parse_driver_info, self.node)
|
||||
|
||||
|
||||
class IRMCCommonMethodsTestCase(db_base.DbTestCase):
|
||||
|
||||
|
248
ironic/tests/unit/drivers/modules/irmc/test_inspect.py
Normal file
248
ironic/tests/unit/drivers/modules/irmc/test_inspect.py
Normal file
@ -0,0 +1,248 @@
|
||||
# Copyright 2015 FUJITSU LIMITED
|
||||
#
|
||||
# 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 Inspection Driver
|
||||
"""
|
||||
|
||||
import mock
|
||||
|
||||
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 inspect as irmc_inspect
|
||||
from ironic import objects
|
||||
from ironic.tests.unit.conductor import mgr_utils
|
||||
from ironic.tests.unit.db import base as db_base
|
||||
from ironic.tests.unit.db import utils as db_utils
|
||||
from ironic.tests.unit.drivers import third_party_driver_mock_specs \
|
||||
as mock_specs
|
||||
from ironic.tests.unit.objects import utils as obj_utils
|
||||
|
||||
INFO_DICT = db_utils.get_test_irmc_info()
|
||||
|
||||
|
||||
class IRMCInspectInternalMethodsTestCase(db_base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(IRMCInspectInternalMethodsTestCase, 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)
|
||||
|
||||
@mock.patch('ironic.drivers.modules.irmc.inspect.snmp.SNMPClient',
|
||||
spec_set=True, autospec=True)
|
||||
def test__get_mac_addresses(self, snmpclient_mock):
|
||||
snmpclient_mock.return_value = mock.Mock(
|
||||
**{'get_next.side_effect': [[2, 2, 7],
|
||||
['aa:aa:aa:aa:aa:aa',
|
||||
'bb:bb:bb:bb:bb:bb',
|
||||
'cc:cc:cc:cc:cc:cc']]})
|
||||
inspected_macs = ['aa:aa:aa:aa:aa:aa', 'bb:bb:bb:bb:bb:bb']
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
result = irmc_inspect._get_mac_addresses(task.node)
|
||||
self.assertEqual(inspected_macs, result)
|
||||
|
||||
@mock.patch.object(irmc_inspect, '_get_mac_addresses', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(irmc_inspect, 'scci',
|
||||
spec_set=mock_specs.SCCICLIENT_IRMC_SCCI_SPEC)
|
||||
@mock.patch.object(irmc_common, 'get_irmc_report', spec_set=True,
|
||||
autospec=True)
|
||||
def test__inspect_hardware(
|
||||
self, get_irmc_report_mock, scci_mock, _get_mac_addresses_mock):
|
||||
inspected_props = {
|
||||
'memory_mb': '1024',
|
||||
'local_gb': 10,
|
||||
'cpus': 2,
|
||||
'cpu_arch': 'x86_64'}
|
||||
inspected_macs = ['aa:aa:aa:aa:aa:aa', 'bb:bb:bb:bb:bb:bb']
|
||||
report = 'fake_report'
|
||||
get_irmc_report_mock.return_value = report
|
||||
scci_mock.get_essential_properties.return_value = inspected_props
|
||||
_get_mac_addresses_mock.return_value = inspected_macs
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
result = irmc_inspect._inspect_hardware(task.node)
|
||||
|
||||
get_irmc_report_mock.assert_called_once_with(task.node)
|
||||
scci_mock.get_essential_properties.assert_called_once_with(
|
||||
report, irmc_inspect.IRMCInspect.ESSENTIAL_PROPERTIES)
|
||||
self.assertEqual((inspected_props, inspected_macs), result)
|
||||
|
||||
@mock.patch.object(irmc_inspect, '_get_mac_addresses', spec_set=True,
|
||||
autospec=True)
|
||||
@mock.patch.object(irmc_inspect, 'scci',
|
||||
spec_set=mock_specs.SCCICLIENT_IRMC_SCCI_SPEC)
|
||||
@mock.patch.object(irmc_common, 'get_irmc_report', spec_set=True,
|
||||
autospec=True)
|
||||
def test__inspect_hardware_exception(
|
||||
self, get_irmc_report_mock, scci_mock, _get_mac_addresses_mock):
|
||||
report = 'fake_report'
|
||||
get_irmc_report_mock.return_value = report
|
||||
side_effect = exception.SNMPFailure("fake exception")
|
||||
scci_mock.get_essential_properties.side_effect = side_effect
|
||||
irmc_inspect.scci.SCCIInvalidInputError = Exception
|
||||
irmc_inspect.scci.SCCIClientError = Exception
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
self.assertRaises(exception.HardwareInspectionFailure,
|
||||
irmc_inspect._inspect_hardware,
|
||||
task.node)
|
||||
get_irmc_report_mock.assert_called_once_with(task.node)
|
||||
self.assertFalse(_get_mac_addresses_mock.called)
|
||||
|
||||
|
||||
class IRMCInspectTestCase(db_base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(IRMCInspectTestCase, 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', spec_set=True,
|
||||
autospec=True)
|
||||
def test_validate(self, parse_driver_info_mock):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
task.driver.power.validate(task)
|
||||
parse_driver_info_mock.assert_called_once_with(task.node)
|
||||
|
||||
@mock.patch.object(irmc_common, 'parse_driver_info', spec_set=True,
|
||||
autospec=True)
|
||||
def test_validate_fail(self, parse_driver_info_mock):
|
||||
side_effect = exception.InvalidParameterValue("Invalid Input")
|
||||
parse_driver_info_mock.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.object(irmc_inspect.LOG, 'info', spec_set=True, autospec=True)
|
||||
@mock.patch('ironic.drivers.modules.irmc.inspect.objects.Port',
|
||||
spec_set=True, autospec=True)
|
||||
@mock.patch.object(irmc_inspect, '_inspect_hardware', spec_set=True,
|
||||
autospec=True)
|
||||
def test_inspect_hardware(self, _inspect_hardware_mock, port_mock,
|
||||
info_mock):
|
||||
inspected_props = {
|
||||
'memory_mb': '1024',
|
||||
'local_gb': 10,
|
||||
'cpus': 2,
|
||||
'cpu_arch': 'x86_64'}
|
||||
inspected_macs = ['aa:aa:aa:aa:aa:aa', 'bb:bb:bb:bb:bb:bb']
|
||||
_inspect_hardware_mock.return_value = (inspected_props,
|
||||
inspected_macs)
|
||||
new_port_mock1 = mock.MagicMock(spec=objects.Port)
|
||||
new_port_mock2 = mock.MagicMock(spec=objects.Port)
|
||||
|
||||
port_mock.side_effect = [new_port_mock1, new_port_mock2]
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
result = task.driver.inspect.inspect_hardware(task)
|
||||
|
||||
node_id = task.node.id
|
||||
_inspect_hardware_mock.assert_called_once_with(task.node)
|
||||
|
||||
# note (naohirot):
|
||||
# as of mock 1.2, assert_has_calls has a bug which returns
|
||||
# "AssertionError: Calls not found." if mock_calls has class
|
||||
# method call such as below:
|
||||
|
||||
# AssertionError: Calls not found.
|
||||
# Expected: [call.list_by_node_id(
|
||||
# <oslo_context.context.RequestContext object at 0x7f1a34f8c0d0>,
|
||||
# 1)]
|
||||
# Actual: [call.list_by_node_id(
|
||||
# <oslo_context.context.RequestContext object at 0x7f1a34f8c0d0>,
|
||||
# 1)]
|
||||
#
|
||||
# workaround, remove class method call from mock_calls list
|
||||
del port_mock.mock_calls[0]
|
||||
port_mock.assert_has_calls([
|
||||
# workaround, comment out class method call from expected list
|
||||
# mock.call.list_by_node_id(task.context, node_id),
|
||||
mock.call(task.context, address=inspected_macs[0],
|
||||
node_id=node_id),
|
||||
mock.call(task.context, address=inspected_macs[1],
|
||||
node_id=node_id)
|
||||
])
|
||||
new_port_mock1.create.assert_called_once_with()
|
||||
new_port_mock2.create.assert_called_once_with()
|
||||
|
||||
self.assertTrue(info_mock.called)
|
||||
task.node.refresh()
|
||||
self.assertEqual(inspected_props, task.node.properties)
|
||||
self.assertEqual(states.MANAGEABLE, result)
|
||||
|
||||
@mock.patch('ironic.objects.Port', spec_set=True, autospec=True)
|
||||
@mock.patch.object(irmc_inspect, '_inspect_hardware', spec_set=True,
|
||||
autospec=True)
|
||||
def test_inspect_hardware_inspect_exception(
|
||||
self, _inspect_hardware_mock, port_mock):
|
||||
side_effect = exception.HardwareInspectionFailure("fake exception")
|
||||
_inspect_hardware_mock.side_effect = side_effect
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
self.assertRaises(exception.HardwareInspectionFailure,
|
||||
task.driver.inspect.inspect_hardware,
|
||||
task)
|
||||
self.assertFalse(port_mock.called)
|
||||
|
||||
@mock.patch.object(irmc_inspect.LOG, 'warn', spec_set=True, autospec=True)
|
||||
@mock.patch('ironic.objects.Port', spec_set=True, autospec=True)
|
||||
@mock.patch.object(irmc_inspect, '_inspect_hardware', spec_set=True,
|
||||
autospec=True)
|
||||
def test_inspect_hardware_mac_already_exist(
|
||||
self, _inspect_hardware_mock, port_mock, warn_mock):
|
||||
inspected_props = {
|
||||
'memory_mb': '1024',
|
||||
'local_gb': 10,
|
||||
'cpus': 2,
|
||||
'cpu_arch': 'x86_64'}
|
||||
inspected_macs = ['aa:aa:aa:aa:aa:aa', 'bb:bb:bb:bb:bb:bb']
|
||||
_inspect_hardware_mock.return_value = (inspected_props,
|
||||
inspected_macs)
|
||||
side_effect = exception.MACAlreadyExists("fake exception")
|
||||
new_port_mock = port_mock.return_value
|
||||
new_port_mock.create.side_effect = side_effect
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
result = task.driver.inspect.inspect_hardware(task)
|
||||
|
||||
_inspect_hardware_mock.assert_called_once_with(task.node)
|
||||
self.assertTrue(port_mock.call_count, 2)
|
||||
task.node.refresh()
|
||||
self.assertEqual(inspected_props, task.node.properties)
|
||||
self.assertEqual(states.MANAGEABLE, result)
|
@ -101,6 +101,19 @@ class SNMPClientTestCase(base.TestCase):
|
||||
mock_cmdgenerator.getCmd.assert_called_once_with(mock.ANY, mock.ANY,
|
||||
self.oid)
|
||||
|
||||
@mock.patch.object(snmp.SNMPClient, '_get_transport', autospec=True)
|
||||
@mock.patch.object(snmp.SNMPClient, '_get_auth', autospec=True)
|
||||
def test_get_next(self, mock_auth, mock_transport, mock_cmdgen):
|
||||
var_bind = (self.oid, self.value)
|
||||
mock_cmdgenerator = mock_cmdgen.return_value
|
||||
mock_cmdgenerator.nextCmd.return_value = (
|
||||
"", None, 0, [[var_bind, var_bind]])
|
||||
client = snmp.SNMPClient(self.address, self.port, snmp.SNMP_V3)
|
||||
val = client.get_next(self.oid)
|
||||
self.assertEqual([self.value, self.value], val)
|
||||
mock_cmdgenerator.nextCmd.assert_called_once_with(mock.ANY, mock.ANY,
|
||||
self.oid)
|
||||
|
||||
@mock.patch.object(snmp.SNMPClient, '_get_transport', autospec=True)
|
||||
@mock.patch.object(snmp.SNMPClient, '_get_auth', autospec=True)
|
||||
def test_get_err_transport(self, mock_auth, mock_transport, mock_cmdgen):
|
||||
@ -113,6 +126,19 @@ class SNMPClientTestCase(base.TestCase):
|
||||
self.assertRaises(exception.SNMPFailure, client.get, self.oid)
|
||||
self.assertFalse(mock_cmdgenerator.getCmd.called)
|
||||
|
||||
@mock.patch.object(snmp.SNMPClient, '_get_transport', autospec=True)
|
||||
@mock.patch.object(snmp.SNMPClient, '_get_auth', autospec=True)
|
||||
def test_get_next_err_transport(self, mock_auth, mock_transport,
|
||||
mock_cmdgen):
|
||||
mock_transport.side_effect = snmp_error.PySnmpError
|
||||
var_bind = (self.oid, self.value)
|
||||
mock_cmdgenerator = mock_cmdgen.return_value
|
||||
mock_cmdgenerator.nextCmd.return_value = ("engine error", None, 0,
|
||||
[[var_bind, var_bind]])
|
||||
client = snmp.SNMPClient(self.address, self.port, snmp.SNMP_V3)
|
||||
self.assertRaises(exception.SNMPFailure, client.get_next, self.oid)
|
||||
self.assertFalse(mock_cmdgenerator.nextCmd.called)
|
||||
|
||||
@mock.patch.object(snmp.SNMPClient, '_get_transport', autospec=True)
|
||||
@mock.patch.object(snmp.SNMPClient, '_get_auth', autospec=True)
|
||||
def test_get_err_engine(self, mock_auth, mock_transport, mock_cmdgen):
|
||||
@ -125,6 +151,18 @@ class SNMPClientTestCase(base.TestCase):
|
||||
mock_cmdgenerator.getCmd.assert_called_once_with(mock.ANY, mock.ANY,
|
||||
self.oid)
|
||||
|
||||
@mock.patch.object(snmp.SNMPClient, '_get_transport', autospec=True)
|
||||
@mock.patch.object(snmp.SNMPClient, '_get_auth', autospec=True)
|
||||
def test_get_next_err_engine(self, mock_auth, mock_transport, mock_cmdgen):
|
||||
var_bind = (self.oid, self.value)
|
||||
mock_cmdgenerator = mock_cmdgen.return_value
|
||||
mock_cmdgenerator.nextCmd.return_value = ("engine error", None, 0,
|
||||
[[var_bind, var_bind]])
|
||||
client = snmp.SNMPClient(self.address, self.port, snmp.SNMP_V3)
|
||||
self.assertRaises(exception.SNMPFailure, client.get_next, self.oid)
|
||||
mock_cmdgenerator.nextCmd.assert_called_once_with(mock.ANY, mock.ANY,
|
||||
self.oid)
|
||||
|
||||
@mock.patch.object(snmp.SNMPClient, '_get_transport', autospec=True)
|
||||
@mock.patch.object(snmp.SNMPClient, '_get_auth', autospec=True)
|
||||
def test_set(self, mock_auth, mock_transport, mock_cmdgen):
|
||||
|
@ -49,6 +49,7 @@ class IRMCVirtualMediaIscsiTestCase(testtools.TestCase):
|
||||
self.assertIsInstance(driver.management,
|
||||
irmc.management.IRMCManagement)
|
||||
self.assertIsInstance(driver.vendor, iscsi_deploy.VendorPassthru)
|
||||
self.assertIsInstance(driver.inspect, irmc.inspect.IRMCInspect)
|
||||
|
||||
@mock.patch.object(irmc.importutils, 'try_import')
|
||||
def test___init___try_import_exception(self, mock_try_import):
|
||||
@ -91,6 +92,7 @@ class IRMCVirtualMediaAgentTestCase(testtools.TestCase):
|
||||
self.assertIsInstance(driver.management,
|
||||
irmc.management.IRMCManagement)
|
||||
self.assertIsInstance(driver.vendor, irmc.agent.AgentVendorInterface)
|
||||
self.assertIsInstance(driver.inspect, irmc.inspect.IRMCInspect)
|
||||
|
||||
@mock.patch.object(irmc.importutils, 'try_import')
|
||||
def test___init___try_import_exception(self, mock_try_import):
|
||||
|
@ -101,6 +101,7 @@ SCCICLIENT_IRMC_SCCI_SPEC = (
|
||||
'get_sensor_data',
|
||||
'get_virtual_cd_set_params_cmd',
|
||||
'get_virtual_fd_set_params_cmd',
|
||||
'get_essential_properties',
|
||||
)
|
||||
|
||||
ONEVIEWCLIENT_SPEC = (
|
||||
|
@ -0,0 +1,3 @@
|
||||
---
|
||||
features:
|
||||
- Adds out-Of-band inspection support for iRMC driver.
|
Loading…
Reference in New Issue
Block a user