diff options
author | Nisha Agarwal <agarwalnisha1980@gmail.com> | 2017-03-22 00:38:44 -0700 |
---|---|---|
committer | Ilya Etingof <etingof@gmail.com> | 2018-11-03 09:09:56 +0100 |
commit | 41d3356571eafd7f3192d05d7c7523ac295ff5c0 (patch) | |
tree | 54daaeb8536e8d230e35c44c69eea89608b70933 /ironic | |
parent | 325b0a6bd42d9eda531ace1185ecdc5e2ceb8614 (diff) | |
download | ironic-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
Diffstat (limited to 'ironic')
-rw-r--r-- | ironic/drivers/modules/deploy_utils.py | 28 | ||||
-rw-r--r-- | ironic/drivers/modules/ilo/inspect.py | 30 | ||||
-rw-r--r-- | ironic/drivers/modules/redfish/inspect.py | 192 | ||||
-rw-r--r-- | ironic/drivers/redfish.py | 9 | ||||
-rw-r--r-- | ironic/tests/unit/drivers/modules/ilo/test_inspect.py | 44 | ||||
-rw-r--r-- | ironic/tests/unit/drivers/modules/redfish/test_inspect.py | 189 | ||||
-rw-r--r-- | ironic/tests/unit/drivers/modules/redfish/test_management.py | 3 | ||||
-rw-r--r-- | ironic/tests/unit/drivers/modules/redfish/test_power.py | 3 | ||||
-rw-r--r-- | ironic/tests/unit/drivers/modules/test_deploy_utils.py | 33 | ||||
-rw-r--r-- | ironic/tests/unit/drivers/test_redfish.py | 6 | ||||
-rw-r--r-- | ironic/tests/unit/drivers/third_party_driver_mock_specs.py | 5 | ||||
-rw-r--r-- | ironic/tests/unit/drivers/third_party_driver_mocks.py | 7 |
12 files changed, 479 insertions, 70 deletions
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 |