Пример #1
0
 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)
Пример #2
0
 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)
Пример #3
0
    def inspect_hardware(self, task):
        """Inspect hardware to obtain the hardware properties.

        This particular implementation only starts inspection using
        ironic-inspector. Results will be checked in a periodic task.

        :param task: a task from TaskManager.
        :returns: states.INSPECTWAIT
        :raises: HardwareInspectionFailure on failure
        """
        try:
            enabled_macs = task.driver.management.get_mac_addresses(task)
            if enabled_macs:
                inspect_utils.create_ports_if_not_exist(
                    task, enabled_macs, get_mac_address=lambda x: x[0])
            else:
                LOG.warning(
                    "Not attempting to create any port as no NICs "
                    "were discovered in 'enabled' state for node "
                    "%(node)s: %(mac_data)s", {
                        'mac_data': enabled_macs,
                        'node': task.node.uuid
                    })

        except exception.UnsupportedDriverExtension:
            LOG.debug(
                'Pre-creating ports prior to inspection not supported'
                ' on node %s.', task.node.uuid)

        ironic_manages_boot = _ironic_manages_boot(
            task, raise_exc=CONF.inspector.require_managed_boot)

        utils.set_node_nested_field(task.node, 'driver_internal_info',
                                    _IRONIC_MANAGES_BOOT, ironic_manages_boot)
        task.node.save()

        LOG.debug(
            'Starting inspection for node %(uuid)s using '
            'ironic-inspector, booting is managed by %(project)s', {
                'uuid': task.node.uuid,
                'project':
                'ironic' if ironic_manages_boot else 'ironic-inspector'
            })

        if ironic_manages_boot:
            _start_managed_inspection(task)
        else:
            # NOTE(dtantsur): spawning a short-living green thread so that
            # we can release a lock as soon as possible and allow
            # ironic-inspector to operate on the node.
            eventlet.spawn_n(_start_inspection, task.node.uuid, task.context)
        return states.INSPECTWAIT
Пример #4
0
 def _create_ports(self, task, system):
     enabled_macs = redfish_utils.get_enabled_macs(task, system)
     if enabled_macs:
         inspect_utils.create_ports_if_not_exist(
             task, enabled_macs, get_mac_address=lambda x: x[0])
     else:
         LOG.warning(
             "Not attempting to create any port as no NICs "
             "were discovered in 'enabled' state for node "
             "%(node)s: %(mac_data)s", {
                 'mac_data': enabled_macs,
                 'node': task.node.uuid
             })
Пример #5
0
 def test_create_ports_if_not_exist_attempts_port_creation_blindly(
         self, port_mock, log_info_mock):
     macs = {'aa:bb:cc:dd:ee:ff': sushy.STATE_ENABLED,
             'aa:bb:aa:aa:aa:aa': sushy.STATE_DISABLED}
     node_id = self.node.id
     port_dict1 = {'address': 'aa:bb:cc:dd:ee:ff', 'node_id': node_id}
     port_dict2 = {'address': 'aa:bb:aa:aa:aa:aa', 'node_id': node_id}
     with task_manager.acquire(self.context, self.node.uuid,
                               shared=False) as task:
         utils.create_ports_if_not_exist(
             task, macs, get_mac_address=lambda x: x[0])
         self.assertTrue(log_info_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)
         self.assertEqual(2, port_mock.return_value.create.call_count)
Пример #6
0
 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()
Пример #7
0
 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()
Пример #8
0
    def _create_ports(self, task, system):
        if (system.ethernet_interfaces
                and system.ethernet_interfaces.summary):
            macs = system.ethernet_interfaces.summary

            # Create ports for the discovered NICs being in 'enabled' state
            enabled_macs = {nic_mac: nic_state
                            for nic_mac, nic_state in macs.items()
                            if nic_state == sushy.STATE_ENABLED}
            if enabled_macs:
                inspect_utils.create_ports_if_not_exist(
                    task, enabled_macs, get_mac_address=lambda x: x[0])
            else:
                LOG.warning("Not attempting to create any port as no NICs "
                            "were discovered in 'enabled' state for node "
                            "%(node)s: %(mac_data)s",
                            {'mac_data': macs, 'node': task.node.uuid})
        else:
            LOG.warning("No NIC information discovered "
                        "for node %(node)s", {'node': task.node.uuid})
