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:
Naohiro Tamura 2015-06-26 00:00:42 +09:00
parent e668b5e85b
commit 37590a8633
13 changed files with 602 additions and 2 deletions

View File

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

View File

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

View File

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

View File

@ -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.
"""

View 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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

@ -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 = (

View File

@ -0,0 +1,3 @@
---
features:
- Adds out-Of-band inspection support for iRMC driver.