Add Redfish inspect interface

Add the InspectInterface to the `redfish` hardware type. This enables
OOB inspection in ironic.

Story: 1668487
Task: 10571

Co-Authored-By: Ilya Etingof <etingof@gmail.com>
Depends-On: I3a79f2afe6c838636df554ee468f8f2e0cf0859e
Depends-On: Ieb374f8cabb0418bb2680fdab690446346fc354f
Change-Id: Ie3167569db060620fe0b9784bc7d7a854f2ef754
This commit is contained in:
Nisha Agarwal 2017-03-22 00:38:44 -07:00 committed by Ilya Etingof
parent 325b0a6bd4
commit 41d3356571
15 changed files with 505 additions and 71 deletions

View File

@ -21,7 +21,8 @@ Enabling the Redfish driver
=========================== ===========================
#. Add ``redfish`` to the list of ``enabled_hardware_types``, #. Add ``redfish`` to the list of ``enabled_hardware_types``,
``enabled_power_interfaces`` and ``enabled_management_interfaces`` ``enabled_power_interfaces``, ``enabled_management_interfaces`` and
``enabled_inspect_interfaces``
in ``/etc/ironic/ironic.conf``. For example:: in ``/etc/ironic/ironic.conf``. For example::
[DEFAULT] [DEFAULT]
@ -29,6 +30,7 @@ Enabling the Redfish driver
enabled_hardware_types = ipmi,redfish enabled_hardware_types = ipmi,redfish
enabled_power_interfaces = ipmitool,redfish enabled_power_interfaces = ipmitool,redfish
enabled_management_interfaces = ipmitool,redfish enabled_management_interfaces = ipmitool,redfish
enabled_inspect_interfaces = inspector,redfish
#. Restart the ironic conductor service:: #. Restart the ironic conductor service::
@ -104,6 +106,21 @@ bare metal node as well as set it to either Legacy BIOS or UEFI.
it remains the responsibility of the operator to configure proper it remains the responsibility of the operator to configure proper
boot mode to their bare metal nodes. boot mode to their bare metal nodes.
Out-Of-Band inspection
^^^^^^^^^^^^^^^^^^^^^^
The ``redfish`` hardware type can inspect the bare metal node by querying
Redfish BMC. This process if quick and reliable compared to the way
how the ``inspector`` hardware type works e.g. booting bare metal node into
the introspection ramdisk.
.. note::
The ``redfish`` inspect interface largely relies on the optional parts
of the Redfish specification. Not all Redfish-compliant BMCs might serve
the required information, in which case bare metal node inspection would
fail.
.. _Redfish: http://redfish.dmtf.org/ .. _Redfish: http://redfish.dmtf.org/
.. _Sushy: https://git.openstack.org/cgit/openstack/sushy .. _Sushy: https://git.openstack.org/cgit/openstack/sushy
.. _TLS: https://en.wikipedia.org/wiki/Transport_Layer_Security .. _TLS: https://en.wikipedia.org/wiki/Transport_Layer_Security

View File

@ -579,6 +579,34 @@ def get_single_nic_with_vif_port_id(task):
return port.address return port.address
def create_ports_if_not_exist(task, macs):
"""Create ironic ports for the mac addresses.
Creates ironic ports for the mac addresses returned with inspection
or as requested by operator.
:param task: a TaskManager instance.
:param macs: A dictionary of port numbers to mac addresses
returned by node inspection.
"""
node = task.node
for port_num, mac in macs.items():
port_dict = {'address': mac, 'node_id': node.id}
port = objects.Port(task.context, **port_dict)
try:
port.create()
LOG.info(_("Port %(port_num)s created for MAC address %(address)s "
"for node %(node)s"),
{'address': mac, 'node': node.uuid, 'port_num': port_num})
except exception.MACAlreadyExists:
LOG.warning(_("Port %(port_num)s already exists for MAC address"
"%(address)s for node %(node)s"),
{'address': mac, 'node': node.uuid,
'port_num': port_num})
def agent_get_clean_steps(task, interface=None, override_priorities=None): def agent_get_clean_steps(task, interface=None, override_priorities=None):
"""Get the list of cached clean steps from the agent. """Get the list of cached clean steps from the agent.

View File