Пример #9
0
    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.

        """
        # Ensure we create a port for every NIC port found for consistency
        # with our WSMAN inspect behavior and to work around a bug in some
        # versions of the firmware where the port state is not being
        # reported correctly.

        ethernet_interfaces_mac = self._get_mac_address(task)
        inspect_utils.create_ports_if_not_exist(task, ethernet_interfaces_mac)
        return super(DracRedfishInspect, self).inspect_hardware(task)
Пример #10
0
 def test_create_ports_if_not_exist_attempts_port_creation_blindly(
         self, port_mock, log_info_mock):
     macs = {
         'aa:bb:cc:dd:ee:ff': sushy.STATE_ENABLED,
         'aa:bb:aa:aa:aa:aa': sushy.STATE_DISABLED
     }
     node_id = self.node.id
     port_dict1 = {'address': 'aa:bb:cc:dd:ee:ff', 'node_id': node_id}
     port_dict2 = {'address': 'aa:bb:aa:aa:aa:aa', 'node_id': node_id}
     with task_manager.acquire(self.context, self.node.uuid,
                               shared=False) as task:
         utils.create_ports_if_not_exist(task,
                                         macs,
                                         get_mac_address=lambda x: x[0])
         self.assertTrue(log_info_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)
         self.assertEqual(2, port_mock.return_value.create.call_count)
Пример #11
0
    def inspect_hardware(self, task):
        """Inspect hardware to get the hardware properties.

        Inspects hardware to get the essential and additional hardware
        properties. It fails if any of the essential properties
        are not received from the node.  It doesn't fail if node fails
        to return any capabilities as the capabilities differ from hardware
        to hardware mostly.

        :param task: a TaskManager instance.
        :raises: HardwareInspectionFailure if essential properties
                 could not be retrieved successfully.
        :raises: IloOperationError if system fails to get power state.
        :returns: The resulting state of inspection.

        """
        power_turned_on = False
        ilo_object = ilo_common.get_ilo_object(task.node)
        try:
            state = task.driver.power.get_power_state(task)
        except exception.IloOperationError as ilo_exception:
            operation = (_("Inspecting hardware (get_power_state) on %s") %
                         task.node.uuid)
            raise exception.IloOperationError(operation=operation,
                                              error=ilo_exception)
        if state != states.POWER_ON:
            LOG.info(
                "The node %s is not powered on. Powering on the "
                "node for inspection.", task.node.uuid)
            conductor_utils.node_power_action(task, states.POWER_ON)
            power_turned_on = True

        # get the essential properties and update the node properties
        # with it.

        inspected_properties = {}
        result = _get_essential_properties(task.node, ilo_object)

        # A temporary hook for OOB inspection to not to update 'local_gb'
        # for hardware if the storage is a "Direct Attached Storage" or
        # "Dynamic Smart Array Controllers" and the operator has manually
        # updated the local_gb in node properties prior to node inspection.
        # This will be removed once we have inband inspection support for
        # ilo drivers.
        current_local_gb = task.node.properties.get('local_gb')
        properties = result['properties']
        if current_local_gb:
            if properties['local_gb'] == 0 and current_local_gb > 0:
                properties['local_gb'] = current_local_gb
                LOG.warning(
                    'Could not discover size of disk on the node '
                    '%s. Value of `properties/local_gb` of the '
                    'node is not overwritten.', task.node.uuid)

        for known_property in self.ESSENTIAL_PROPERTIES:
            inspected_properties[known_property] = properties[known_property]
        node_properties = task.node.properties
        node_properties.update(inspected_properties)
        task.node.properties = node_properties

        # Inspect the hardware for additional hardware capabilities.
        # Since additional hardware capabilities may not apply to all the
        # hardwares, the method inspect_hardware() doesn't raise an error
        # for these capabilities.
        capabilities = _get_capabilities(task.node, ilo_object)
        model = None
        if capabilities:
            model = capabilities.get('server_model')
            valid_cap = _create_supported_capabilities_dict(capabilities)
            capabilities = utils.get_updated_capabilities(
                task.node.properties.get('capabilities'), valid_cap)
            if capabilities:
                node_properties['capabilities'] = capabilities
                task.node.properties = node_properties

        # RIBCL(Gen8) protocol cannot determine if a NIC
        # is physically connected with cable or not when the server
        # is not provisioned. RIS(Gen9) can detect the same for few NIC
        # adapters but not for all. However it is possible to determine
        # the same using Redfish(Gen10) protocol. Hence proliantutils
        # returns ALL MACs for Gen8 and Gen9 while it returns
        # only active MACs for Gen10. A warning is being added
        # for the user so that he knows that he needs to remove the
        # ironic ports created for inactive ports for Gen8 and Gen9.
        servers = ['Gen8', 'Gen9']
        if model is not None and any(serv in model for serv in servers):
            LOG.warning(
                'iLO cannot determine if the NICs are physically '
                'connected or not for ProLiant Gen8 and Gen9 servers. '
                'Hence returns all the MACs present on the server. '
                'Please remove the ironic ports created for inactive '
                'NICs manually for the node %(node)s',
                {"node": task.node.uuid})
        task.node.save()

        # Create ports for the nics detected.
        inspect_utils.create_ports_if_not_exist(task, result['macs'])

        LOG.debug(
            "Node properties for %(node)s are updated as "
            "%(properties)s", {
                'properties': inspected_properties,
                'node': task.node.uuid
            })

        LOG.info("Node %s inspected.", task.node.uuid)
        if power_turned_on:
            conductor_utils.node_power_action(task, states.POWER_OFF)
            LOG.info(
                "The node %s was powered on for inspection. "
                "Powered off the node as inspection completed.",
                task.node.uuid)
        return states.MANAGEABLE
Пример #12
0
    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:
            LOG.debug(
                "Attempting to discover system simple storage size for "
                "node %(node)s", {'node': task.node.uuid})
            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.exceptions.SushyError as ex:
            LOG.debug(
                "No simple storage information discovered "
                "for node %(node)s: %(err)s", {
                    'node': task.node.uuid,
                    'err': ex
                })

        storage_size = 0

        try:
            LOG.debug(
                "Attempting to discover system storage volume size for "
                "node %(node)s", {'node': task.node.uuid})
            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.exceptions.SushyError as ex:
            LOG.debug(
                "No storage volume information discovered "
                "for node %(node)s: %(err)s", {
                    'node': task.node.uuid,
                    'err': ex
                })

        try:
            if not storage_size:
                LOG.debug(
                    "Attempting to discover system storage drive size "
                    "for node %(node)s", {'node': task.node.uuid})
                if system.storage and system.storage.drives_sizes_bytes:
                    storage_size = [
                        size for size in system.storage.drives_sizes_bytes
                        if size >= 4 * units.Gi
                    ] or [0]

                    storage_size = storage_size[0]

        except sushy.exceptions.SushyError as ex:
            LOG.debug(
                "No storage drive information discovered "
                "for node %(node)s: %(err)s", {
                    'node': task.node.uuid,
                    'err': ex
                })

        # NOTE(etingof): pick the smallest disk larger than 4G among available
        if simple_storage_size and storage_size:
            local_gb = min(simple_storage_size, storage_size)

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

        # TODO(etingof): should we respect root device hints here?

        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. Assuming this is a disk-less node",
                {'node': task.node.uuid})
            inspected_properties['local_gb'] = '0'

        if system.boot.mode:
            if not drivers_utils.get_node_capability(task.node, 'boot_mode'):
                capabilities = utils.get_updated_capabilities(
                    inspected_properties.get('capabilities', ''),
                    {'boot_mode': BOOT_MODE_MAP[system.boot.mode]})

                inspected_properties['capabilities'] = capabilities

        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.summary):
            macs = system.ethernet_interfaces.summary

            # Create ports for the discovered NICs being in 'enabled' state
            enabled_macs = {
                nic_mac: nic_state
                for nic_mac, nic_state in macs.items()
                if nic_state == sushy.STATE_ENABLED
            }
            if enabled_macs:
                inspect_utils.create_ports_if_not_exist(
                    task, enabled_macs, get_mac_address=lambda x: x[0])
            else:
                LOG.warning(
                    "Not attempting to create any port as no NICs "
                    "were discovered in 'enabled' state for node "
                    "%(node)s: %(mac_data)s", {
                        'mac_data': macs,
                        'node': task.node.uuid
                    })
        else:
            LOG.warning("No NIC information discovered "
                        "for node %(node)s", {'node': task.node.uuid})

        return states.MANAGEABLE
Пример #13
0
    def inspect_hardware(self, task):
        """Inspect hardware to get the hardware properties.

        Inspects hardware to get the essential and additional hardware
        properties. It fails if any of the essential properties
        are not received from the node.  It doesn't fail if node fails
        to return any capabilities as the capabilities differ from hardware
        to hardware mostly.

        :param task: a TaskManager instance.
        :raises: HardwareInspectionFailure if essential properties
                 could not be retrieved successfully.
        :raises: IloOperationError if system fails to get power state.
        :returns: The resulting state of inspection.

        """
        power_turned_on = False
        ilo_object = ilo_common.get_ilo_object(task.node)
        try:
            state = task.driver.power.get_power_state(task)
        except exception.IloOperationError as ilo_exception:
            operation = (_("Inspecting hardware (get_power_state) on %s")
                         % task.node.uuid)
            raise exception.IloOperationError(operation=operation,
                                              error=ilo_exception)
        if state != states.POWER_ON:
            LOG.info("The node %s is not powered on. Powering on the "
                     "node for inspection.", task.node.uuid)
            conductor_utils.node_power_action(task, states.POWER_ON)
            power_turned_on = True

        # get the essential properties and update the node properties
        # with it.

        inspected_properties = {}
        result = _get_essential_properties(task.node, ilo_object)

        # A temporary hook for OOB inspection to not to update 'local_gb'
        # for hardware if the storage is a "Direct Attached Storage" or
        # "Dynamic Smart Array Controllers" and the operator has manually
        # updated the local_gb in node properties prior to node inspection.
        # This will be removed once we have inband inspection support for
        # ilo drivers.
        current_local_gb = task.node.properties.get('local_gb')
        properties = result['properties']
        if current_local_gb:
            if properties['local_gb'] == 0 and current_local_gb > 0:
                properties['local_gb'] = current_local_gb
                LOG.warning('Could not discover size of disk on the node '
                            '%s. Value of `properties/local_gb` of the '
                            'node is not overwritten.', task.node.uuid)

        for known_property in self.ESSENTIAL_PROPERTIES:
            inspected_properties[known_property] = properties[known_property]
        node_properties = task.node.properties
        node_properties.update(inspected_properties)
        task.node.properties = node_properties

        # Inspect the hardware for additional hardware capabilities.
        # Since additional hardware capabilities may not apply to all the
        # hardwares, the method inspect_hardware() doesn't raise an error
        # for these capabilities.
        capabilities = _get_capabilities(task.node, ilo_object)
        if capabilities:
            valid_cap = _create_supported_capabilities_dict(capabilities)
            capabilities = utils.get_updated_capabilities(
                task.node.properties.get('capabilities'), valid_cap)
            if capabilities:
                node_properties['capabilities'] = capabilities
                task.node.properties = node_properties

        task.node.save()

        # Create ports for the nics detected.
        inspect_utils.create_ports_if_not_exist(task, result['macs'])

        LOG.debug("Node properties for %(node)s are updated as "
                  "%(properties)s",
                  {'properties': inspected_properties,
                   'node': task.node.uuid})

        LOG.info("Node %s inspected.", task.node.uuid)
        if power_turned_on:
            conductor_utils.node_power_action(task, states.POWER_OFF)
            LOG.info("The node %s was powered on for inspection. "
                     "Powered off the node as inspection completed.",
                     task.node.uuid)
        return states.MANAGEABLE
Пример #14
0
    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:
            LOG.debug("Attempting to discover system simple storage size for "
                      "node %(node)s", {'node': task.node.uuid})
            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.exceptions.SushyError as ex:
            LOG.debug("No simple storage information discovered "
                      "for node %(node)s: %(err)s", {'node': task.node.uuid,
                                                     'err': ex})

        storage_size = 0

        try:
            LOG.debug("Attempting to discover system storage volume size for "
                      "node %(node)s", {'node': task.node.uuid})
            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.exceptions.SushyError as ex:
            LOG.debug("No storage volume information discovered "
                      "for node %(node)s: %(err)s", {'node': task.node.uuid,
                                                     'err': ex})

        try:
            if not storage_size:
                LOG.debug("Attempting to discover system storage drive size "
                          "for node %(node)s", {'node': task.node.uuid})
                if system.storage and system.storage.drives_sizes_bytes:
                    storage_size = [
                        size for size in system.storage.drives_sizes_bytes
                        if size >= 4 * units.Gi
                    ] or [0]

                    storage_size = storage_size[0]

        except sushy.exceptions.SushyError as ex:
            LOG.debug("No storage drive information discovered "
                      "for node %(node)s: %(err)s", {'node': task.node.uuid,
                                                     'err': ex})

        # NOTE(etingof): pick the smallest disk larger than 4G among available
        if simple_storage_size and storage_size:
            local_gb = min(simple_storage_size, storage_size)

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

        # TODO(etingof): should we respect root device hints here?

        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. Assuming this is a disk-less node",
                        {'node': task.node.uuid})
            inspected_properties['local_gb'] = '0'

        if system.boot.mode:
            if not drivers_utils.get_node_capability(task.node, 'boot_mode'):
                capabilities = utils.get_updated_capabilities(
                    inspected_properties.get('capabilities', ''),
                    {'boot_mode': BOOT_MODE_MAP[system.boot.mode]})

                inspected_properties['capabilities'] = capabilities

        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.summary):
            macs = system.ethernet_interfaces.summary

            # Create ports for the discovered NICs being in 'enabled' state
            enabled_macs = {nic_mac: nic_state
                            for nic_mac, nic_state in macs.items()
                            if nic_state == sushy.STATE_ENABLED}
            if enabled_macs:
                inspect_utils.create_ports_if_not_exist(
                    task, enabled_macs, get_mac_address=lambda x: x[0])
            else:
                LOG.warning("Not attempting to create any port as no NICs "
                            "were discovered in 'enabled' state for node "
                            "%(node)s: %(mac_data)s",
                            {'mac_data': macs, 'node': task.node.uuid})
        else:
            LOG.warning("No NIC information discovered "
                        "for node %(node)s", {'node': task.node.uuid})

        return states.MANAGEABLE