summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNisha Agarwal <agarwalnisha1980@gmail.com>2017-03-22 00:38:44 -0700
committerIlya Etingof <etingof@gmail.com>2018-11-03 09:09:56 +0100
commit41d3356571eafd7f3192d05d7c7523ac295ff5c0 (patch)
tree54daaeb8536e8d230e35c44c69eea89608b70933
parent325b0a6bd42d9eda531ace1185ecdc5e2ceb8614 (diff)
downloadironic-41d3356571eafd7f3192d05d7c7523ac295ff5c0.tar.gz
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
-rw-r--r--doc/source/admin/drivers/redfish.rst19
-rw-r--r--ironic/drivers/modules/deploy_utils.py28
-rw-r--r--ironic/drivers/modules/ilo/inspect.py30
-rw-r--r--ironic/drivers/modules/redfish/inspect.py192
-rw-r--r--ironic/drivers/redfish.py9
-rw-r--r--ironic/tests/unit/drivers/modules/ilo/test_inspect.py44
-rw-r--r--ironic/tests/unit/drivers/modules/redfish/test_inspect.py189
-rw-r--r--ironic/tests/unit/drivers/modules/redfish/test_management.py3
-rw-r--r--ironic/tests/unit/drivers/modules/redfish/test_power.py3
-rw-r--r--ironic/tests/unit/drivers/modules/test_deploy_utils.py33
-rw-r--r--ironic/tests/unit/drivers/test_redfish.py6
-rw-r--r--ironic/tests/unit/drivers/third_party_driver_mock_specs.py5
-rw-r--r--ironic/tests/unit/drivers/third_party_driver_mocks.py7
-rw-r--r--releasenotes/notes/add-redfish-inspect-interface-1577e70167f24ae4.yaml7
-rw-r--r--setup.cfg1
15 files changed, 505 insertions, 71 deletions
diff --git a/doc/source/admin/drivers/redfish.rst b/doc/source/admin/drivers/redfish.rst
index a0adac60c..bcfb68321 100644
--- a/doc/source/admin/drivers/redfish.rst
+++ b/doc/source/admin/drivers/redfish.rst
@@ -21,7 +21,8 @@ Enabling the Redfish driver
===========================
#. 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::
[DEFAULT]
@@ -29,6 +30,7 @@ Enabling the Redfish driver
enabled_hardware_types = ipmi,redfish
enabled_power_interfaces = ipmitool,redfish
enabled_management_interfaces = ipmitool,redfish
+ enabled_inspect_interfaces = inspector,redfish
#. 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
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/
.. _Sushy: https://git.openstack.org/cgit/openstack/sushy
.. _TLS: https://en.wikipedia.org/wiki/Transport_Layer_Security
diff --git a/ironic/drivers/modules/deploy_utils.py b/ironic/drivers/modules/deploy_utils.py
index ba7990117..21cb895eb 100644
--- a/ironic/drivers/modules/deploy_utils.py
+++ b/ironic/drivers/modules/deploy_utils.py
@@ -579,6 +579,34 @@ def get_single_nic_with_vif_port_id(task):
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):
"""Get the list of cached clean steps from the agent.
diff --git a/ironic/drivers/modules/ilo/inspect.py b/ironic/drivers/modules/ilo/inspect.py
index 6884a675e..231a0d90b 100644
--- a/ironic/drivers/modules/ilo/inspect.py
+++ b/ironic/drivers/modules/ilo/inspect.py
@@ -22,8 +22,8 @@ from ironic.common import states
from ironic.common import utils
from ironic.conductor import utils as conductor_utils
from ironic.drivers import base
+from ironic.drivers.modules import deploy_utils
from ironic.drivers.modules.ilo import common as ilo_common
-from ironic import objects
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'}
-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):
"""Inspects the node and get essential scheduling properties
@@ -270,7 +244,7 @@ class IloInspect(base.InspectInterface):
task.node.save()
# 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 "
"%(properties)s",
diff --git a/ironic/drivers/modules/redfish/inspect.py b/ironic/drivers/modules/redfish/inspect.py
new file mode 100644
index 000000000..8fc92a15e
--- /dev/null
+++ b/ironic/drivers/modules/redfish/inspect.py
@@ -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
diff --git a/ironic/drivers/redfish.py b/ironic/drivers/redfish.py
index ecf7c789a..9e5c93331 100644
--- a/ironic/drivers/redfish.py
+++ b/ironic/drivers/redfish.py
@@ -14,6 +14,9 @@
# under the License.
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 power as redfish_power
@@ -30,3 +33,9 @@ class RedfishHardware(generic.GenericHardware):
def supported_power_interfaces(self):
"""List of supported power interfaces."""
return [redfish_power.RedfishPower]
+
+ @property
+ def supported_inspect_interfaces(self):
+ """List of supported power interfaces."""
+ return [redfish_inspect.RedfishInspect, inspector.Inspector,
+ noop.NoInspect]
diff --git a/ironic/tests/unit/drivers/modules/ilo/test_inspect.py b/ironic/tests/unit/drivers/modules/ilo/test_inspect.py
index 8c73d5c76..2efc104f0 100644
--- a/ironic/tests/unit/drivers/modules/ilo/test_inspect.py
+++ b/ironic/tests/unit/drivers/modules/ilo/test_inspect.py
@@ -23,10 +23,10 @@ from ironic.common import states
from ironic.common import utils
from ironic.conductor import task_manager
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 inspect as ilo_inspect
from ironic.drivers.modules.ilo import power as ilo_power
-from ironic import objects
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,
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)
@mock.patch.object(ilo_inspect, '_get_essential_properties', spec_set=True,
autospec=True)
@@ -88,7 +88,7 @@ class IloInspectTestCase(test_common.BaseIloTest):
spec_set=True, autospec=True)
@mock.patch.object(ilo_inspect, '_get_capabilities', spec_set=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)
@mock.patch.object(ilo_inspect, '_get_essential_properties', spec_set=True,
autospec=True)
@@ -131,7 +131,7 @@ class IloInspectTestCase(test_common.BaseIloTest):
@mock.patch.object(ilo_inspect, '_get_capabilities', spec_set=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)
@mock.patch.object(ilo_inspect, '_get_essential_properties', spec_set=True,
autospec=True)
@@ -170,7 +170,7 @@ class IloInspectTestCase(test_common.BaseIloTest):
@mock.patch.object(ilo_inspect, '_get_capabilities', spec_set=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)
@mock.patch.object(ilo_inspect, '_get_essential_properties', spec_set=True,
autospec=True)
@@ -209,7 +209,7 @@ class IloInspectTestCase(test_common.BaseIloTest):
@mock.patch.object(ilo_inspect, '_get_capabilities', spec_set=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)
@mock.patch.object(ilo_inspect, '_get_essential_properties', spec_set=True,
autospec=True)
@@ -256,38 +256,6 @@ class IloInspectTestCase(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):
ilo_mock = mock.MagicMock(spec=['get_essential_properties'])
properties = {'memory_mb': '512', 'local_gb': '10',
diff --git a/ironic/tests/unit/drivers/modules/redfish/test_inspect.py b/ironic/tests/unit/drivers/modules/redfish/test_inspect.py
new file mode 100644
index 000000000..b70585638
--- /dev/null
+++ b/ironic/tests/unit/drivers/modules/redfish/test_inspect.py
@@ -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)
diff --git a/ironic/tests/unit/drivers/modules/redfish/test_management.py b/ironic/tests/unit/drivers/modules/redfish/test_management.py
index d42e4a4e7..f35ffce5e 100644
--- a/ironic/tests/unit/drivers/modules/redfish/test_management.py
+++ b/ironic/tests/unit/drivers/modules/redfish/test_management.py
@@ -41,7 +41,8 @@ class RedfishManagementTestCase(db_base.DbTestCase):
super(RedfishManagementTestCase, self).setUp()
self.config(enabled_hardware_types=['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.context, driver='redfish', driver_info=INFO_DICT)
diff --git a/ironic/tests/unit/drivers/modules/redfish/test_power.py b/ironic/tests/unit/drivers/modules/redfish/test_power.py
index 2c8b964f6..2318b6217 100644
--- a/ironic/tests/unit/drivers/modules/redfish/test_power.py
+++ b/ironic/tests/unit/drivers/modules/redfish/test_power.py
@@ -41,7 +41,8 @@ class RedfishPowerTestCase(db_base.DbTestCase):
super(RedfishPowerTestCase, self).setUp()
self.config(enabled_hardware_types=['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.context, driver='redfish', driver_info=INFO_DICT)
diff --git a/ironic/tests/unit/drivers/modules/test_deploy_utils.py b/ironic/tests/unit/drivers/modules/test_deploy_utils.py
index 3cecc5ec1..6f9354856 100644
--- a/ironic/tests/unit/drivers/modules/test_deploy_utils.py
+++ b/ironic/tests/unit/drivers/modules/test_deploy_utils.py
@@ -44,6 +44,7 @@ from ironic.drivers.modules import image_cache
from ironic.drivers.modules import pxe
from ironic.drivers.modules.storage import cinder
from ironic.drivers import utils as driver_utils
+from ironic import objects
from ironic.tests import base as tests_base
from ironic.tests.unit.db import base as db_base
from ironic.tests.unit.db import utils as db_utils
@@ -1307,6 +1308,38 @@ class OtherFunctionTestCase(db_base.DbTestCase):
self.assertRaises(exception.InvalidParameterValue,
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):
diff --git a/ironic/tests/unit/drivers/test_redfish.py b/ironic/tests/unit/drivers/test_redfish.py
index bddd63ce4..073df7d77 100644
--- a/ironic/tests/unit/drivers/test_redfish.py
+++ b/ironic/tests/unit/drivers/test_redfish.py
@@ -17,6 +17,7 @@ from ironic.conductor import task_manager
from ironic.drivers.modules import iscsi_deploy
from ironic.drivers.modules import noop
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 power as redfish_power
from ironic.tests.unit.db import base as db_base
@@ -29,11 +30,14 @@ class RedfishHardwareTestCase(db_base.DbTestCase):
super(RedfishHardwareTestCase, self).setUp()
self.config(enabled_hardware_types=['redfish'],
enabled_power_interfaces=['redfish'],
- enabled_management_interfaces=['redfish'])
+ enabled_management_interfaces=['redfish'],
+ enabled_inspect_interfaces=['redfish'])
def test_default_interfaces(self):
node = obj_utils.create_test_node(self.context, driver='redfish')
with task_manager.acquire(self.context, node.id) as task:
+ self.assertIsInstance(task.driver.inspect,
+ redfish_inspect.RedfishInspect)
self.assertIsInstance(task.driver.management,
redfish_mgmt.RedfishManagement)
self.assertIsInstance(task.driver.power,
diff --git a/ironic/tests/unit/drivers/third_party_driver_mock_specs.py b/ironic/tests/unit/drivers/third_party_driver_mock_specs.py
index a5b1c6b5a..11fc9646d 100644
--- a/ironic/tests/unit/drivers/third_party_driver_mock_specs.py
+++ b/ironic/tests/unit/drivers/third_party_driver_mock_specs.py
@@ -146,6 +146,11 @@ SUSHY_CONSTANTS_SPEC = (
'BOOT_SOURCE_ENABLED_ONCE',
'BOOT_SOURCE_MODE_BIOS',
'BOOT_SOURCE_MODE_UEFI',
+ 'PROCESSOR_ARCH_x86',
+ 'PROCESSOR_ARCH_IA_64',
+ 'PROCESSOR_ARCH_ARM',
+ 'PROCESSOR_ARCH_MIPS',
+ 'PROCESSOR_ARCH_OEM',
)
XCLARITY_SPEC = (
diff --git a/ironic/tests/unit/drivers/third_party_driver_mocks.py b/ironic/tests/unit/drivers/third_party_driver_mocks.py
index fe955ba39..fa6031291 100644
--- a/ironic/tests/unit/drivers/third_party_driver_mocks.py
+++ b/ironic/tests/unit/drivers/third_party_driver_mocks.py
@@ -219,7 +219,12 @@ if not sushy:
BOOT_SOURCE_ENABLED_CONTINUOUS='continuous',
BOOT_SOURCE_ENABLED_ONCE='once',
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
diff --git a/releasenotes/notes/add-redfish-inspect-interface-1577e70167f24ae4.yaml b/releasenotes/notes/add-redfish-inspect-interface-1577e70167f24ae4.yaml
new file mode 100644
index 000000000..f5d066a89
--- /dev/null
+++ b/releasenotes/notes/add-redfish-inspect-interface-1577e70167f24ae4.yaml
@@ -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.
diff --git a/setup.cfg b/setup.cfg
index 1cf0f0e87..3c234debe 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -88,6 +88,7 @@ ironic.hardware.interfaces.inspect =
inspector = ironic.drivers.modules.inspector:Inspector
irmc = ironic.drivers.modules.irmc.inspect:IRMCInspect
no-inspect = ironic.drivers.modules.noop:NoInspect
+ redfish = ironic.drivers.modules.redfish.inspect:RedfishInspect
ironic.hardware.interfaces.management =
cimc = ironic.drivers.modules.cimc.management:CIMCManagement