@ -22,8 +22,8 @@ from ironic.common import states
from ironic.common import utils from ironic.common import utils
from ironic.conductor import utils as conductor_utils from ironic.conductor import utils as conductor_utils
from ironic.drivers import base from ironic.drivers import base
from ironic.drivers.modules import deploy_utils
from ironic.drivers.modules.ilo import common as ilo_common from ironic.drivers.modules.ilo import common as ilo_common
from ironic import objects
ilo_error = importutils.try_import('proliantutils.exception') ilo_error = importutils.try_import('proliantutils.exception')
@ -48,32 +48,6 @@ CAPABILITIES_KEYS = {'secure_boot', 'rom_firmware_version',
'nvdimm_n', 'logical_nvdimm_n', 'persistent_memory'} 'nvdimm_n', 'logical_nvdimm_n', 'persistent_memory'}
def _create_ports_if_not_exist(task, macs):
"""Create ironic ports for the mac addresses.
Creates ironic ports for the mac addresses returned with inspection
or as requested by operator.
:param task: a TaskManager instance.
:param macs: A dictionary of port numbers to mac addresses
returned by node inspection.
"""
node = task.node
for mac in macs.values():
port_dict = {'address': mac, 'node_id': node.id}
port = objects.Port(task.context, **port_dict)
try:
port.create()
LOG.info("Port created for MAC address %(address)s for node "
"%(node)s", {'address': mac, 'node': node.uuid})
except exception.MACAlreadyExists:
LOG.warning("Port already exists for MAC address %(address)s "
"for node %(node)s",
{'address': mac, 'node': node.uuid})
def _get_essential_properties(node, ilo_object): def _get_essential_properties(node, ilo_object):
"""Inspects the node and get essential scheduling properties """Inspects the node and get essential scheduling properties
@ -270,7 +244,7 @@ class IloInspect(base.InspectInterface):
task.node.save() task.node.save()
# Create ports for the nics detected. # Create ports for the nics detected.
_create_ports_if_not_exist(task, result['macs']) deploy_utils.create_ports_if_not_exist(task, result['macs'])
LOG.debug("Node properties for %(node)s are updated as " LOG.debug("Node properties for %(node)s are updated as "
"%(properties)s", "%(properties)s",

View File

@ -0,0 +1,192 @@
# 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.
"""
Redfish Inspect Interface
"""
from oslo_log import log
from oslo_utils import importutils
from oslo_utils import units
from ironic.common import exception
from ironic.common.i18n import _
from ironic.common import states
from ironic.drivers import base
from ironic.drivers.modules import deploy_utils
from ironic.drivers.modules.redfish import utils as redfish_utils
LOG = log.getLogger(__name__)
sushy = importutils.try_import('sushy')
if sushy:
CPU_ARCH_MAP = {
sushy.PROCESSOR_ARCH_x86: 'x86_64',
sushy.PROCESSOR_ARCH_IA_64: 'ia64',
sushy.PROCESSOR_ARCH_ARM: 'arm',
sushy.PROCESSOR_ARCH_MIPS: 'mips',
sushy.PROCESSOR_ARCH_OEM: 'oem'
}
class RedfishInspect(base.InspectInterface):
def __init__(self):
"""Initialize the Redfish inspection interface.
:raises: DriverLoadError if the driver can't be loaded due to
missing dependencies
"""
super(RedfishInspect, self).__init__()
if not sushy:
raise exception.DriverLoadError(
driver='redfish',
reason=_('Unable to import the sushy library'))
def get_properties(self):
"""Return the properties of the interface.
:returns: dictionary of <property name>:<property description> entries.
"""
return redfish_utils.COMMON_PROPERTIES.copy()
def validate(self, task):
"""Validate the driver-specific Node deployment info.
This method validates whether the 'driver_info' properties of
the task's node contains the required information for this
interface to function.
This method is often executed synchronously in API requests, so it
should not conduct long-running checks.
:param task: A TaskManager instance containing the node to act on.
:raises: InvalidParameterValue on malformed parameter(s)
:raises: MissingParameterValue on missing parameter(s)
"""
redfish_utils.parse_driver_info(task.node)
def inspect_hardware(self, task):
"""Inspect hardware to get the hardware properties.
Inspects hardware to get the essential properties.
It fails if any of the essential properties
are not received from the node.
:param task: a TaskManager instance.
:raises: HardwareInspectionFailure if essential properties
could not be retrieved successfully.
:returns: The resulting state of inspection.
"""
system = redfish_utils.get_system(task.node)
# get the essential properties and update the node properties
# with it.
inspected_properties = task.node.properties
if system.memory_summary and system.memory_summary.size_gib:
inspected_properties['memory_mb'] = str(
system.memory_summary.size_gib * units.Ki)
if system.processors and system.processors.summary:
cpus, arch = system.processors.summary
if cpus:
inspected_properties['cpus'] = cpus
if arch:
try:
inspected_properties['cpu_arch'] = CPU_ARCH_MAP[arch]
except KeyError:
LOG.warning(
_("Unknown CPU arch %(arch)s discovered "
"for Node %(node)s"), {'node': task.node.uuid,
'arch': arch})
simple_storage_size = 0
try:
if (system.simple_storage and
system.simple_storage.disks_sizes_bytes):
simple_storage_size = [
size for size in system.simple_storage.disks_sizes_bytes
if size >= 4 * units.Gi
] or [0]
simple_storage_size = simple_storage_size[0]
except sushy.SushyError:
LOG.info(
_("No simple storage information discovered "
"for Node %(node)s"), {'node': task.node.uuid})
storage_size = 0
try:
if system.storage and system.storage.volumes_sizes_bytes:
storage_size = [
size for size in system.storage.volumes_sizes_bytes
if size >= 4 * units.Gi
] or [0]
storage_size = storage_size[0]
except sushy.SushyError:
LOG.info(_("No storage volume information discovered "
"for Node %(node)s"), {'node': task.node.uuid})
local_gb = max(simple_storage_size, storage_size)
# Note(deray): Convert the received size to GiB and reduce the
# value by 1 GB as consumers like Ironic requires the ``local_gb``
# to be returned 1 less than actual size.
local_gb = max(0, int(local_gb / units.Gi - 1))
if local_gb:
inspected_properties['local_gb'] = str(local_gb)
else:
LOG.warning(_("Could not provide a valid storage size configured "
"for Node %(node)s"), {'node': task.node.uuid})
valid_keys = self.ESSENTIAL_PROPERTIES
missing_keys = valid_keys - set(inspected_properties)
if missing_keys:
error = (_('Failed to discover the following properties: '
'%(missing_keys)s on node %(node)s'),
{'missing_keys': ', '.join(missing_keys),
'node': task.node.uuid})
raise exception.HardwareInspectionFailure(error=error)
task.node.properties = inspected_properties
task.node.save()
LOG.debug(_("Node properties for %(node)s are updated as "
"%(properties)s"),
{'properties': inspected_properties,
'node': task.node.uuid})
if (system.ethernet_interfaces and
system.ethernet_interfaces.eth_summary):
macs = system.ethernet_interfaces.eth_summary
# Create ports for the nics detected.
deploy_utils.create_ports_if_not_exist(task, macs)
else:
LOG.info(_("No NIC information discovered "
"for Node %(node)s"), {'node': task.node.uuid})
LOG.info(_("Node %(node)s inspected."), {'node': task.node.uuid})
return states.MANAGEABLE

View File

@ -14,6 +14,9 @@
# under the License. # under the License.
from ironic.drivers import generic from ironic.drivers import generic
from ironic.drivers.modules import inspector
from ironic.drivers.modules import noop
from ironic.drivers.modules.redfish import inspect as redfish_inspect
from ironic.drivers.modules.redfish import management as redfish_mgmt from ironic.drivers.modules.redfish import management as redfish_mgmt
from ironic.drivers.modules.redfish import power as redfish_power from ironic.drivers.modules.redfish import power as redfish_power
@ -30,3 +33,9 @@ class RedfishHardware(generic.GenericHardware):
def supported_power_interfaces(self): def supported_power_interfaces(self):
"""List of supported power interfaces.""" """List of supported power interfaces."""
return [redfish_power.RedfishPower] return [redfish_power.RedfishPower]
@property
def supported_inspect_interfaces(self):
"""List of supported power interfaces."""
return [redfish_inspect.RedfishInspect, inspector.Inspector,
noop.NoInspect]

View File

@ -23,10 +23,10 @@ from ironic.common import states
from ironic.common import utils from ironic.common import utils
from ironic.conductor import task_manager from ironic.conductor import task_manager
from ironic.conductor import utils as conductor_utils from ironic.conductor import utils as conductor_utils
from ironic.drivers.modules import deploy_utils
from ironic.drivers.modules.ilo import common as ilo_common from ironic.drivers.modules.ilo import common as ilo_common
from ironic.drivers.modules.ilo import inspect as ilo_inspect from ironic.drivers.modules.ilo import inspect as ilo_inspect
from ironic.drivers.modules.ilo import power as ilo_power from ironic.drivers.modules.ilo import power as ilo_power
from ironic import objects
from ironic.tests.unit.drivers.modules.ilo import test_common from ironic.tests.unit.drivers.modules.ilo import test_common
@ -51,7 +51,7 @@ class IloInspectTestCase(test_common.BaseIloTest):
@mock.patch.object(ilo_inspect, '_get_capabilities', spec_set=True, @mock.patch.object(ilo_inspect, '_get_capabilities', spec_set=True,
autospec=True) autospec=True)
@mock.patch.object(ilo_inspect, '_create_ports_if_not_exist', @mock.patch.object(deploy_utils, 'create_ports_if_not_exist',
spec_set=True, autospec=True) spec_set=True, autospec=True)
@mock.patch.object(ilo_inspect, '_get_essential_properties', spec_set=True, @mock.patch.object(ilo_inspect, '_get_essential_properties', spec_set=True,
autospec=True) autospec=True)
@ -88,7 +88,7 @@ class IloInspectTestCase(test_common.BaseIloTest):
spec_set=True, autospec=True) spec_set=True, autospec=True)
@mock.patch.object(ilo_inspect, '_get_capabilities', spec_set=True, @mock.patch.object(ilo_inspect, '_get_capabilities', spec_set=True,
autospec=True) autospec=True)
@mock.patch.object(ilo_inspect, '_create_ports_if_not_exist', @mock.patch.object(deploy_utils, 'create_ports_if_not_exist',
spec_set=True, autospec=True) spec_set=True, autospec=True)
@mock.patch.object(ilo_inspect, '_get_essential_properties', spec_set=True, @mock.patch.object(ilo_inspect, '_get_essential_properties', spec_set=True,
autospec=True) autospec=True)
@ -131,7 +131,7 @@ class IloInspectTestCase(test_common.BaseIloTest):
@mock.patch.object(ilo_inspect, '_get_capabilities', spec_set=True, @mock.patch.object(ilo_inspect, '_get_capabilities', spec_set=True,
autospec=True) autospec=True)
@mock.patch.object(ilo_inspect, '_create_ports_if_not_exist', @mock.patch.object(deploy_utils, 'create_ports_if_not_exist',
spec_set=True, autospec=True) spec_set=True, autospec=True)
@mock.patch.object(ilo_inspect, '_get_essential_properties', spec_set=True, @mock.patch.object(ilo_inspect, '_get_essential_properties', spec_set=True,
autospec=True) autospec=True)
@ -170,7 +170,7 @@ class IloInspectTestCase(test_common.BaseIloTest):
@mock.patch.object(ilo_inspect, '_get_capabilities', spec_set=True, @mock.patch.object(ilo_inspect, '_get_capabilities', spec_set=True,
autospec=True) autospec=True)
@mock.patch.object(ilo_inspect, '_create_ports_if_not_exist', @mock.patch.object(deploy_utils, 'create_ports_if_not_exist',
spec_set=True, autospec=True) spec_set=True, autospec=True)
@mock.patch.object(ilo_inspect, '_get_essential_properties', spec_set=True, @mock.patch.object(ilo_inspect, '_get_essential_properties', spec_set=True,
autospec=True) autospec=True)
@ -209,7 +209,7 @@ class IloInspectTestCase(test_common.BaseIloTest):
@mock.patch.object(ilo_inspect, '_get_capabilities', spec_set=True, @mock.patch.object(ilo_inspect, '_get_capabilities', spec_set=True,
autospec=True) autospec=True)
@mock.patch.object(ilo_inspect, '_create_ports_if_not_exist', @mock.patch.object(deploy_utils, 'create_ports_if_not_exist',
spec_set=True, autospec=True) spec_set=True, autospec=True)
@mock.patch.object(ilo_inspect, '_get_essential_properties', spec_set=True, @mock.patch.object(ilo_inspect, '_get_essential_properties', spec_set=True,
autospec=True) autospec=True)
@ -256,38 +256,6 @@ class IloInspectTestCase(test_common.BaseIloTest):
class TestInspectPrivateMethods(test_common.BaseIloTest): class TestInspectPrivateMethods(test_common.BaseIloTest):
@mock.patch.object(ilo_inspect.LOG, 'info', spec_set=True, autospec=True)
@mock.patch.object(objects, 'Port', spec_set=True, autospec=True)
def test__create_ports_if_not_exist(self, port_mock, log_mock):
macs = {'Port 1': 'aa:aa:aa:aa:aa:aa', 'Port 2': 'bb:bb:bb:bb:bb:bb'}
node_id = self.node.id
port_dict1 = {'address': 'aa:aa:aa:aa:aa:aa', 'node_id': node_id}
port_dict2 = {'address': 'bb:bb:bb:bb:bb:bb', 'node_id': node_id}
port_obj1, port_obj2 = mock.MagicMock(), mock.MagicMock()
port_mock.side_effect = [port_obj1, port_obj2]
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
ilo_inspect._create_ports_if_not_exist(task, macs)
self.assertTrue(log_mock.called)
expected_calls = [mock.call(task.context, **port_dict1),
mock.call(task.context, **port_dict2)]
port_mock.assert_has_calls(expected_calls, any_order=True)
port_obj1.create.assert_called_once_with()
port_obj2.create.assert_called_once_with()
@mock.patch.object(ilo_inspect.LOG, 'warning',
spec_set=True, autospec=True)
@mock.patch.object(objects.Port, 'create', spec_set=True, autospec=True)
def test__create_ports_if_not_exist_mac_exception(self,
create_mock,
log_mock):
create_mock.side_effect = exception.MACAlreadyExists('f')
macs = {'Port 1': 'aa:aa:aa:aa:aa:aa', 'Port 2': 'bb:bb:bb:bb:bb:bb'}
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
ilo_inspect._create_ports_if_not_exist(task, macs)
self.assertEqual(2, log_mock.call_count)
def test__get_essential_properties_ok(self): def test__get_essential_properties_ok(self):
ilo_mock = mock.MagicMock(spec=['get_essential_properties']) ilo_mock = mock.MagicMock(spec=['get_essential_properties'])
properties = {'memory_mb': '512', 'local_gb': '10', properties = {'memory_mb': '512', 'local_gb': '10',

View File

@ -0,0 +1,189 @@
# Copyright 2017 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from oslo_utils import importutils
from oslo_utils import units
from ironic.common import exception
from ironic.conductor import task_manager
from ironic.drivers.modules import deploy_utils
from ironic.drivers.modules.redfish import utils as redfish_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.objects import utils as obj_utils
sushy = importutils.try_import('sushy')
INFO_DICT = db_utils.get_test_redfish_info()
class MockedSushyError(Exception):
pass
class RedfishInspectTestCase(db_base.DbTestCase):
def setUp(self):
super(RedfishInspectTestCase, self).setUp()
self.config(enabled_hardware_types=['redfish'],
enabled_power_interfaces=['redfish'],
enabled_management_interfaces=['redfish'],
enabled_inspect_interfaces=['redfish'])
self.node = obj_utils.create_test_node(
self.context, driver='redfish', driver_info=INFO_DICT)
def init_system_mock(self, system_mock, **properties):
system_mock.reset()
system_mock.memory_summary.size_gib = 2
system_mock.processors.summary = '8', 'MIPS'
system_mock.simple_storage.disks_sizes_bytes = (
1 * units.Gi, units.Gi * 3, units.Gi * 5)
system_mock.storage.volumes_sizes_bytes = (
2 * units.Gi, units.Gi * 4, units.Gi * 6)
system_mock.ethernet_interfaces.eth_summary = {
'1': '00:11:22:33:44:55', '2': '66:77:88:99:AA:BB'
}
return system_mock
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 redfish_utils.COMMON_PROPERTIES:
self.assertIn(prop, properties)
@mock.patch.object(redfish_utils, 'parse_driver_info', autospec=True)
def test_validate(self, mock_parse_driver_info):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
task.driver.management.validate(task)
mock_parse_driver_info.assert_called_once_with(task.node)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
@mock.patch.object(deploy_utils, 'create_ports_if_not_exist',
autospec=True)
def test_inspect_hardware_ok(self, mock_create_ports_if_not_exist,
mock_get_system):
expected_properties = {
'cpu_arch': 'mips', 'cpus': '8',
'local_gb': '4', 'memory_mb': '2048'
}
system = self.init_system_mock(mock_get_system.return_value)
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
task.driver.inspect.inspect_hardware(task)
mock_create_ports_if_not_exist.assert_called_once_with(
task, system.ethernet_interfaces.eth_summary)
mock_get_system.assert_called_once_with(task.node)
self.assertEqual(expected_properties, task.node.properties)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
def test_inspect_hardware_fail_missing_cpu(self, mock_get_system):
system_mock = self.init_system_mock(mock_get_system.return_value)
system_mock.processors.summary = None, None
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
task.node.properties.pop('cpu_arch')
self.assertRaises(exception.HardwareInspectionFailure,
task.driver.inspect.inspect_hardware, task)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
def test_inspect_hardware_ignore_missing_cpu(self, mock_get_system):
system_mock = self.init_system_mock(mock_get_system.return_value)
system_mock.processors.summary = None, None
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
expected_properties = {
'cpu_arch': 'x86_64', 'cpus': '8',
'local_gb': '4', 'memory_mb': '2048'
}
task.driver.inspect.inspect_hardware(task)
self.assertEqual(expected_properties, task.node.properties)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
def test_inspect_hardware_fail_missing_local_gb(self, mock_get_system):
system_mock = self.init_system_mock(mock_get_system.return_value)
system_mock.simple_storage.disks_sizes_bytes = None
system_mock.storage.volumes_sizes_bytes = None
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
task.node.properties.pop('local_gb')
self.assertRaises(exception.HardwareInspectionFailure,
task.driver.inspect.inspect_hardware, task)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
def test_inspect_hardware_ignore_missing_local_gb(self, mock_get_system):
system_mock = self.init_system_mock(mock_get_system.return_value)
system_mock.simple_storage.disks_sizes_bytes = None
system_mock.storage.volumes_sizes_bytes = None
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
expected_properties = {
'cpu_arch': 'mips', 'cpus': '8',
'local_gb': '10', 'memory_mb': '2048'
}
task.driver.inspect.inspect_hardware(task)
self.assertEqual(expected_properties, task.node.properties)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
def test_inspect_hardware_fail_missing_memory_mb(self, mock_get_system):
system_mock = self.init_system_mock(mock_get_system.return_value)
system_mock.memory_summary.size_gib = None
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
task.node.properties.pop('memory_mb')
self.assertRaises(exception.HardwareInspectionFailure,
task.driver.inspect.inspect_hardware, task)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
def test_inspect_hardware_ignore_missing_memory_mb(self, mock_get_system):
system_mock = self.init_system_mock(mock_get_system.return_value)
system_mock.memory_summary.size_gib = None
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
expected_properties = {
'cpu_arch': 'mips', 'cpus': '8',
'local_gb': '4', 'memory_mb': '4096'
}
task.driver.inspect.inspect_hardware(task)
self.assertEqual(expected_properties, task.node.properties)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
@mock.patch.object(deploy_utils, 'create_ports_if_not_exist',
autospec=True)
def test_inspect_hardware_ignore_missing_nics(
self, mock_create_ports_if_not_exist, mock_get_system):
system_mock = self.init_system_mock(mock_get_system.return_value)
system_mock.ethernet_interfaces.eth_summary = None
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
task.driver.inspect.inspect_hardware(task)
self.assertFalse(mock_create_ports_if_not_exist.called)

View File

@ -41,7 +41,8 @@ class RedfishManagementTestCase(db_base.DbTestCase):
super(RedfishManagementTestCase, self).setUp() super(RedfishManagementTestCase, self).setUp()
self.config(enabled_hardware_types=['redfish'], self.config(enabled_hardware_types=['redfish'],
enabled_power_interfaces=['redfish'], enabled_power_interfaces=['redfish'],
enabled_management_interfaces=['redfish']) enabled_management_interfaces=['redfish'],
enabled_inspect_interfaces=['redfish'])
self.node = obj_utils.create_test_node( self.node = obj_utils.create_test_node(
self.context, driver='redfish', driver_info=INFO_DICT) self.context, driver='redfish', driver_info=INFO_DICT)

View File

@ -41,7 +41,8 @@ class RedfishPowerTestCase(db_base.DbTestCase):
super(RedfishPowerTestCase, self).setUp() super(RedfishPowerTestCase, self).setUp()
self.config(enabled_hardware_types=['redfish'], self.config(enabled_hardware_types=['redfish'],
enabled_power_interfaces=['redfish'], enabled_power_interfaces=['redfish'],
enabled_management_interfaces=['redfish']) enabled_management_interfaces=['redfish'],
enabled_inspect_interfaces=['redfish'])
self.node = obj_utils.create_test_node( self.node = obj_utils.create_test_node(
self.context, driver='redfish', driver_info=INFO_DICT) self.context, driver='redfish', driver_info=INFO_DICT)

View File

@ -44,6 +44,7 @@ from ironic.drivers.modules import image_cache
from ironic.drivers.modules import pxe from ironic.drivers.modules import pxe
from ironic.drivers.modules.storage import cinder from ironic.drivers.modules.storage import cinder
from ironic.drivers import utils as driver_utils from ironic.drivers import utils as driver_utils
from ironic import objects
from ironic.tests import base as tests_base from ironic.tests import base as tests_base
from ironic.tests.unit.db import base as db_base from ironic.tests.unit.db import base as db_base
from ironic.tests.unit.db import utils as db_utils from ironic.tests.unit.db import utils as db_utils
@ -1307,6 +1308,38 @@ class OtherFunctionTestCase(db_base.DbTestCase):
self.assertRaises(exception.InvalidParameterValue, self.assertRaises(exception.InvalidParameterValue,
utils.get_ironic_api_url) utils.get_ironic_api_url)
@mock.patch.object(utils.LOG, 'info', spec_set=True, autospec=True)
@mock.patch.object(objects, 'Port', spec_set=True, autospec=True)
def test_create_ports_if_not_exist(self, port_mock, log_mock):
macs = {'Port 1': 'aa:aa:aa:aa:aa:aa', 'Port 2': 'bb:bb:bb:bb:bb:bb'}
node_id = self.node.id
port_dict1 = {'address': 'aa:aa:aa:aa:aa:aa', 'node_id': node_id}
port_dict2 = {'address': 'bb:bb:bb:bb:bb:bb', 'node_id': node_id}
port_obj1, port_obj2 = mock.MagicMock(), mock.MagicMock()
port_mock.side_effect = [port_obj1, port_obj2]
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
utils.create_ports_if_not_exist(task, macs)
self.assertTrue(log_mock.called)
expected_calls = [mock.call(task.context, **port_dict1),
mock.call(task.context, **port_dict2)]
port_mock.assert_has_calls(expected_calls, any_order=True)
port_obj1.create.assert_called_once_with()
port_obj2.create.assert_called_once_with()
@mock.patch.object(utils.LOG, 'warning',
spec_set=True, autospec=True)
@mock.patch.object(objects.Port, 'create', spec_set=True, autospec=True)
def test_create_ports_if_not_exist_mac_exception(self,
create_mock,
log_mock):
create_mock.side_effect = exception.MACAlreadyExists('f')
macs = {'Port 1': 'aa:aa:aa:aa:aa:aa', 'Port 2': 'bb:bb:bb:bb:bb:bb'}
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
utils.create_ports_if_not_exist(task, macs)
self.assertEqual(2, log_mock.call_count)
class GetSingleNicTestCase(db_base.DbTestCase): class GetSingleNicTestCase(db_base.DbTestCase):

View File

@ -17,6 +17,7 @@ from ironic.conductor import task_manager
from ironic.drivers.modules import iscsi_deploy from ironic.drivers.modules import iscsi_deploy
from ironic.drivers.modules import noop from ironic.drivers.modules import noop
from ironic.drivers.modules import pxe from ironic.drivers.modules import pxe
from ironic.drivers.modules.redfish import inspect as redfish_inspect
from ironic.drivers.modules.redfish import management as redfish_mgmt from ironic.drivers.modules.redfish import management as redfish_mgmt
from ironic.drivers.modules.redfish import power as redfish_power from ironic.drivers.modules.redfish import power as redfish_power
from ironic.tests.unit.db import base as db_base from ironic.tests.unit.db import base as db_base
@ -29,11 +30,14 @@ class RedfishHardwareTestCase(db_base.DbTestCase):
super(RedfishHardwareTestCase, self).setUp() super(RedfishHardwareTestCase, self).setUp()
self.config(enabled_hardware_types=['redfish'], self.config(enabled_hardware_types=['redfish'],
enabled_power_interfaces=['redfish'], enabled_power_interfaces=['redfish'],
enabled_management_interfaces=['redfish']) enabled_management_interfaces=['redfish'],
enabled_inspect_interfaces=['redfish'])
def test_default_interfaces(self): def test_default_interfaces(self):
node = obj_utils.create_test_node(self.context, driver='redfish') node = obj_utils.create_test_node(self.context, driver='redfish')
with task_manager.acquire(self.context, node.id) as task: with task_manager.acquire(self.context, node.id) as task:
self.assertIsInstance(task.driver.inspect,
redfish_inspect.RedfishInspect)
self.assertIsInstance(task.driver.management, self.assertIsInstance(task.driver.management,
redfish_mgmt.RedfishManagement) redfish_mgmt.RedfishManagement)
self.assertIsInstance(task.driver.power, self.assertIsInstance(task.driver.power,

View File

@ -146,6 +146,11 @@ SUSHY_CONSTANTS_SPEC = (
'BOOT_SOURCE_ENABLED_ONCE', 'BOOT_SOURCE_ENABLED_ONCE',
'BOOT_SOURCE_MODE_BIOS', 'BOOT_SOURCE_MODE_BIOS',
'BOOT_SOURCE_MODE_UEFI', 'BOOT_SOURCE_MODE_UEFI',
'PROCESSOR_ARCH_x86',
'PROCESSOR_ARCH_IA_64',
'PROCESSOR_ARCH_ARM',
'PROCESSOR_ARCH_MIPS',
'PROCESSOR_ARCH_OEM',
) )
XCLARITY_SPEC = ( XCLARITY_SPEC = (

View File

@ -219,7 +219,12 @@ if not sushy:
BOOT_SOURCE_ENABLED_CONTINUOUS='continuous', BOOT_SOURCE_ENABLED_CONTINUOUS='continuous',
BOOT_SOURCE_ENABLED_ONCE='once', BOOT_SOURCE_ENABLED_ONCE='once',
BOOT_SOURCE_MODE_BIOS='bios', BOOT_SOURCE_MODE_BIOS='bios',
BOOT_SOURCE_MODE_UEFI='uefi' BOOT_SOURCE_MODE_UEFI='uefi',
PROCESSOR_ARCH_x86='x86 or x86-64',
PROCESSOR_ARCH_IA_64='Intel Itanium',
PROCESSOR_ARCH_ARM='ARM',
PROCESSOR_ARCH_MIPS='MIPS',
PROCESSOR_ARCH_OEM='OEM-defined',
) )
sys.modules['sushy'] = sushy sys.modules['sushy'] = sushy

View File

@ -0,0 +1,7 @@
---
features:
- |
Adds out-of-band inspection support to ``redfish`` hardware type'.
Successful inspection populates mandatory properties: "cpus",
"local_gb", "cpu_arch", "memory_mb" and creates ironic ports
for inspected nodes.

View File

@ -88,6 +88,7 @@ ironic.hardware.interfaces.inspect =
inspector = ironic.drivers.modules.inspector:Inspector inspector = ironic.drivers.modules.inspector:Inspector
irmc = ironic.drivers.modules.irmc.inspect:IRMCInspect irmc = ironic.drivers.modules.irmc.inspect:IRMCInspect
no-inspect = ironic.drivers.modules.noop:NoInspect no-inspect = ironic.drivers.modules.noop:NoInspect
redfish = ironic.drivers.modules.redfish.inspect:RedfishInspect
ironic.hardware.interfaces.management = ironic.hardware.interfaces.management =
cimc = ironic.drivers.modules.cimc.management:CIMCManagement cimc = ironic.drivers.modules.cimc.management:CIMCManagement