Add Redfish inspect interface
Add the InspectInterface to the `redfish` hardware type. This enables OOB inspection in ironic. Story: 1668487 Task: 10571 Co-Authored-By: Ilya Etingof <etingof@gmail.com> Depends-On: I3a79f2afe6c838636df554ee468f8f2e0cf0859e Depends-On: Ieb374f8cabb0418bb2680fdab690446346fc354f Change-Id: Ie3167569db060620fe0b9784bc7d7a854f2ef754
This commit is contained in:
parent
325b0a6bd4
commit
41d3356571
@ -21,7 +21,8 @@ Enabling the Redfish driver
|
|||||||
===========================
|
===========================
|
||||||
|
|
||||||
#. Add ``redfish`` to the list of ``enabled_hardware_types``,
|
#. Add ``redfish`` to the list of ``enabled_hardware_types``,
|
||||||
``enabled_power_interfaces`` and ``enabled_management_interfaces``
|
``enabled_power_interfaces``, ``enabled_management_interfaces`` and
|
||||||
|
``enabled_inspect_interfaces``
|
||||||
in ``/etc/ironic/ironic.conf``. For example::
|
in ``/etc/ironic/ironic.conf``. For example::
|
||||||
|
|
||||||
[DEFAULT]
|
[DEFAULT]
|
||||||
@ -29,6 +30,7 @@ Enabling the Redfish driver
|
|||||||
enabled_hardware_types = ipmi,redfish
|
enabled_hardware_types = ipmi,redfish
|
||||||
enabled_power_interfaces = ipmitool,redfish
|
enabled_power_interfaces = ipmitool,redfish
|
||||||
enabled_management_interfaces = ipmitool,redfish
|
enabled_management_interfaces = ipmitool,redfish
|
||||||
|
enabled_inspect_interfaces = inspector,redfish
|
||||||
|
|
||||||
#. Restart the ironic conductor service::
|
#. Restart the ironic conductor service::
|
||||||
|
|
||||||
@ -104,6 +106,21 @@ bare metal node as well as set it to either Legacy BIOS or UEFI.
|
|||||||
it remains the responsibility of the operator to configure proper
|
it remains the responsibility of the operator to configure proper
|
||||||
boot mode to their bare metal nodes.
|
boot mode to their bare metal nodes.
|
||||||
|
|
||||||
|
Out-Of-Band inspection
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The ``redfish`` hardware type can inspect the bare metal node by querying
|
||||||
|
Redfish BMC. This process if quick and reliable compared to the way
|
||||||
|
how the ``inspector`` hardware type works e.g. booting bare metal node into
|
||||||
|
the introspection ramdisk.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The ``redfish`` inspect interface largely relies on the optional parts
|
||||||
|
of the Redfish specification. Not all Redfish-compliant BMCs might serve
|
||||||
|
the required information, in which case bare metal node inspection would
|
||||||
|
fail.
|
||||||
|
|
||||||
.. _Redfish: http://redfish.dmtf.org/
|
.. _Redfish: http://redfish.dmtf.org/
|
||||||
.. _Sushy: https://git.openstack.org/cgit/openstack/sushy
|
.. _Sushy: https://git.openstack.org/cgit/openstack/sushy
|
||||||
.. _TLS: https://en.wikipedia.org/wiki/Transport_Layer_Security
|
.. _TLS: https://en.wikipedia.org/wiki/Transport_Layer_Security
|
||||||
|
@ -579,6 +579,34 @@ def get_single_nic_with_vif_port_id(task):
|
|||||||
return port.address
|
return port.address
|
||||||
|
|
||||||
|
|
||||||
|
def create_ports_if_not_exist(task, macs):
|
||||||
|
"""Create ironic ports for the mac addresses.
|
||||||
|
|
||||||
|
Creates ironic ports for the mac addresses returned with inspection
|
||||||
|
or as requested by operator.
|
||||||
|
|
||||||
|
:param task: a TaskManager instance.
|
||||||
|
:param macs: A dictionary of port numbers to mac addresses
|
||||||
|
returned by node inspection.
|
||||||
|
|
||||||
|
"""
|
||||||
|
node = task.node
|
||||||
|
for port_num, mac in macs.items():
|
||||||
|
port_dict = {'address': mac, 'node_id': node.id}
|
||||||
|
port = objects.Port(task.context, **port_dict)
|
||||||
|
|
||||||
|
try:
|
||||||
|
port.create()
|
||||||
|
LOG.info(_("Port %(port_num)s created for MAC address %(address)s "
|
||||||
|
"for node %(node)s"),
|
||||||
|
{'address': mac, 'node': node.uuid, 'port_num': port_num})
|
||||||
|
except exception.MACAlreadyExists:
|
||||||
|
LOG.warning(_("Port %(port_num)s already exists for MAC address"
|
||||||
|
"%(address)s for node %(node)s"),
|
||||||
|
{'address': mac, 'node': node.uuid,
|
||||||
|
'port_num': port_num})
|
||||||
|
|
||||||
|
|
||||||
def agent_get_clean_steps(task, interface=None, override_priorities=None):
|
def agent_get_clean_steps(task, interface=None, override_priorities=None):
|
||||||
"""Get the list of cached clean steps from the agent.
|
"""Get the list of cached clean steps from the agent.
|
||||||
|
|
||||||
|
@ -22,8 +22,8 @@ from ironic.common import states
|
|||||||
from ironic.common import utils
|
from ironic.common import utils
|
||||||
from ironic.conductor import utils as conductor_utils
|
from ironic.conductor import utils as conductor_utils
|
||||||
from ironic.drivers import base
|
from ironic.drivers import base
|
||||||
|
from ironic.drivers.modules import deploy_utils
|
||||||
from ironic.drivers.modules.ilo import common as ilo_common
|
from ironic.drivers.modules.ilo import common as ilo_common
|
||||||
from ironic import objects
|
|
||||||
|
|
||||||
ilo_error = importutils.try_import('proliantutils.exception')
|
ilo_error = importutils.try_import('proliantutils.exception')
|
||||||
|
|
||||||
@ -48,32 +48,6 @@ CAPABILITIES_KEYS = {'secure_boot', 'rom_firmware_version',
|
|||||||
'nvdimm_n', 'logical_nvdimm_n', 'persistent_memory'}
|
'nvdimm_n', 'logical_nvdimm_n', 'persistent_memory'}
|
||||||
|
|
||||||
|
|
||||||
def _create_ports_if_not_exist(task, macs):
|
|
||||||
"""Create ironic ports for the mac addresses.
|
|
||||||
|
|
||||||
Creates ironic ports for the mac addresses returned with inspection
|
|
||||||
or as requested by operator.
|
|
||||||
|
|
||||||
:param task: a TaskManager instance.
|
|
||||||
:param macs: A dictionary of port numbers to mac addresses
|
|
||||||
returned by node inspection.
|
|
||||||
|
|
||||||
"""
|
|
||||||
node = task.node
|
|
||||||
for mac in macs.values():
|
|
||||||
port_dict = {'address': mac, 'node_id': node.id}
|
|
||||||
port = objects.Port(task.context, **port_dict)
|
|
||||||
|
|
||||||
try:
|
|
||||||
port.create()
|
|
||||||
LOG.info("Port created for MAC address %(address)s for node "
|
|
||||||
"%(node)s", {'address': mac, 'node': node.uuid})
|
|
||||||
except exception.MACAlreadyExists:
|
|
||||||
LOG.warning("Port already exists for MAC address %(address)s "
|
|
||||||
"for node %(node)s",
|
|
||||||
{'address': mac, 'node': node.uuid})
|
|
||||||
|
|
||||||
|
|
||||||
def _get_essential_properties(node, ilo_object):
|
def _get_essential_properties(node, ilo_object):
|
||||||
"""Inspects the node and get essential scheduling properties
|
"""Inspects the node and get essential scheduling properties
|
||||||
|
|
||||||
@ -270,7 +244,7 @@ class IloInspect(base.InspectInterface):
|
|||||||
task.node.save()
|
task.node.save()
|
||||||
|
|
||||||
# Create ports for the nics detected.
|
# Create ports for the nics detected.
|
||||||
_create_ports_if_not_exist(task, result['macs'])
|
deploy_utils.create_ports_if_not_exist(task, result['macs'])
|
||||||
|
|
||||||
LOG.debug("Node properties for %(node)s are updated as "
|
LOG.debug("Node properties for %(node)s are updated as "
|
||||||
"%(properties)s",
|
"%(properties)s",
|
||||||
|
192
ironic/drivers/modules/redfish/inspect.py
Normal file
192
ironic/drivers/modules/redfish/inspect.py
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
"""
|
||||||
|
Redfish Inspect Interface
|
||||||
|
"""
|
||||||
|
|
||||||
|
from oslo_log import log
|
||||||
|
from oslo_utils import importutils
|
||||||
|
from oslo_utils import units
|
||||||
|
|
||||||
|
from ironic.common import exception
|
||||||
|
from ironic.common.i18n import _
|
||||||
|
from ironic.common import states
|
||||||
|
from ironic.drivers import base
|
||||||
|
from ironic.drivers.modules import deploy_utils
|
||||||
|
from ironic.drivers.modules.redfish import utils as redfish_utils
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
sushy = importutils.try_import('sushy')
|
||||||
|
|
||||||
|
if sushy:
|
||||||
|
CPU_ARCH_MAP = {
|
||||||
|
sushy.PROCESSOR_ARCH_x86: 'x86_64',
|
||||||
|
sushy.PROCESSOR_ARCH_IA_64: 'ia64',
|
||||||
|
sushy.PROCESSOR_ARCH_ARM: 'arm',
|
||||||
|
sushy.PROCESSOR_ARCH_MIPS: 'mips',
|
||||||
|
sushy.PROCESSOR_ARCH_OEM: 'oem'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class RedfishInspect(base.InspectInterface):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize the Redfish inspection interface.
|
||||||
|
|
||||||
|
:raises: DriverLoadError if the driver can't be loaded due to
|
||||||
|
missing dependencies
|
||||||
|
"""
|
||||||
|
super(RedfishInspect, self).__init__()
|
||||||
|
if not sushy:
|
||||||
|
raise exception.DriverLoadError(
|
||||||
|
driver='redfish',
|
||||||
|
reason=_('Unable to import the sushy library'))
|
||||||
|
|
||||||
|
def get_properties(self):
|
||||||
|
"""Return the properties of the interface.
|
||||||
|
|
||||||
|
:returns: dictionary of <property name>:<property description> entries.
|
||||||
|
"""
|
||||||
|
return redfish_utils.COMMON_PROPERTIES.copy()
|
||||||
|
|
||||||
|
def validate(self, task):
|
||||||
|
"""Validate the driver-specific Node deployment info.
|
||||||
|
|
||||||
|
This method validates whether the 'driver_info' properties of
|
||||||
|
the task's node contains the required information for this
|
||||||
|
interface to function.
|
||||||
|
|
||||||
|
This method is often executed synchronously in API requests, so it
|
||||||
|
should not conduct long-running checks.
|
||||||
|
|
||||||
|
:param task: A TaskManager instance containing the node to act on.
|
||||||
|
:raises: InvalidParameterValue on malformed parameter(s)
|
||||||
|
:raises: MissingParameterValue on missing parameter(s)
|
||||||
|
"""
|
||||||
|
redfish_utils.parse_driver_info(task.node)
|
||||||
|
|
||||||
|
def inspect_hardware(self, task):
|
||||||
|
"""Inspect hardware to get the hardware properties.
|
||||||
|
|
||||||
|
Inspects hardware to get the essential properties.
|
||||||
|
It fails if any of the essential properties
|
||||||
|
are not received from the node.
|
||||||
|
|
||||||
|
:param task: a TaskManager instance.
|
||||||
|
:raises: HardwareInspectionFailure if essential properties
|
||||||
|
could not be retrieved successfully.
|
||||||
|
:returns: The resulting state of inspection.
|
||||||
|
|
||||||
|
"""
|
||||||
|
system = redfish_utils.get_system(task.node)
|
||||||
|
|
||||||
|
# get the essential properties and update the node properties
|
||||||
|
# with it.
|
||||||
|
inspected_properties = task.node.properties
|
||||||
|
|
||||||
|
if system.memory_summary and system.memory_summary.size_gib:
|
||||||
|
inspected_properties['memory_mb'] = str(
|
||||||
|
system.memory_summary.size_gib * units.Ki)
|
||||||
|
|
||||||
|
if system.processors and system.processors.summary:
|
||||||
|
cpus, arch = system.processors.summary
|
||||||
|
if cpus:
|
||||||
|
inspected_properties['cpus'] = cpus
|
||||||
|
|
||||||
|
if arch:
|
||||||
|
try:
|
||||||
|
inspected_properties['cpu_arch'] = CPU_ARCH_MAP[arch]
|
||||||
|
|
||||||
|
except KeyError:
|
||||||
|
LOG.warning(
|
||||||
|
_("Unknown CPU arch %(arch)s discovered "
|
||||||
|
"for Node %(node)s"), {'node': task.node.uuid,
|
||||||
|
'arch': arch})
|
||||||
|
|
||||||
|
simple_storage_size = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
if (system.simple_storage and
|
||||||
|
system.simple_storage.disks_sizes_bytes):
|
||||||
|
simple_storage_size = [
|
||||||
|
size for size in system.simple_storage.disks_sizes_bytes
|
||||||
|
if size >= 4 * units.Gi
|
||||||
|
] or [0]
|
||||||
|
|
||||||
|
simple_storage_size = simple_storage_size[0]
|
||||||
|
|
||||||
|
except sushy.SushyError:
|
||||||
|
LOG.info(
|
||||||
|
_("No simple storage information discovered "
|
||||||
|
"for Node %(node)s"), {'node': task.node.uuid})
|
||||||
|
|
||||||
|
storage_size = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
if system.storage and system.storage.volumes_sizes_bytes:
|
||||||
|
storage_size = [
|
||||||
|
size for size in system.storage.volumes_sizes_bytes
|
||||||
|
if size >= 4 * units.Gi
|
||||||
|
] or [0]
|
||||||
|
|
||||||
|
storage_size = storage_size[0]
|
||||||
|
|
||||||
|
except sushy.SushyError:
|
||||||
|
LOG.info(_("No storage volume information discovered "
|
||||||
|
"for Node %(node)s"), {'node': task.node.uuid})
|
||||||
|
|
||||||
|
local_gb = max(simple_storage_size, storage_size)
|
||||||
|
|
||||||
|
# Note(deray): Convert the received size to GiB and reduce the
|
||||||
|
# value by 1 GB as consumers like Ironic requires the ``local_gb``
|
||||||
|
# to be returned 1 less than actual size.
|
||||||
|
local_gb = max(0, int(local_gb / units.Gi - 1))
|
||||||
|
|
||||||
|
if local_gb:
|
||||||
|
inspected_properties['local_gb'] = str(local_gb)
|
||||||
|
|
||||||
|
else:
|
||||||
|
LOG.warning(_("Could not provide a valid storage size configured "
|
||||||
|
"for Node %(node)s"), {'node': task.node.uuid})
|
||||||
|
|
||||||
|
valid_keys = self.ESSENTIAL_PROPERTIES
|
||||||
|
missing_keys = valid_keys - set(inspected_properties)
|
||||||
|
if missing_keys:
|
||||||
|
error = (_('Failed to discover the following properties: '
|
||||||
|
'%(missing_keys)s on node %(node)s'),
|
||||||
|
{'missing_keys': ', '.join(missing_keys),
|
||||||
|
'node': task.node.uuid})
|
||||||
|
raise exception.HardwareInspectionFailure(error=error)
|
||||||
|
|
||||||
|
task.node.properties = inspected_properties
|
||||||
|
task.node.save()
|
||||||
|
|
||||||
|
LOG.debug(_("Node properties for %(node)s are updated as "
|
||||||
|
"%(properties)s"),
|
||||||
|
{'properties': inspected_properties,
|
||||||
|
'node': task.node.uuid})
|
||||||
|
|
||||||
|
if (system.ethernet_interfaces and
|
||||||
|
system.ethernet_interfaces.eth_summary):
|
||||||
|
macs = system.ethernet_interfaces.eth_summary
|
||||||
|
|
||||||
|
# Create ports for the nics detected.
|
||||||
|
deploy_utils.create_ports_if_not_exist(task, macs)
|
||||||
|
|
||||||
|
else:
|
||||||
|
LOG.info(_("No NIC information discovered "
|
||||||
|
"for Node %(node)s"), {'node': task.node.uuid})
|
||||||
|
|
||||||
|
LOG.info(_("Node %(node)s inspected."), {'node': task.node.uuid})
|
||||||
|
|
||||||
|
return states.MANAGEABLE
|
@ -14,6 +14,9 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from ironic.drivers import generic
|
from ironic.drivers import generic
|
||||||
|
from ironic.drivers.modules import inspector
|
||||||
|
from ironic.drivers.modules import noop
|
||||||
|
from ironic.drivers.modules.redfish import inspect as redfish_inspect
|
||||||
from ironic.drivers.modules.redfish import management as redfish_mgmt
|
from ironic.drivers.modules.redfish import management as redfish_mgmt
|
||||||
from ironic.drivers.modules.redfish import power as redfish_power
|
from ironic.drivers.modules.redfish import power as redfish_power
|
||||||
|
|
||||||
@ -30,3 +33,9 @@ class RedfishHardware(generic.GenericHardware):
|
|||||||
def supported_power_interfaces(self):
|
def supported_power_interfaces(self):
|
||||||
"""List of supported power interfaces."""
|
"""List of supported power interfaces."""
|
||||||
return [redfish_power.RedfishPower]
|
return [redfish_power.RedfishPower]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def supported_inspect_interfaces(self):
|
||||||
|
"""List of supported power interfaces."""
|
||||||
|
return [redfish_inspect.RedfishInspect, inspector.Inspector,
|
||||||
|
noop.NoInspect]
|
||||||
|
@ -23,10 +23,10 @@ from ironic.common import states
|
|||||||
from ironic.common import utils
|
from ironic.common import utils
|
||||||
from ironic.conductor import task_manager
|
from ironic.conductor import task_manager
|
||||||
from ironic.conductor import utils as conductor_utils
|
from ironic.conductor import utils as conductor_utils
|
||||||
|
from ironic.drivers.modules import deploy_utils
|
||||||
from ironic.drivers.modules.ilo import common as ilo_common
|
from ironic.drivers.modules.ilo import common as ilo_common
|
||||||
from ironic.drivers.modules.ilo import inspect as ilo_inspect
|
from ironic.drivers.modules.ilo import inspect as ilo_inspect
|
||||||
from ironic.drivers.modules.ilo import power as ilo_power
|
from ironic.drivers.modules.ilo import power as ilo_power
|
||||||
from ironic import objects
|
|
||||||
from ironic.tests.unit.drivers.modules.ilo import test_common
|
from ironic.tests.unit.drivers.modules.ilo import test_common
|
||||||
|
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ class IloInspectTestCase(test_common.BaseIloTest):
|
|||||||
|
|
||||||
@mock.patch.object(ilo_inspect, '_get_capabilities', spec_set=True,
|
@mock.patch.object(ilo_inspect, '_get_capabilities', spec_set=True,
|
||||||
autospec=True)
|
autospec=True)
|
||||||
@mock.patch.object(ilo_inspect, '_create_ports_if_not_exist',
|
@mock.patch.object(deploy_utils, 'create_ports_if_not_exist',
|
||||||
spec_set=True, autospec=True)
|
spec_set=True, autospec=True)
|
||||||
@mock.patch.object(ilo_inspect, '_get_essential_properties', spec_set=True,
|
@mock.patch.object(ilo_inspect, '_get_essential_properties', spec_set=True,
|
||||||
autospec=True)
|
autospec=True)
|
||||||
@ -88,7 +88,7 @@ class IloInspectTestCase(test_common.BaseIloTest):
|
|||||||
spec_set=True, autospec=True)
|
spec_set=True, autospec=True)
|
||||||
@mock.patch.object(ilo_inspect, '_get_capabilities', spec_set=True,
|
@mock.patch.object(ilo_inspect, '_get_capabilities', spec_set=True,
|
||||||
autospec=True)
|
autospec=True)
|
||||||
@mock.patch.object(ilo_inspect, '_create_ports_if_not_exist',
|
@mock.patch.object(deploy_utils, 'create_ports_if_not_exist',
|
||||||
spec_set=True, autospec=True)
|
spec_set=True, autospec=True)
|
||||||
@mock.patch.object(ilo_inspect, '_get_essential_properties', spec_set=True,
|
@mock.patch.object(ilo_inspect, '_get_essential_properties', spec_set=True,
|
||||||
autospec=True)
|
autospec=True)
|
||||||
@ -131,7 +131,7 @@ class IloInspectTestCase(test_common.BaseIloTest):
|
|||||||
|
|
||||||
@mock.patch.object(ilo_inspect, '_get_capabilities', spec_set=True,
|
@mock.patch.object(ilo_inspect, '_get_capabilities', spec_set=True,
|
||||||
autospec=True)
|
autospec=True)
|
||||||
@mock.patch.object(ilo_inspect, '_create_ports_if_not_exist',
|
@mock.patch.object(deploy_utils, 'create_ports_if_not_exist',
|
||||||
spec_set=True, autospec=True)
|
spec_set=True, autospec=True)
|
||||||
@mock.patch.object(ilo_inspect, '_get_essential_properties', spec_set=True,
|
@mock.patch.object(ilo_inspect, '_get_essential_properties', spec_set=True,
|
||||||
autospec=True)
|
autospec=True)
|
||||||
@ -170,7 +170,7 @@ class IloInspectTestCase(test_common.BaseIloTest):
|
|||||||
|
|
||||||
@mock.patch.object(ilo_inspect, '_get_capabilities', spec_set=True,
|
@mock.patch.object(ilo_inspect, '_get_capabilities', spec_set=True,
|
||||||
autospec=True)
|
autospec=True)
|
||||||
@mock.patch.object(ilo_inspect, '_create_ports_if_not_exist',
|
@mock.patch.object(deploy_utils, 'create_ports_if_not_exist',
|
||||||
spec_set=True, autospec=True)
|
spec_set=True, autospec=True)
|
||||||
@mock.patch.object(ilo_inspect, '_get_essential_properties', spec_set=True,
|
@mock.patch.object(ilo_inspect, '_get_essential_properties', spec_set=True,
|
||||||
autospec=True)
|
autospec=True)
|
||||||
@ -209,7 +209,7 @@ class IloInspectTestCase(test_common.BaseIloTest):
|
|||||||
|
|
||||||
@mock.patch.object(ilo_inspect, '_get_capabilities', spec_set=True,
|
@mock.patch.object(ilo_inspect, '_get_capabilities', spec_set=True,
|
||||||
autospec=True)
|
autospec=True)
|
||||||
@mock.patch.object(ilo_inspect, '_create_ports_if_not_exist',
|
@mock.patch.object(deploy_utils, 'create_ports_if_not_exist',
|
||||||
spec_set=True, autospec=True)
|
spec_set=True, autospec=True)
|
||||||
@mock.patch.object(ilo_inspect, '_get_essential_properties', spec_set=True,
|
@mock.patch.object(ilo_inspect, '_get_essential_properties', spec_set=True,
|
||||||
autospec=True)
|
autospec=True)
|
||||||
@ -256,38 +256,6 @@ class IloInspectTestCase(test_common.BaseIloTest):
|
|||||||
|
|
||||||
class TestInspectPrivateMethods(test_common.BaseIloTest):
|
class TestInspectPrivateMethods(test_common.BaseIloTest):
|
||||||
|
|
||||||
@mock.patch.object(ilo_inspect.LOG, 'info', spec_set=True, autospec=True)
|
|
||||||
@mock.patch.object(objects, 'Port', spec_set=True, autospec=True)
|
|
||||||
def test__create_ports_if_not_exist(self, port_mock, log_mock):
|
|
||||||
macs = {'Port 1': 'aa:aa:aa:aa:aa:aa', 'Port 2': 'bb:bb:bb:bb:bb:bb'}
|
|
||||||
node_id = self.node.id
|
|
||||||
port_dict1 = {'address': 'aa:aa:aa:aa:aa:aa', 'node_id': node_id}
|
|
||||||
port_dict2 = {'address': 'bb:bb:bb:bb:bb:bb', 'node_id': node_id}
|
|
||||||
port_obj1, port_obj2 = mock.MagicMock(), mock.MagicMock()
|
|
||||||
port_mock.side_effect = [port_obj1, port_obj2]
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
|
||||||
shared=False) as task:
|
|
||||||
ilo_inspect._create_ports_if_not_exist(task, macs)
|
|
||||||
self.assertTrue(log_mock.called)
|
|
||||||
expected_calls = [mock.call(task.context, **port_dict1),
|
|
||||||
mock.call(task.context, **port_dict2)]
|
|
||||||
port_mock.assert_has_calls(expected_calls, any_order=True)
|
|
||||||
port_obj1.create.assert_called_once_with()
|
|
||||||
port_obj2.create.assert_called_once_with()
|
|
||||||
|
|
||||||
@mock.patch.object(ilo_inspect.LOG, 'warning',
|
|
||||||
spec_set=True, autospec=True)
|
|
||||||
@mock.patch.object(objects.Port, 'create', spec_set=True, autospec=True)
|
|
||||||
def test__create_ports_if_not_exist_mac_exception(self,
|
|
||||||
create_mock,
|
|
||||||
log_mock):
|
|
||||||
create_mock.side_effect = exception.MACAlreadyExists('f')
|
|
||||||
macs = {'Port 1': 'aa:aa:aa:aa:aa:aa', 'Port 2': 'bb:bb:bb:bb:bb:bb'}
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
|
||||||
shared=False) as task:
|
|
||||||
ilo_inspect._create_ports_if_not_exist(task, macs)
|
|
||||||
self.assertEqual(2, log_mock.call_count)
|
|
||||||
|
|
||||||
def test__get_essential_properties_ok(self):
|
def test__get_essential_properties_ok(self):
|
||||||
ilo_mock = mock.MagicMock(spec=['get_essential_properties'])
|
ilo_mock = mock.MagicMock(spec=['get_essential_properties'])
|
||||||
properties = {'memory_mb': '512', 'local_gb': '10',
|
properties = {'memory_mb': '512', 'local_gb': '10',
|
||||||
|
189
ironic/tests/unit/drivers/modules/redfish/test_inspect.py
Normal file
189
ironic/tests/unit/drivers/modules/redfish/test_inspect.py
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
# Copyright 2017 Red Hat, Inc.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import mock
|
||||||
|
from oslo_utils import importutils
|
||||||
|
from oslo_utils import units
|
||||||
|
|
||||||
|
from ironic.common import exception
|
||||||
|
from ironic.conductor import task_manager
|
||||||
|
from ironic.drivers.modules import deploy_utils
|
||||||
|
from ironic.drivers.modules.redfish import utils as redfish_utils
|
||||||
|
from ironic.tests.unit.db import base as db_base
|
||||||
|
from ironic.tests.unit.db import utils as db_utils
|
||||||
|
from ironic.tests.unit.objects import utils as obj_utils
|
||||||
|
|
||||||
|
sushy = importutils.try_import('sushy')
|
||||||
|
|
||||||
|
INFO_DICT = db_utils.get_test_redfish_info()
|
||||||
|
|
||||||
|
|
||||||
|
class MockedSushyError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class RedfishInspectTestCase(db_base.DbTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(RedfishInspectTestCase, self).setUp()
|
||||||
|
self.config(enabled_hardware_types=['redfish'],
|
||||||
|
enabled_power_interfaces=['redfish'],
|
||||||
|
enabled_management_interfaces=['redfish'],
|
||||||
|
enabled_inspect_interfaces=['redfish'])
|
||||||
|
self.node = obj_utils.create_test_node(
|
||||||
|
self.context, driver='redfish', driver_info=INFO_DICT)
|
||||||
|
|
||||||
|
def init_system_mock(self, system_mock, **properties):
|
||||||
|
|
||||||
|
system_mock.reset()
|
||||||
|
|
||||||
|
system_mock.memory_summary.size_gib = 2
|
||||||
|
|
||||||
|
system_mock.processors.summary = '8', 'MIPS'
|
||||||
|
|
||||||
|
system_mock.simple_storage.disks_sizes_bytes = (
|
||||||
|
1 * units.Gi, units.Gi * 3, units.Gi * 5)
|
||||||
|
system_mock.storage.volumes_sizes_bytes = (
|
||||||
|
2 * units.Gi, units.Gi * 4, units.Gi * 6)
|
||||||
|
|
||||||
|
system_mock.ethernet_interfaces.eth_summary = {
|
||||||
|
'1': '00:11:22:33:44:55', '2': '66:77:88:99:AA:BB'
|
||||||
|
}
|
||||||
|
|
||||||
|
return system_mock
|
||||||
|
|
||||||
|
def test_get_properties(self):
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
properties = task.driver.get_properties()
|
||||||
|
for prop in redfish_utils.COMMON_PROPERTIES:
|
||||||
|
self.assertIn(prop, properties)
|
||||||
|
|
||||||
|
@mock.patch.object(redfish_utils, 'parse_driver_info', autospec=True)
|
||||||
|
def test_validate(self, mock_parse_driver_info):
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
task.driver.management.validate(task)
|
||||||
|
mock_parse_driver_info.assert_called_once_with(task.node)
|
||||||
|
|
||||||
|
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||||
|
@mock.patch.object(deploy_utils, 'create_ports_if_not_exist',
|
||||||
|
autospec=True)
|
||||||
|
def test_inspect_hardware_ok(self, mock_create_ports_if_not_exist,
|
||||||
|
mock_get_system):
|
||||||
|
expected_properties = {
|
||||||
|
'cpu_arch': 'mips', 'cpus': '8',
|
||||||
|
'local_gb': '4', 'memory_mb': '2048'
|
||||||
|
}
|
||||||
|
|
||||||
|
system = self.init_system_mock(mock_get_system.return_value)
|
||||||
|
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
task.driver.inspect.inspect_hardware(task)
|
||||||
|
mock_create_ports_if_not_exist.assert_called_once_with(
|
||||||
|
task, system.ethernet_interfaces.eth_summary)
|
||||||
|
mock_get_system.assert_called_once_with(task.node)
|
||||||
|
self.assertEqual(expected_properties, task.node.properties)
|
||||||
|
|
||||||
|
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||||
|
def test_inspect_hardware_fail_missing_cpu(self, mock_get_system):
|
||||||
|
system_mock = self.init_system_mock(mock_get_system.return_value)
|
||||||
|
system_mock.processors.summary = None, None
|
||||||
|
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
task.node.properties.pop('cpu_arch')
|
||||||
|
self.assertRaises(exception.HardwareInspectionFailure,
|
||||||
|
task.driver.inspect.inspect_hardware, task)
|
||||||
|
|
||||||
|
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||||
|
def test_inspect_hardware_ignore_missing_cpu(self, mock_get_system):
|
||||||
|
system_mock = self.init_system_mock(mock_get_system.return_value)
|
||||||
|
system_mock.processors.summary = None, None
|
||||||
|
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
expected_properties = {
|
||||||
|
'cpu_arch': 'x86_64', 'cpus': '8',
|
||||||
|
'local_gb': '4', 'memory_mb': '2048'
|
||||||
|
}
|
||||||
|
task.driver.inspect.inspect_hardware(task)
|
||||||
|
self.assertEqual(expected_properties, task.node.properties)
|
||||||
|
|
||||||
|
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||||
|
def test_inspect_hardware_fail_missing_local_gb(self, mock_get_system):
|
||||||
|
system_mock = self.init_system_mock(mock_get_system.return_value)
|
||||||
|
system_mock.simple_storage.disks_sizes_bytes = None
|
||||||
|
system_mock.storage.volumes_sizes_bytes = None
|
||||||
|
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
task.node.properties.pop('local_gb')
|
||||||
|
self.assertRaises(exception.HardwareInspectionFailure,
|
||||||
|
task.driver.inspect.inspect_hardware, task)
|
||||||
|
|
||||||
|
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||||
|
def test_inspect_hardware_ignore_missing_local_gb(self, mock_get_system):
|
||||||
|
system_mock = self.init_system_mock(mock_get_system.return_value)
|
||||||
|
system_mock.simple_storage.disks_sizes_bytes = None
|
||||||
|
system_mock.storage.volumes_sizes_bytes = None
|
||||||
|
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
expected_properties = {
|
||||||
|
'cpu_arch': 'mips', 'cpus': '8',
|
||||||
|
'local_gb': '10', 'memory_mb': '2048'
|
||||||
|
}
|
||||||
|
task.driver.inspect.inspect_hardware(task)
|
||||||
|
self.assertEqual(expected_properties, task.node.properties)
|
||||||
|
|
||||||
|
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||||
|
def test_inspect_hardware_fail_missing_memory_mb(self, mock_get_system):
|
||||||
|
system_mock = self.init_system_mock(mock_get_system.return_value)
|
||||||
|
system_mock.memory_summary.size_gib = None
|
||||||
|
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
task.node.properties.pop('memory_mb')
|
||||||
|
self.assertRaises(exception.HardwareInspectionFailure,
|
||||||
|
task.driver.inspect.inspect_hardware, task)
|
||||||
|
|
||||||
|
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||||
|
def test_inspect_hardware_ignore_missing_memory_mb(self, mock_get_system):
|
||||||
|
system_mock = self.init_system_mock(mock_get_system.return_value)
|
||||||
|
system_mock.memory_summary.size_gib = None
|
||||||
|
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
expected_properties = {
|
||||||
|
'cpu_arch': 'mips', 'cpus': '8',
|
||||||
|
'local_gb': '4', 'memory_mb': '4096'
|
||||||
|
}
|
||||||
|
task.driver.inspect.inspect_hardware(task)
|
||||||
|
self.assertEqual(expected_properties, task.node.properties)
|
||||||
|
|
||||||
|
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||||
|
@mock.patch.object(deploy_utils, 'create_ports_if_not_exist',
|
||||||
|
autospec=True)
|
||||||
|
def test_inspect_hardware_ignore_missing_nics(
|
||||||
|
self, mock_create_ports_if_not_exist, mock_get_system):
|
||||||
|
system_mock = self.init_system_mock(mock_get_system.return_value)
|
||||||
|
system_mock.ethernet_interfaces.eth_summary = None
|
||||||
|
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
task.driver.inspect.inspect_hardware(task)
|
||||||
|
self.assertFalse(mock_create_ports_if_not_exist.called)
|
@ -41,7 +41,8 @@ class RedfishManagementTestCase(db_base.DbTestCase):
|
|||||||
super(RedfishManagementTestCase, self).setUp()
|
super(RedfishManagementTestCase, self).setUp()
|
||||||
self.config(enabled_hardware_types=['redfish'],
|
self.config(enabled_hardware_types=['redfish'],
|
||||||
enabled_power_interfaces=['redfish'],
|
enabled_power_interfaces=['redfish'],
|
||||||
enabled_management_interfaces=['redfish'])
|
enabled_management_interfaces=['redfish'],
|
||||||
|
enabled_inspect_interfaces=['redfish'])
|
||||||
self.node = obj_utils.create_test_node(
|
self.node = obj_utils.create_test_node(
|
||||||
self.context, driver='redfish', driver_info=INFO_DICT)
|
self.context, driver='redfish', driver_info=INFO_DICT)
|
||||||
|
|
||||||
|
@ -41,7 +41,8 @@ class RedfishPowerTestCase(db_base.DbTestCase):
|
|||||||
super(RedfishPowerTestCase, self).setUp()
|
super(RedfishPowerTestCase, self).setUp()
|
||||||
self.config(enabled_hardware_types=['redfish'],
|
self.config(enabled_hardware_types=['redfish'],
|
||||||
enabled_power_interfaces=['redfish'],
|
enabled_power_interfaces=['redfish'],
|
||||||
enabled_management_interfaces=['redfish'])
|
enabled_management_interfaces=['redfish'],
|
||||||
|
enabled_inspect_interfaces=['redfish'])
|
||||||
self.node = obj_utils.create_test_node(
|
self.node = obj_utils.create_test_node(
|
||||||
self.context, driver='redfish', driver_info=INFO_DICT)
|
self.context, driver='redfish', driver_info=INFO_DICT)
|
||||||
|
|
||||||
|
@ -44,6 +44,7 @@ from ironic.drivers.modules import image_cache
|
|||||||
from ironic.drivers.modules import pxe
|
from ironic.drivers.modules import pxe
|
||||||
from ironic.drivers.modules.storage import cinder
|
from ironic.drivers.modules.storage import cinder
|
||||||
from ironic.drivers import utils as driver_utils
|
from ironic.drivers import utils as driver_utils
|
||||||
|
from ironic import objects
|
||||||
from ironic.tests import base as tests_base
|
from ironic.tests import base as tests_base
|
||||||
from ironic.tests.unit.db import base as db_base
|
from ironic.tests.unit.db import base as db_base
|
||||||
from ironic.tests.unit.db import utils as db_utils
|
from ironic.tests.unit.db import utils as db_utils
|
||||||
@ -1307,6 +1308,38 @@ class OtherFunctionTestCase(db_base.DbTestCase):
|
|||||||
self.assertRaises(exception.InvalidParameterValue,
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
utils.get_ironic_api_url)
|
utils.get_ironic_api_url)
|
||||||
|
|
||||||
|
@mock.patch.object(utils.LOG, 'info', spec_set=True, autospec=True)
|
||||||
|
@mock.patch.object(objects, 'Port', spec_set=True, autospec=True)
|
||||||
|
def test_create_ports_if_not_exist(self, port_mock, log_mock):
|
||||||
|
macs = {'Port 1': 'aa:aa:aa:aa:aa:aa', 'Port 2': 'bb:bb:bb:bb:bb:bb'}
|
||||||
|
node_id = self.node.id
|
||||||
|
port_dict1 = {'address': 'aa:aa:aa:aa:aa:aa', 'node_id': node_id}
|
||||||
|
port_dict2 = {'address': 'bb:bb:bb:bb:bb:bb', 'node_id': node_id}
|
||||||
|
port_obj1, port_obj2 = mock.MagicMock(), mock.MagicMock()
|
||||||
|
port_mock.side_effect = [port_obj1, port_obj2]
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=False) as task:
|
||||||
|
utils.create_ports_if_not_exist(task, macs)
|
||||||
|
self.assertTrue(log_mock.called)
|
||||||
|
expected_calls = [mock.call(task.context, **port_dict1),
|
||||||
|
mock.call(task.context, **port_dict2)]
|
||||||
|
port_mock.assert_has_calls(expected_calls, any_order=True)
|
||||||
|
port_obj1.create.assert_called_once_with()
|
||||||
|
port_obj2.create.assert_called_once_with()
|
||||||
|
|
||||||
|
@mock.patch.object(utils.LOG, 'warning',
|
||||||
|
spec_set=True, autospec=True)
|
||||||
|
@mock.patch.object(objects.Port, 'create', spec_set=True, autospec=True)
|
||||||
|
def test_create_ports_if_not_exist_mac_exception(self,
|
||||||
|
create_mock,
|
||||||
|
log_mock):
|
||||||
|
create_mock.side_effect = exception.MACAlreadyExists('f')
|
||||||
|
macs = {'Port 1': 'aa:aa:aa:aa:aa:aa', 'Port 2': 'bb:bb:bb:bb:bb:bb'}
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=False) as task:
|
||||||
|
utils.create_ports_if_not_exist(task, macs)
|
||||||
|
self.assertEqual(2, log_mock.call_count)
|
||||||
|
|
||||||
|
|
||||||
class GetSingleNicTestCase(db_base.DbTestCase):
|
class GetSingleNicTestCase(db_base.DbTestCase):
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ from ironic.conductor import task_manager
|
|||||||
from ironic.drivers.modules import iscsi_deploy
|
from ironic.drivers.modules import iscsi_deploy
|
||||||
from ironic.drivers.modules import noop
|
from ironic.drivers.modules import noop
|
||||||
from ironic.drivers.modules import pxe
|
from ironic.drivers.modules import pxe
|
||||||
|
from ironic.drivers.modules.redfish import inspect as redfish_inspect
|
||||||
from ironic.drivers.modules.redfish import management as redfish_mgmt
|
from ironic.drivers.modules.redfish import management as redfish_mgmt
|
||||||
from ironic.drivers.modules.redfish import power as redfish_power
|
from ironic.drivers.modules.redfish import power as redfish_power
|
||||||
from ironic.tests.unit.db import base as db_base
|
from ironic.tests.unit.db import base as db_base
|
||||||
@ -29,11 +30,14 @@ class RedfishHardwareTestCase(db_base.DbTestCase):
|
|||||||
super(RedfishHardwareTestCase, self).setUp()
|
super(RedfishHardwareTestCase, self).setUp()
|
||||||
self.config(enabled_hardware_types=['redfish'],
|
self.config(enabled_hardware_types=['redfish'],
|
||||||
enabled_power_interfaces=['redfish'],
|
enabled_power_interfaces=['redfish'],
|
||||||
enabled_management_interfaces=['redfish'])
|
enabled_management_interfaces=['redfish'],
|
||||||
|
enabled_inspect_interfaces=['redfish'])
|
||||||
|
|
||||||
def test_default_interfaces(self):
|
def test_default_interfaces(self):
|
||||||
node = obj_utils.create_test_node(self.context, driver='redfish')
|
node = obj_utils.create_test_node(self.context, driver='redfish')
|
||||||
with task_manager.acquire(self.context, node.id) as task:
|
with task_manager.acquire(self.context, node.id) as task:
|
||||||
|
self.assertIsInstance(task.driver.inspect,
|
||||||
|
redfish_inspect.RedfishInspect)
|
||||||
self.assertIsInstance(task.driver.management,
|
self.assertIsInstance(task.driver.management,
|
||||||
redfish_mgmt.RedfishManagement)
|
redfish_mgmt.RedfishManagement)
|
||||||
self.assertIsInstance(task.driver.power,
|
self.assertIsInstance(task.driver.power,
|
||||||
|
@ -146,6 +146,11 @@ SUSHY_CONSTANTS_SPEC = (
|
|||||||
'BOOT_SOURCE_ENABLED_ONCE',
|
'BOOT_SOURCE_ENABLED_ONCE',
|
||||||
'BOOT_SOURCE_MODE_BIOS',
|
'BOOT_SOURCE_MODE_BIOS',
|
||||||
'BOOT_SOURCE_MODE_UEFI',
|
'BOOT_SOURCE_MODE_UEFI',
|
||||||
|
'PROCESSOR_ARCH_x86',
|
||||||
|
'PROCESSOR_ARCH_IA_64',
|
||||||
|
'PROCESSOR_ARCH_ARM',
|
||||||
|
'PROCESSOR_ARCH_MIPS',
|
||||||
|
'PROCESSOR_ARCH_OEM',
|
||||||
)
|
)
|
||||||
|
|
||||||
XCLARITY_SPEC = (
|
XCLARITY_SPEC = (
|
||||||
|
@ -219,7 +219,12 @@ if not sushy:
|
|||||||
BOOT_SOURCE_ENABLED_CONTINUOUS='continuous',
|
BOOT_SOURCE_ENABLED_CONTINUOUS='continuous',
|
||||||
BOOT_SOURCE_ENABLED_ONCE='once',
|
BOOT_SOURCE_ENABLED_ONCE='once',
|
||||||
BOOT_SOURCE_MODE_BIOS='bios',
|
BOOT_SOURCE_MODE_BIOS='bios',
|
||||||
BOOT_SOURCE_MODE_UEFI='uefi'
|
BOOT_SOURCE_MODE_UEFI='uefi',
|
||||||
|
PROCESSOR_ARCH_x86='x86 or x86-64',
|
||||||
|
PROCESSOR_ARCH_IA_64='Intel Itanium',
|
||||||
|
PROCESSOR_ARCH_ARM='ARM',
|
||||||
|
PROCESSOR_ARCH_MIPS='MIPS',
|
||||||
|
PROCESSOR_ARCH_OEM='OEM-defined',
|
||||||
)
|
)
|
||||||
|
|
||||||
sys.modules['sushy'] = sushy
|
sys.modules['sushy'] = sushy
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Adds out-of-band inspection support to ``redfish`` hardware type'.
|
||||||
|
Successful inspection populates mandatory properties: "cpus",
|
||||||
|
"local_gb", "cpu_arch", "memory_mb" and creates ironic ports
|
||||||
|
for inspected nodes.
|
@ -88,6 +88,7 @@ ironic.hardware.interfaces.inspect =
|
|||||||
inspector = ironic.drivers.modules.inspector:Inspector
|
inspector = ironic.drivers.modules.inspector:Inspector
|
||||||
irmc = ironic.drivers.modules.irmc.inspect:IRMCInspect
|
irmc = ironic.drivers.modules.irmc.inspect:IRMCInspect
|
||||||
no-inspect = ironic.drivers.modules.noop:NoInspect
|
no-inspect = ironic.drivers.modules.noop:NoInspect
|
||||||
|
redfish = ironic.drivers.modules.redfish.inspect:RedfishInspect
|
||||||
|
|
||||||
ironic.hardware.interfaces.management =
|
ironic.hardware.interfaces.management =
|
||||||
cimc = ironic.drivers.modules.cimc.management:CIMCManagement
|
cimc = ironic.drivers.modules.cimc.management:CIMCManagement
|
||||||
|
Loading…
Reference in New Issue
Block a user