From 7adbf11bb843fc1e80d6c9dc62ac200561c3a9f8 Mon Sep 17 00:00:00 2001 From: Nguyen Hung Phuong Date: Fri, 6 Jan 2017 17:44:02 +0700 Subject: [PATCH] Add additional capabilities discovery for iRMC driver This update enhances iRMC out-of-band hardware inspection for FUJITSU PRIMERGY bare metal nodes having iRMC S4 and beyond. The capabilities are server_model, rom_firmware_version, pci_gpu_devices, trusted_boot and irmc_firmware_version. Co-authored-By: Nguyen Van Trung Change-Id: I1958e18a5b9d933e2aa405b200bac7717f146611 Closes-Bug: #1637422 --- doc/source/admin/drivers/irmc.rst | 97 ++++- driver-requirements.txt | 2 +- etc/ironic/ironic.conf.sample | 9 + ironic/conf/irmc.py | 9 + ironic/drivers/modules/irmc/inspect.py | 82 ++++- .../unit/drivers/modules/irmc/test_inspect.py | 348 +++++++++++++++++- .../drivers/third_party_driver_mock_specs.py | 1 + ...itional-capabilities-4fd72ba50d05676c.yaml | 9 + 8 files changed, 534 insertions(+), 23 deletions(-) create mode 100644 releasenotes/notes/irmc-additional-capabilities-4fd72ba50d05676c.yaml diff --git a/doc/source/admin/drivers/irmc.rst b/doc/source/admin/drivers/irmc.rst index 88d7a01a8a..4ab9aa4926 100644 --- a/doc/source/admin/drivers/irmc.rst +++ b/doc/source/admin/drivers/irmc.rst @@ -26,7 +26,7 @@ Prerequisites * Install `python-scciclient `_ and `pysnmp `_ packages:: - $ pip install "python-scciclient>=0.5.0" pysnmp + $ pip install "python-scciclient>=0.6.0" pysnmp Hardware Type ============= @@ -62,10 +62,10 @@ hardware interfaces: Supports ``irmc``, ``inspector``, and ``no-inspect``. The default is ``irmc``. - .. note:: - `Ironic Inspector `_ - needs to be present and configured to use ``inspector`` as the - inspect interface. +.. note:: + `Ironic Inspector `_ + needs to be present and configured to use ``inspector`` as the + inspect interface. * management Supports only ``irmc``. @@ -491,6 +491,93 @@ The drivers support the PCI controllers, Fibrechannel Cards, Converged Network Adapters supported by `Fujitsu ServerView Virtual-IO Manager `_. +Hardware Inspection Support +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``irmc`` hardware type (only ``irmc`` inspect interface is supported) and +the following iRMC classic drivers support Hardware Inspection: + +* ``pxe_irmc`` +* ``iscsi_irmc`` +* ``agent_irmc`` + +.. note:: + SNMP requires being enabled in ServerView® iRMC S4 Web Server(Network + Settings\SNMP section). + +Configuration +~~~~~~~~~~~~~ + +The Hardware Inspection Support in the iRMC drivers requires the following +configuration: + +* It is necessary to set ironic configuration with ``gpu_ids`` option + in ``[irmc]`` section. + + ``gpu_ids`` is a list of ``/`` where: + + - ````: 4 hexadecimal digits starts with '0x'. + - ````: 4 hexadecimal digits starts with '0x'. + + Here is a sample value for gpu_ids:: + + gpu_ids = 0x1000/0x0079,0x2100/0x0080 + +* It is necessary that pyghmi version >= 1.0.22 and pysnmp version >= 4.2.3 + are used on the conductor. The latest version of pyghmi can + be downloaded from `here `__ + and pysnmp can be downloaded from `here + `__. + +Supported properties +~~~~~~~~~~~~~~~~~~~~ + +The inspection process will discover the following essential properties +(properties required for scheduling deployment): + +* ``memory_mb``: memory size + +* ``cpus``: number of cpus + +* ``cpu_arch``: cpu architecture + +* ``local_gb``: disk size + +Inspection can also discover the following extra capabilities for iRMC +drivers: + +* ``irmc_firmware_version``: iRMC firmware version + +* ``rom_firmware_version``: ROM firmware version + +* ``trusted_boot``: The flag whether TPM(Trusted Platform Module) is + supported by the server. The possible values are 'True' or 'False'. + +* ``server_model``: server model + +* ``pci_gpu_devices``: number of gpu devices connected to the bare metal. + +.. note:: + + * The disk size is returned only when eLCM License for FUJITSU PRIMERGY + servers is activated. If the license is not activated, then Hardware + Inspection will fail to get this value. + * Before inspecting, if the server is power-off, it will be turned on + automatically. System will wait for a few second before start + inspecting. After inspection, power status will be restored to the + previous state. + +The operator can specify these capabilities in compute service flavor, for +example:: + + openstack flavor set baremetal-flavor-name --property capabilities:irmc_firmware_version="iRMC S4-8.64F" + + openstack flavor set baremetal-flavor-name --property capabilities:server_model="TX2540M1F5" + + openstack flavor set baremetal-flavor-name --property capabilities:pci_gpu_devices="1" + +See :ref:`capabilities-discovery` for more details and examples. + Supported platforms =================== This driver supports FUJITSU PRIMERGY BX S4 or RX S8 servers and above. diff --git a/driver-requirements.txt b/driver-requirements.txt index a344bd667b..c643d2e96b 100644 --- a/driver-requirements.txt +++ b/driver-requirements.txt @@ -8,7 +8,7 @@ proliantutils>=2.4.1 pysnmp python-ironic-inspector-client>=1.5.0 python-oneviewclient<3.0.0,>=2.5.2 -python-scciclient>=0.5.0 +python-scciclient>=0.6.0 UcsSdk==0.8.2.2 python-dracclient>=1.3.0 diff --git a/etc/ironic/ironic.conf.sample b/etc/ironic/ironic.conf.sample index c11b6cd0b5..7c61ed8b5d 100644 --- a/etc/ironic/ironic.conf.sample +++ b/etc/ironic/ironic.conf.sample @@ -1963,6 +1963,15 @@ # value) #clean_priority_restore_irmc_bios_config = 0 +# List of vendor IDs and device IDs for GPU device to inspect. +# List items are in format vendorID/deviceID and separated by +# commas. GPU inspection will use this value to count the +# number of GPU device in a node. If this option is not +# defined, then leave out pci_gpu_devices in capabilities +# property. Sample gpu_ids value: 0x1000/0x0079,0x2100/0x0080 +# (list value) +#gpu_ids = + [ironic_lib] diff --git a/ironic/conf/irmc.py b/ironic/conf/irmc.py index bc558d03c5..ce0a0d53c5 100644 --- a/ironic/conf/irmc.py +++ b/ironic/conf/irmc.py @@ -72,6 +72,15 @@ opts = [ cfg.IntOpt('clean_priority_restore_irmc_bios_config', default=0, help=_('Priority for restore_irmc_bios_config clean step.')), + cfg.ListOpt('gpu_ids', + default=[], + help=_('List of vendor IDs and device IDs for GPU device to ' + 'inspect. List items are in format vendorID/deviceID ' + 'and separated by commas. GPU inspection will use this ' + 'value to count the number of GPU device in a node. If ' + 'this option is not defined, then leave out ' + 'pci_gpu_devices in capabilities property. ' + 'Sample gpu_ids value: 0x1000/0x0079,0x2100/0x0080')), ] diff --git a/ironic/drivers/modules/irmc/inspect.py b/ironic/drivers/modules/irmc/inspect.py index 582eb0b850..012fac5e8c 100644 --- a/ironic/drivers/modules/irmc/inspect.py +++ b/ironic/drivers/modules/irmc/inspect.py @@ -14,13 +14,19 @@ """ iRMC Inspect Interface """ +import re + from ironic_lib import metrics_utils from oslo_log import log as logging from oslo_utils import importutils +from ironic.common import boot_devices from ironic.common import exception from ironic.common.i18n import _ from ironic.common import states +from ironic.common import utils +from ironic.conductor import utils as manager_utils +from ironic.conf import CONF from ironic.drivers import base from ironic.drivers.modules.irmc import common as irmc_common from ironic.drivers.modules import snmp @@ -84,6 +90,9 @@ sc2UnitNodeMacAddress OBJECT-TYPE """ MAC_ADDRESS_OID = '1.3.6.1.4.1.231.2.10.2.2.10.3.1.1.9.1' +CAPABILITIES_PROPERTIES = {'trusted_boot', 'irmc_firmware_version', + 'rom_firmware_version', 'server_model', + 'pci_gpu_devices'} def _get_mac_addresses(node): @@ -108,20 +117,55 @@ def _get_mac_addresses(node): if c == NODE_CLASS_OID_VALUE['primary']] -def _inspect_hardware(node): +def _inspect_hardware(node, **kwargs): """Inspect the node and get hardware information. :param node: node object. + :param kwargs: the dictionary of additional parameters. :raises: HardwareInspectionFailure, if unable to get essential hardware properties. :returns: a pair of dictionary and list, the dictionary contains keys as in IRMCInspect.ESSENTIAL_PROPERTIES and its inspected values, the list contains mac addresses. """ + capabilities_props = set(CAPABILITIES_PROPERTIES) + + # Remove all capabilities item which will be inspected in the existing + # capabilities of node + if 'capabilities' in node.properties: + existing_cap = node.properties['capabilities'].split(',') + for item in capabilities_props: + for prop in existing_cap: + if item == prop.split(':')[0]: + existing_cap.remove(prop) + node.properties['capabilities'] = ",".join(existing_cap) + + # get gpu_ids in ironic configuration + values = [gpu_id.lower() for gpu_id in CONF.irmc.gpu_ids] + + # if gpu_ids = [], pci_gpu_devices will not be inspected + if len(values) == 0: + capabilities_props.remove('pci_gpu_devices') + try: report = irmc_common.get_irmc_report(node) props = scci.get_essential_properties( report, IRMCInspect.ESSENTIAL_PROPERTIES) + d_info = irmc_common.parse_driver_info(node) + capabilities = scci.get_capabilities_properties( + d_info, + capabilities_props, + values, + **kwargs) + if capabilities: + if capabilities.get('pci_gpu_devices') == 0: + capabilities.pop('pci_gpu_devices') + if capabilities.get('trusted_boot') is False: + capabilities.pop('trusted_boot') + capabilities = utils.get_updated_capabilities( + node.properties.get('capabilities'), capabilities) + if capabilities: + props['capabilities'] = capabilities macs = _get_mac_addresses(node) except (scci.SCCIInvalidInputError, scci.SCCIClientError, @@ -137,6 +181,19 @@ def _inspect_hardware(node): class IRMCInspect(base.InspectInterface): """Interface for out of band inspection.""" + def __init__(self): + """Validate the driver-specific inspection information. + + This action will validate gpu_ids value along with starting + ironic-conductor service. + """ + for gpu_id in CONF.irmc.gpu_ids: + if not re.match('^0x[0-9a-f]{4}/0x[0-9a-f]{4}$', gpu_id.lower()): + raise exception.InvalidParameterValue(_( + "Invalid [irmc]/gpu_ids configuration option.")) + + super(IRMCInspect, self).__init__() + def get_properties(self): """Return the properties of the interface. @@ -170,7 +227,20 @@ class IRMCInspect(base.InspectInterface): :returns: states.MANAGEABLE, if hardware inspection succeeded. """ node = task.node - (props, macs) = _inspect_hardware(node) + kwargs = {} + # Inspect additional capabilities task requires node with power on + # status + old_power_state = task.driver.power.get_power_state(task) + if old_power_state == states.POWER_OFF: + manager_utils.node_set_boot_device(task, boot_devices.BIOS, False) + manager_utils.node_power_action(task, states.POWER_ON) + + LOG.info("The Node %(node_uuid)s being powered on for inspection", + {'node_uuid': task.node.uuid}) + + kwargs['sleep_flag'] = True + + (props, macs) = _inspect_hardware(node, **kwargs) node.properties = dict(node.properties, **props) node.save() @@ -189,4 +259,12 @@ class IRMCInspect(base.InspectInterface): {'address': mac, 'node_uuid': node.uuid}) LOG.info("Node %s inspected", node.uuid) + # restore old power state + if old_power_state == states.POWER_OFF: + manager_utils.node_power_action(task, states.POWER_OFF) + + LOG.info("The Node %(node_uuid)s being powered off after " + "inspection", + {'node_uuid': task.node.uuid}) + return states.MANAGEABLE diff --git a/ironic/tests/unit/drivers/modules/irmc/test_inspect.py b/ironic/tests/unit/drivers/modules/irmc/test_inspect.py index c74f51edb5..92ce437f68 100644 --- a/ironic/tests/unit/drivers/modules/irmc/test_inspect.py +++ b/ironic/tests/unit/drivers/modules/irmc/test_inspect.py @@ -20,15 +20,19 @@ import mock from ironic.common import exception from ironic.common import states +from ironic.common import utils from ironic.conductor import task_manager +from ironic.conductor import utils as manager_utils from ironic.drivers.modules.irmc import common as irmc_common from ironic.drivers.modules.irmc import inspect as irmc_inspect +from ironic.drivers.modules.irmc import power as irmc_power from ironic import objects from ironic.tests.unit.conductor import mgr_utils from ironic.tests.unit.db import base as db_base from ironic.tests.unit.db import utils as db_utils -from ironic.tests.unit.drivers import third_party_driver_mock_specs \ - as mock_specs +from ironic.tests.unit.drivers import ( + third_party_driver_mock_specs as mock_specs +) from ironic.tests.unit.objects import utils as obj_utils INFO_DICT = db_utils.get_test_irmc_info() @@ -66,24 +70,45 @@ class IRMCInspectInternalMethodsTestCase(db_base.DbTestCase): autospec=True) def test__inspect_hardware( self, get_irmc_report_mock, scci_mock, _get_mac_addresses_mock): + # Set config flags + gpu_ids = ['0x1000/0x0079', '0x2100/0x0080'] + + self.config(gpu_ids=gpu_ids, group='irmc') + kwargs = {'sleep_flag': False} + inspected_props = { 'memory_mb': '1024', 'local_gb': 10, 'cpus': 2, 'cpu_arch': 'x86_64'} + inspected_capabilities = { + 'trusted_boot': False, + 'irmc_firmware_version': 'iRMC S4-7.82F', + 'server_model': 'TX2540M1F5', + 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x', + 'pci_gpu_devices': 1} + inspected_macs = ['aa:aa:aa:aa:aa:aa', 'bb:bb:bb:bb:bb:bb'] report = 'fake_report' get_irmc_report_mock.return_value = report scci_mock.get_essential_properties.return_value = inspected_props + scci_mock.get_capabilities_properties.return_value = ( + inspected_capabilities) _get_mac_addresses_mock.return_value = inspected_macs with task_manager.acquire(self.context, self.node.uuid, shared=True) as task: - result = irmc_inspect._inspect_hardware(task.node) - + result = irmc_inspect._inspect_hardware(task.node, **kwargs) get_irmc_report_mock.assert_called_once_with(task.node) scci_mock.get_essential_properties.assert_called_once_with( report, irmc_inspect.IRMCInspect.ESSENTIAL_PROPERTIES) - self.assertEqual((inspected_props, inspected_macs), result) + scci_mock.get_capabilities_properties.assert_called_once_with( + mock.ANY, irmc_inspect.CAPABILITIES_PROPERTIES, + gpu_ids, **kwargs) + expected_props = dict(inspected_props) + inspected_capabilities = utils.get_updated_capabilities( + '', inspected_capabilities) + expected_props['capabilities'] = inspected_capabilities + self.assertEqual((expected_props, inspected_macs), result) @mock.patch.object(irmc_inspect, '_get_mac_addresses', spec_set=True, autospec=True) @@ -94,6 +119,7 @@ class IRMCInspectInternalMethodsTestCase(db_base.DbTestCase): def test__inspect_hardware_exception( self, get_irmc_report_mock, scci_mock, _get_mac_addresses_mock): report = 'fake_report' + kwargs = {'sleep_flag': False} get_irmc_report_mock.return_value = report side_effect = exception.SNMPFailure("fake exception") scci_mock.get_essential_properties.side_effect = side_effect @@ -104,7 +130,7 @@ class IRMCInspectInternalMethodsTestCase(db_base.DbTestCase): shared=True) as task: self.assertRaises(exception.HardwareInspectionFailure, irmc_inspect._inspect_hardware, - task.node) + task.node, **kwargs) get_irmc_report_mock.assert_called_once_with(task.node) self.assertFalse(_get_mac_addresses_mock.called) @@ -131,7 +157,7 @@ class IRMCInspectTestCase(db_base.DbTestCase): def test_validate(self, parse_driver_info_mock): with task_manager.acquire(self.context, self.node.uuid, shared=True) as task: - task.driver.power.validate(task) + task.driver.inspect.validate(task) parse_driver_info_mock.assert_called_once_with(task.node) @mock.patch.object(irmc_common, 'parse_driver_info', spec_set=True, @@ -143,22 +169,31 @@ class IRMCInspectTestCase(db_base.DbTestCase): with task_manager.acquire(self.context, self.node.uuid, shared=True) as task: self.assertRaises(exception.InvalidParameterValue, - task.driver.power.validate, + task.driver.inspect.validate, task) + def test__init_fail_invalid_input(self): + # Set config flags + self.config(gpu_ids='100/x079,0x20/', group='irmc') + self.assertRaises(exception.InvalidParameterValue, + irmc_inspect.IRMCInspect) + @mock.patch.object(irmc_inspect.LOG, 'info', spec_set=True, autospec=True) @mock.patch('ironic.drivers.modules.irmc.inspect.objects.Port', spec_set=True, autospec=True) @mock.patch.object(irmc_inspect, '_inspect_hardware', spec_set=True, autospec=True) - def test_inspect_hardware(self, _inspect_hardware_mock, port_mock, - info_mock): + @mock.patch.object(irmc_power.IRMCPower, 'get_power_state', spec_set=True, + autospec=True) + def test_inspect_hardware(self, power_state_mock, _inspect_hardware_mock, + port_mock, info_mock): inspected_props = { 'memory_mb': '1024', 'local_gb': 10, 'cpus': 2, 'cpu_arch': 'x86_64'} inspected_macs = ['aa:aa:aa:aa:aa:aa', 'bb:bb:bb:bb:bb:bb'] + power_state_mock.return_value = states.POWER_ON _inspect_hardware_mock.return_value = (inspected_props, inspected_macs) new_port_mock1 = mock.MagicMock(spec=objects.Port) @@ -167,7 +202,7 @@ class IRMCInspectTestCase(db_base.DbTestCase): port_mock.side_effect = [new_port_mock1, new_port_mock2] with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: + shared=False) as task: result = task.driver.inspect.inspect_hardware(task) node_id = task.node.id @@ -204,16 +239,73 @@ class IRMCInspectTestCase(db_base.DbTestCase): self.assertEqual(inspected_props, task.node.properties) self.assertEqual(states.MANAGEABLE, result) + @mock.patch.object(manager_utils, 'node_power_action', spec_set=True, + autospec=True) + @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True, + autospec=True) + @mock.patch.object(irmc_inspect.LOG, 'info', spec_set=True, autospec=True) + @mock.patch.object(irmc_inspect.objects, 'Port', + spec_set=True, autospec=True) + @mock.patch.object(irmc_inspect, '_inspect_hardware', spec_set=True, + autospec=True) + @mock.patch.object(irmc_power.IRMCPower, 'get_power_state', spec_set=True, + autospec=True) + def test_inspect_hardware_with_power_off(self, power_state_mock, + _inspect_hardware_mock, + port_mock, info_mock, + set_boot_device_mock, + power_action_mock): + inspected_props = { + 'memory_mb': '1024', + 'local_gb': 10, + 'cpus': 2, + 'cpu_arch': 'x86_64'} + inspected_macs = ['aa:aa:aa:aa:aa:aa', 'bb:bb:bb:bb:bb:bb'] + power_state_mock.return_value = states.POWER_OFF + _inspect_hardware_mock.return_value = (inspected_props, + inspected_macs) + new_port_mock1 = mock.MagicMock(spec=objects.Port) + new_port_mock2 = mock.MagicMock(spec=objects.Port) + + port_mock.side_effect = [new_port_mock1, new_port_mock2] + + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + result = task.driver.inspect.inspect_hardware(task) + + node_id = task.node.id + _inspect_hardware_mock.assert_called_once_with(task.node, + sleep_flag=True) + + port_mock.assert_has_calls([ + mock.call(task.context, address=inspected_macs[0], + node_id=node_id), + mock.call(task.context, address=inspected_macs[1], + node_id=node_id) + ]) + new_port_mock1.create.assert_called_once_with() + new_port_mock2.create.assert_called_once_with() + + self.assertTrue(info_mock.called) + task.node.refresh() + self.assertEqual(inspected_props, task.node.properties) + self.assertEqual(states.MANAGEABLE, result) + self.assertEqual(power_action_mock.called, True) + self.assertEqual(power_action_mock.call_count, 2) + @mock.patch('ironic.objects.Port', spec_set=True, autospec=True) @mock.patch.object(irmc_inspect, '_inspect_hardware', spec_set=True, autospec=True) + @mock.patch.object(irmc_power.IRMCPower, 'get_power_state', spec_set=True, + autospec=True) def test_inspect_hardware_inspect_exception( - self, _inspect_hardware_mock, port_mock): + self, power_state_mock, _inspect_hardware_mock, port_mock): side_effect = exception.HardwareInspectionFailure("fake exception") _inspect_hardware_mock.side_effect = side_effect + power_state_mock.return_value = states.POWER_ON with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: + shared=False) as task: self.assertRaises(exception.HardwareInspectionFailure, task.driver.inspect.inspect_hardware, task) @@ -223,8 +315,11 @@ class IRMCInspectTestCase(db_base.DbTestCase): @mock.patch('ironic.objects.Port', spec_set=True, autospec=True) @mock.patch.object(irmc_inspect, '_inspect_hardware', spec_set=True, autospec=True) + @mock.patch.object(irmc_power.IRMCPower, 'get_power_state', spec_set=True, + autospec=True) def test_inspect_hardware_mac_already_exist( - self, _inspect_hardware_mock, port_mock, warn_mock): + self, power_state_mock, _inspect_hardware_mock, + port_mock, warn_mock): inspected_props = { 'memory_mb': '1024', 'local_gb': 10, @@ -233,12 +328,13 @@ class IRMCInspectTestCase(db_base.DbTestCase): inspected_macs = ['aa:aa:aa:aa:aa:aa', 'bb:bb:bb:bb:bb:bb'] _inspect_hardware_mock.return_value = (inspected_props, inspected_macs) + power_state_mock.return_value = states.POWER_ON side_effect = exception.MACAlreadyExists("fake exception") new_port_mock = port_mock.return_value new_port_mock.create.side_effect = side_effect with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: + shared=False) as task: result = task.driver.inspect.inspect_hardware(task) _inspect_hardware_mock.assert_called_once_with(task.node) @@ -246,3 +342,225 @@ class IRMCInspectTestCase(db_base.DbTestCase): task.node.refresh() self.assertEqual(inspected_props, task.node.properties) self.assertEqual(states.MANAGEABLE, result) + + @mock.patch.object(irmc_inspect, '_get_mac_addresses', spec_set=True, + autospec=True) + @mock.patch.object(irmc_inspect, 'scci', + spec_set=mock_specs.SCCICLIENT_IRMC_SCCI_SPEC) + @mock.patch.object(irmc_common, 'get_irmc_report', spec_set=True, + autospec=True) + def _test_inspect_hardware_props(self, gpu_ids, + existed_capabilities, + inspected_capabilities, + expected_capabilities, + get_irmc_report_mock, + scci_mock, + _get_mac_addresses_mock): + capabilities_props = set(irmc_inspect.CAPABILITIES_PROPERTIES) + + # if gpu_ids = [], pci_gpu_devices will not be inspected + if len(gpu_ids) == 0: + capabilities_props.remove('pci_gpu_devices') + + self.config(gpu_ids=gpu_ids, group='irmc') + kwargs = {'sleep_flag': False} + + inspected_props = { + 'memory_mb': '1024', + 'local_gb': 10, + 'cpus': 2, + 'cpu_arch': 'x86_64'} + + inspected_macs = ['aa:aa:aa:aa:aa:aa', 'bb:bb:bb:bb:bb:bb'] + report = 'fake_report' + get_irmc_report_mock.return_value = report + scci_mock.get_essential_properties.return_value = inspected_props + scci_mock.get_capabilities_properties.return_value = \ + inspected_capabilities + _get_mac_addresses_mock.return_value = inspected_macs + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + task.node.properties[u'capabilities'] =\ + ",".join('%(k)s:%(v)s' % {'k': k, 'v': v} + for k, v in existed_capabilities.items()) + result = irmc_inspect._inspect_hardware(task.node, **kwargs) + get_irmc_report_mock.assert_called_once_with(task.node) + scci_mock.get_essential_properties.assert_called_once_with( + report, irmc_inspect.IRMCInspect.ESSENTIAL_PROPERTIES) + scci_mock.get_capabilities_properties.assert_called_once_with( + mock.ANY, capabilities_props, + gpu_ids, **kwargs) + expected_capabilities = utils.get_updated_capabilities( + '', expected_capabilities) + + set1 = set(expected_capabilities.split(',')) + set2 = set(result[0]['capabilities'].split(',')) + self.assertEqual(set1, set2) + + def test_inspect_hardware_existing_cap_in_props(self): + # Set config flags + gpu_ids = ['0x1000/0x0079', '0x2100/0x0080'] + existed_capabilities = { + 'trusted_boot': True, + 'irmc_firmware_version': 'iRMC S4-7.82F', + 'server_model': 'TX2540M1F5', + 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x', + 'pci_gpu_devices': 1} + inspected_capabilities = { + 'trusted_boot': True, + 'irmc_firmware_version': 'iRMC S4-7.82F', + 'server_model': 'TX2540M1F5', + 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x', + 'pci_gpu_devices': 1} + expected_capabilities = { + 'trusted_boot': True, + 'irmc_firmware_version': 'iRMC S4-7.82F', + 'server_model': 'TX2540M1F5', + 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x', + 'pci_gpu_devices': 1} + + self._test_inspect_hardware_props(gpu_ids, + existed_capabilities, + inspected_capabilities, + expected_capabilities) + + def test_inspect_hardware_props_empty_gpu_ids(self): + # Set config flags + gpu_ids = [] + existed_capabilities = {} + inspected_capabilities = { + 'trusted_boot': True, + 'irmc_firmware_version': 'iRMC S4-7.82F', + 'server_model': 'TX2540M1F5', + 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x'} + expected_capabilities = { + 'trusted_boot': True, + 'irmc_firmware_version': 'iRMC S4-7.82F', + 'server_model': 'TX2540M1F5', + 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x'} + + self._test_inspect_hardware_props(gpu_ids, + existed_capabilities, + inspected_capabilities, + expected_capabilities) + + def test_inspect_hardware_props_pci_gpu_devices_return_zero(self): + # Set config flags + gpu_ids = ['0x1000/0x0079', '0x2100/0x0080'] + + existed_capabilities = {} + inspected_capabilities = { + 'trusted_boot': True, + 'irmc_firmware_version': 'iRMC S4-7.82F', + 'server_model': 'TX2540M1F5', + 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x', + 'pci_gpu_devices': 0} + expected_capabilities = { + 'trusted_boot': True, + 'irmc_firmware_version': 'iRMC S4-7.82F', + 'server_model': 'TX2540M1F5', + 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x'} + + self._test_inspect_hardware_props(gpu_ids, + existed_capabilities, + inspected_capabilities, + expected_capabilities) + + def test_inspect_hardware_props_empty_gpu_ids_and_existing_cap(self): + # Set config flags + gpu_ids = [] + existed_capabilities = { + 'trusted_boot': True, + 'irmc_firmware_version': 'iRMC S4-7.82F', + 'server_model': 'TX2540M1F5', + 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x', + 'pci_gpu_devices': 1} + inspected_capabilities = { + 'trusted_boot': True, + 'irmc_firmware_version': 'iRMC S4-7.82F', + 'server_model': 'TX2540M1F5', + 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x'} + expected_capabilities = { + 'trusted_boot': True, + 'irmc_firmware_version': 'iRMC S4-7.82F', + 'server_model': 'TX2540M1F5', + 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x'} + + self._test_inspect_hardware_props(gpu_ids, + existed_capabilities, + inspected_capabilities, + expected_capabilities) + + def test_inspect_hardware_props_pci_gpu_devices_zero_and_existing_cap( + self): + # Set config flags + gpu_ids = ['0x1000/0x0079', '0x2100/0x0080'] + existed_capabilities = { + 'trusted_boot': True, + 'irmc_firmware_version': 'iRMC S4-7.82F', + 'server_model': 'TX2540M1F5', + 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x', + 'pci_gpu_devices': 1} + inspected_capabilities = { + 'trusted_boot': True, + 'irmc_firmware_version': 'iRMC S4-7.82F', + 'server_model': 'TX2540M1F5', + 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x', + 'pci_gpu_devices': 0} + expected_capabilities = { + 'trusted_boot': True, + 'irmc_firmware_version': 'iRMC S4-7.82F', + 'server_model': 'TX2540M1F5', + 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x'} + + self._test_inspect_hardware_props(gpu_ids, + existed_capabilities, + inspected_capabilities, + expected_capabilities) + + def test_inspect_hardware_props_trusted_boot_is_false(self): + # Set config flags + gpu_ids = ['0x1000/0x0079', '0x2100/0x0080'] + existed_capabilities = {} + inspected_capabilities = { + 'trusted_boot': False, + 'irmc_firmware_version': 'iRMC S4-7.82F', + 'server_model': 'TX2540M1F5', + 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x', + 'pci_gpu_devices': 1} + expected_capabilities = { + 'irmc_firmware_version': 'iRMC S4-7.82F', + 'server_model': 'TX2540M1F5', + 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x', + 'pci_gpu_devices': 1} + + self._test_inspect_hardware_props(gpu_ids, + existed_capabilities, + inspected_capabilities, + expected_capabilities) + + def test_inspect_hardware_props_trusted_boot_is_false_and_existing_cap( + self): + # Set config flags + gpu_ids = ['0x1000/0x0079', '0x2100/0x0080'] + existed_capabilities = { + 'trusted_boot': True, + 'irmc_firmware_version': 'iRMC S4-7.82F', + 'server_model': 'TX2540M1F5', + 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x', + 'pci_gpu_devices': 1} + inspected_capabilities = { + 'trusted_boot': False, + 'irmc_firmware_version': 'iRMC S4-7.82F', + 'server_model': 'TX2540M1F5', + 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x', + 'pci_gpu_devices': 1} + expected_capabilities = { + 'irmc_firmware_version': 'iRMC S4-7.82F', + 'server_model': 'TX2540M1F5', + 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x', + 'pci_gpu_devices': 1} + self._test_inspect_hardware_props(gpu_ids, + existed_capabilities, + inspected_capabilities, + expected_capabilities) 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 8f59f10720..f21adafd46 100644 --- a/ironic/tests/unit/drivers/third_party_driver_mock_specs.py +++ b/ironic/tests/unit/drivers/third_party_driver_mock_specs.py @@ -102,6 +102,7 @@ SCCICLIENT_IRMC_SCCI_SPEC = ( 'get_virtual_cd_set_params_cmd', 'get_virtual_fd_set_params_cmd', 'get_essential_properties', + 'get_capabilities_properties', ) SCCICLIENT_IRMC_ELCM_SPEC = ( 'backup_bios_config', diff --git a/releasenotes/notes/irmc-additional-capabilities-4fd72ba50d05676c.yaml b/releasenotes/notes/irmc-additional-capabilities-4fd72ba50d05676c.yaml new file mode 100644 index 0000000000..a47571e35d --- /dev/null +++ b/releasenotes/notes/irmc-additional-capabilities-4fd72ba50d05676c.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + Adds new capabilities ("server_model", "rom_firmware_version", "pci_gpu_devices", + "trusted_boot" and "irmc_firmware_version") to the iRMC out-of-band hardware inspection + for FUJITSU PRIMERGY bare metal nodes with firmware iRMC S4 and beyond. + Before inspecting, if a server is power-off, it will be turned on automatically. + System will wait for a few second before start inspecting. After inspection, + power status will be restored to the previous state.