Merge "Remove inspector inspect interface"

This commit is contained in:
Zuul
2025-11-25 06:50:05 +00:00
committed by Gerrit Code Review
36 changed files with 57 additions and 1098 deletions

View File

@@ -59,10 +59,6 @@ As usual with the ``noop`` management, enable the networking boot fallback:
[pxe]
enable_netboot_fallback = true
If using discovery, :ironic-inspector-doc:`configure discovery in
ironic-inspector <user/usage.html#discovery>` with the default driver set
to ``manual-management``.
Limitations
===========

View File

@@ -92,7 +92,7 @@ Interface Supported Implementations
``deploy`` ``direct``, ``ansible``, ``ramdisk``
``firmware`` ``redfish``, ``no-firmware``
``inspect`` ``idrac-redfish``,
``inspector``, ``no-inspect``
``agent``, ``no-inspect``
``management`` ``idrac-redfish``
``network`` ``flat``, ``neutron``, ``noop``
``power`` ``redfish``, ``idrac-redfish``

View File

@@ -145,7 +145,7 @@ The ``ilo`` hardware type supports following hardware interfaces:
management engine.
* inspect
Supports ``ilo`` and ``inspector``. The default is ``ilo``. They
Supports ``ilo`` and ``agent``. The default is ``ilo``. They
can be enabled by using the :oslo.config:option:`DEFAULT.enabled_inspect_interfaces` option
in ``ironic.conf`` as given below:
@@ -153,11 +153,7 @@ The ``ilo`` hardware type supports following hardware interfaces:
[DEFAULT]
enabled_hardware_types = ilo
enabled_inspect_interfaces = ilo,inspector
.. note::
:ironic-inspector-doc:`Ironic Inspector <>`
needs to be configured to use ``inspector`` as the inspect interface.
enabled_inspect_interfaces = ilo,agent
* management
Supports only ``ilo``. It can be enabled by using the

View File

@@ -65,14 +65,9 @@ hardware interfaces:
The default is ``ipmitool-socat``.
* inspect
Supports ``irmc``, ``inspector``, and ``no-inspect``.
Supports ``irmc``, ``agent``, and ``no-inspect``.
The default is ``irmc``.
.. note::
:ironic-inspector-doc:`Ironic Inspector <>`
needs to be present and configured to use ``inspector`` as the
inspect interface.
* management
Supports only ``irmc``.
@@ -100,7 +95,7 @@ interfaces enabled for ``irmc`` hardware type.
enabled_boot_interfaces = irmc-virtual-media,irmc-pxe
enabled_console_interfaces = ipmitool-socat,ipmitool-shellinabox,no-console
enabled_deploy_interfaces = direct
enabled_inspect_interfaces = irmc,inspector,no-inspect
enabled_inspect_interfaces = irmc,agent,no-inspect
enabled_management_interfaces = irmc
enabled_network_interfaces = flat,neutron
enabled_power_interfaces = irmc

View File

@@ -385,8 +385,8 @@ Out-Of-Band inspection
The ``redfish`` hardware type can inspect the bare metal node by querying
Redfish compatible BMC. This process is quick and reliable compared to the
way the ``inspector`` hardware type works i.e. booting bare metal node
into the introspection ramdisk.
way the ``agent`` hardware type works i.e. booting bare metal node into
the introspection ramdisk.
.. note::

View File

@@ -48,9 +48,9 @@ node:
Inspection
----------
If using :ref:`in-band inspection`, you need to tell ironic or ironic-inspector
not to power off nodes afterwards. Depending on the inspection mode (managed or
unmanaged, with ironic-inspector or without), you need to configure two places.
If using :ref:`in-band inspection`, you need to tell ironic not to power off
nodes afterwards. Depending on the inspection mode (managed or unmanaged),
you need to configure two places.
In ``ironic.conf``:
.. code-block:: ini
@@ -58,13 +58,6 @@ In ``ironic.conf``:
[inspector]
power_off = false
And in ``inspector.conf`` (if needed):
.. code-block:: ini
[processing]
power_off = false
Finally, you need to update the :ref:`inspection PXE/iPXE
configuration <configure-unmanaged-inspection>` to include the
``ipa-api-url`` kernel parameter, pointing at the **ironic** endpoint, in

View File

@@ -18,9 +18,6 @@ There are three kinds of inspection supported by Bare Metal service:
#. :doc:`In-band inspection </admin/inspection/index>` utilizing Ironic Python
Agent to collect information.
#. :doc:`Older in-band inspection </admin/inspection/inspector>` implementation
utilizing the ironic-inspector_ project. This is now **deprecated**.
The node should be in the ``manageable`` state before inspection is initiated.
If it is in the ``enroll`` or ``available`` state, move it to ``manageable``
first::
@@ -31,8 +28,6 @@ Then inspection can be initiated using the following command::
baremetal node inspect <node_UUID>
.. _ironic-inspector: https://pypi.org/project/ironic-inspector
.. _capabilities-discovery:
Capabilities discovery

View File

@@ -7,20 +7,11 @@ information directly from it. This process is more fragile and time-consuming
than the out-of-band inspection, but it is not vendor-specific and works
across a wide range of hardware.
In the 2023.2 "Bobcat" release series, Ironic received an experimental
implementation of in-band inspection that does not require the separate
ironic-inspector_ service.
.. note::
The implementation described in this document is not 100% compatible with
the previous one (based on ironic-inspector_). Check the documentation and
the previous one (based on ironic-inspector). Check the documentation and
the release notes for which features are currently available.
Use :doc:`inspector` for production deployments of Ironic 2023.2 or earlier
releases.
.. _ironic-inspector: https://pypi.org/project/ironic-inspector
.. toctree::
:maxdepth: 2
@@ -38,8 +29,6 @@ stored data, nor does it support the ``"scope"`` field.
The scope field allowed a rule to be applied only to specific nodes with
matching scope value rather than all nodes where conditions are met.
:ironic-inspector-doc:`Inspection rules <user/usage.html#introspection-rules>`
Inspection Rules
----------------

View File

@@ -1,42 +0,0 @@
Inspector Support
=================
Ironic supports in-band inspection using the ironic-inspector_ project. This
is the original in-band inspection implementation, which is being gradually
phased out in favour of a similar implementation inside Ironic proper.
It is supported by all hardware types, and used by default, if enabled, by the
``ipmi`` hardware type. The ``inspector`` *inspect* interface has to be
enabled to use it:
.. code-block:: ini
[DEFAULT]
enabled_inspect_interfaces = inspector,no-inspect
If the ironic-inspector service is not registered in the service catalog, set
the following option:
.. code-block:: ini
[inspector]
endpoint_override = http://inspector.example.com:5050
In order to ensure that ports in Bare Metal service are synchronized with
NIC ports on the node, the following settings in the ironic-inspector
configuration file must be set:
.. code-block:: ini
[processing]
add_ports = all
keep_ports = present
Managed and unmanaged inspection
--------------------------------
There are two modes of in-band inspection: *managed* inspection and *unmanaged*
inspection. See :doc:`/admin/inspection/managed` for more details.
.. _ironic-inspector: https://pypi.org/project/ironic-inspector
.. _python-ironicclient: https://pypi.org/project/python-ironicclient

View File

@@ -34,15 +34,6 @@ inspection specifically. This is where you can configure
[inspector]
extra_kernel_params = ipa-inspection-collectors=default,logs ipa-collect-lldp=1
For the callback URL the ironic-inspector endpoint from the service catalog is
used. If you want to override the endpoint for callback only, set the following
option:
.. code-block:: ini
[inspector]
callback_endpoint_override = https://example.com/baremetal-introspection/v1/continue
For the built-in inspection, the bare metal API endpoint can be overridden
instead:

View File

@@ -6,10 +6,6 @@ that is responsible for :ref:`unmanaged-inspection`. Running it allows
this dnsmasq instance to co-exist with the OpenStack Networking service's DHCP
server on the same physical network.
.. warning::
The PXE filter service is currently experimental. For a production grade
solution, please stay with ironic-inspector for the time being.
How it works?
-------------

View File

@@ -220,13 +220,7 @@ There is a possibility of utilizing
a machine, however Grub2's capabilities for booting a machine are extremely
limited when compared to a tool like iPXE. It is also worth noting the bulk
of Ironic's example configurations utilize iPXE, including whole activities
like unmanaged hardware introspection with ironic-inspector.
For extra context, unmanaged introspection is when you ask ironic-inspector
to inspect a machine *instead* of asking ironic. In other words, using
``openstack baremetal introspection start <node>`` versus
``baremetal node inspect <node>`` commands. This does require the
:oslo.config:option:`inspector.require_managed_boot` setting be set to ``true``.
like unmanaged hardware introspection with agent inspect interface.
Driver support for Deployment with Secure Boot
----------------------------------------------

View File

@@ -1192,11 +1192,10 @@ To remedy this, try setting ``[pxe_filter]sync_period`` to be less frequent,
i.e. a larger value to enable conductors to have time between running syncs.
.. note::
It is anticipated that as part of the 2024.1 release, Ironic will have
this functionality also merged into Ironic directly as part of the
merge of the ``ironic-inspector`` service into ``ironic`` itself. This
merger will result in a slightly more performant implementation, which may
necessitate re-evaluation and tuning of the ``[pxe_filter]sync_period``
Ironic now has this functionality also merged into Ironic directly as part
of the merge of the ``ironic-inspector`` service into ``ironic`` itself.
This merger will result in a slightly more performant implementation, which
may necessitate re-evaluation and tuning of the ``[pxe_filter]sync_period``
parameter.
Some or all of my baremetal nodes disappeared! Help?!

View File

@@ -71,7 +71,6 @@ openstackdocs_projects = [
'cinder',
'glance',
'ironic',
'ironic-inspector',
'ironic-lib',
'ironic-neutron-agent',
'ironic-python-agent',
@@ -88,7 +87,6 @@ openstackdocs_projects = [
'osprofiler',
'os-traits',
'python-ironicclient',
'python-ironic-inspector-client',
'python-openstackclient',
'swift',
]

View File

@@ -40,11 +40,9 @@ which are developed by the same community.
.. seealso::
* :bifrost-doc:`Bifrost Documentation <>`
* :ironic-inspector-doc:`Ironic Inspector Documentation <>`
* :ironic-lib-doc:`Ironic Lib Documentation <>`
* :ironic-python-agent-doc:`Ironic Python Agent (IPA) Documentation <>`
* :python-ironicclient-doc:`Ironic Client Documentation <>`
* :python-ironic-inspector-client-doc:`Ironic Inspector Client Documentation <>`
Adding New Features
===================

View File

@@ -528,11 +528,6 @@ it is a misconfiguration of the deployment.
Configuring unmanaged in-band inspection
----------------------------------------
This section must be followed if you intend to use :ref:`unmanaged-inspection`
without ironic-inspector. For ironic-inspector support, check `its installation
guide
<https://docs.openstack.org/ironic-inspector/latest/install/index.html#configuration>`_.
With PXE
~~~~~~~~

View File

@@ -93,15 +93,13 @@ deploy
inspect
implements fetching hardware information from nodes. Can be implemented
out-of-band (via contacting the node's BMC) or in-band (via booting
a ramdisk on a node). The latter implementation is called ``inspector``
and uses a separate service called
:ironic-inspector-doc:`ironic-inspector <>`. Example:
a ramdisk on a node, mainly achieved by the agent interface).
.. code-block:: ini
[DEFAULT]
enabled_hardware_types = ipmi,ilo,irmc
enabled_inspect_interfaces = ilo,irmc,inspector
enabled_inspect_interfaces = ilo,irmc,agent
See :doc:`/admin/inspection` for more details.
management

View File

@@ -40,8 +40,8 @@ ironic-conductor
ironic-python-agent
A python service which is run in a temporary ramdisk to provide
ironic-conductor and ironic-inspector services with remote access, in-band
hardware control, and hardware introspection.
ironic-conductor with remote access, in-band hardware control, and hardware
introspection.
ironic-novncproxy
A python service which proxies graphical consoles from hosts using the
@@ -125,10 +125,6 @@ additional functionality:
Horizon dashboard, providing graphical interface (GUI) for the Bare Metal
API.
:ironic-inspector-doc:`ironic-inspector <>`
An associated service which performs in-band hardware introspection by
PXE booting unregistered hardware into the ironic-python-agent ramdisk.
diskimage-builder_
A related project to help facilitate the creation of ramdisks and machine
images, such as those running the ironic-python-agent.

View File

@@ -104,8 +104,6 @@ Configuring ironic-conductor service
* ``[glance]`` - to access the OpenStack Image service
* ``[swift]`` - to access the OpenStack Object Storage service
* ``[cinder]`` - to access the OpenStack Block Storage service
* ``[inspector]`` - to access the OpenStack Bare Metal Introspection
service
* ``[service_catalog]`` - a special section holding credentials
the Bare Metal service will use to discover its own API URL endpoint
as registered in the OpenStack Identity service catalog.

View File

@@ -23,10 +23,9 @@ which disk it should pick for the deployment. The list of supported hints is:
.. note::
A node's 'local_gb' property is often set to a value 1 GiB less than the
actual disk size to account for partitioning (this is how DevStack, TripleO
and Ironic Inspector work, to name a few). However, in this case ``size``
should be the actual size. For example, for a 128 GiB disk ``local_gb``
will be 127, but size hint will be 128.
actual disk size to account for partitioning. However, in this case
``size`` should be the actual size. For example, for a 128 GiB disk
``local_gb`` will be 127, but size hint will be 128.
* wwn (STRING): unique storage identifier and typically mapping to a device.
This can be a single device, or a SAN storage controller,

View File

@@ -28,13 +28,10 @@ components participating in the bare metal provisioning:
* The :glance-doc:`Image service <>` provides images for bare metal instances.
The following services can be optionally used by the Bare Metal service:
The following service can be optionally used by the Bare Metal service:
* The :cinder-doc:`Volume service <>` provides volumes to boot bare metal instances from.
* The :ironic-inspector-doc:`Bare Metal Introspection service <>` simplifies enrolling new bare metal
machines by conducting in-band introspection.
Node roles
----------
@@ -189,17 +186,6 @@ The following components of the Bare Metal service are installed on a
The :ironic-neutron-agent-doc:`ironic-neutron-agent <>` service should be started as well.
* If the Bare Metal introspection is used, its ``ironic-inspector`` process
has to be installed on all *controllers*. Each such process works as both
Bare Metal Introspection API and conductor service. A load balancer should
be used to spread the API load between *controllers*.
The API has to be served on the *control plane network*. Additionally,
it has to be exposed to the *bare metal network* for the ramdisk callback
API.
.. TODO(dtantsur): a nice picture to illustrate the above
Shared services
~~~~~~~~~~~~~~~

View File

@@ -17,7 +17,6 @@ import os
from oslo_config import cfg
from ironic.common.i18n import _
from ironic.conf import auth
VALID_ADD_PORTS_VALUES = {
@@ -62,21 +61,20 @@ opts = [
'finishes. Ignored for nodes that have fast '
'track mode enabled.')),
cfg.StrOpt('callback_endpoint_override',
deprecated_for_removal=True,
deprecated_reason=_('This option was used by inspector '
'inspect interface, which was removed.'),
help=_('endpoint to use as a callback for posting back '
'introspection data when boot is managed by ironic. '
'Standard keystoneauth options are used by default.')),
# TODO(dtantsur): change the default to True when ironic-inspector is no
# longer supported (and update the help string).
cfg.BoolOpt('require_managed_boot', default=None,
cfg.BoolOpt('require_managed_boot', default=True,
help=_('require that the in-band inspection boot is fully '
'managed by the node\'s boot interface. Set this to '
'False if your installation has a separate (i)PXE boot '
'environment for node discovery or unmanaged '
'inspection. You may need to set it to False to '
'inspect nodes that are not supported by boot '
'interfaces (e.g. because they don\'t have ports). '
'The default value depends on which inspect interface '
'is used: inspector uses False, agent - True.')),
'interfaces (e.g. because they don\'t have ports).')),
cfg.StrOpt('add_ports',
default='pxe',
help=_('Which MAC addresses to add as ports during '
@@ -203,9 +201,3 @@ def register_opts(conf):
conf.register_opts(discovery_opts, group='auto_discovery')
conf.register_opts(pxe_filter_opts, group='pxe_filter')
conf.register_opts(inspection_rule_opts, group='inspection_rules')
auth.register_auth_opts(conf, 'inspector',
service_type='baremetal-introspection')
def list_opts():
return auth.add_auth_opts(opts, service_type='baremetal-introspection')

View File

@@ -37,7 +37,7 @@ _opts = [
('healthcheck', ironic.conf.healthcheck.opts),
('ilo', ironic.conf.ilo.opts),
('inspection_rules', ironic.conf.inspector.inspection_rule_opts),
('inspector', ironic.conf.inspector.list_opts()),
('inspector', ironic.conf.inspector.opts),
('inventory', ironic.conf.inventory.opts),
('ipmi', ironic.conf.ipmi.opts),
('irmc', ironic.conf.irmc.opts),

View File

@@ -58,7 +58,7 @@ class GenericHardware(hardware_type.AbstractHardwareType):
"""List of supported inspect interfaces."""
# Inspector support should be the default if it's enabled by an
# operator (implying that the service is installed).
return [inspector.Inspector, inspector.AgentInspect, noop.NoInspect]
return [inspector.AgentInspect, noop.NoInspect]
@property
def supported_network_interfaces(self):

View File

@@ -11,6 +11,5 @@
# under the License.
from ironic.drivers.modules.inspector.agent import AgentInspect
from ironic.drivers.modules.inspector.interface import Inspector
__all__ = ['AgentInspect', 'Inspector']
__all__ = ['AgentInspect']

View File

@@ -35,8 +35,6 @@ CONF = cfg.CONF
class AgentInspect(common.Common):
"""In-band inspection."""
default_require_managed_boot = True
def __init__(self):
super().__init__()
enabled_hooks = [x.strip()

View File

@@ -1,57 +0,0 @@
# 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.
"""Client helper for ironic-inspector."""
from keystoneauth1 import exceptions as ks_exception
import openstack
from ironic.common import exception
from ironic.common.i18n import _
from ironic.common import keystone
from ironic.conf import CONF
_INSPECTOR_SESSION = None
def _get_inspector_session(**kwargs):
global _INSPECTOR_SESSION
if not _INSPECTOR_SESSION:
if CONF.auth_strategy != 'keystone':
# NOTE(dtantsur): using set_default instead of set_override because
# the native keystoneauth option must have priority.
CONF.set_default('auth_type', 'none', group='inspector')
service_auth = keystone.get_auth('inspector')
_INSPECTOR_SESSION = keystone.get_session('inspector',
auth=service_auth,
**kwargs)
return _INSPECTOR_SESSION
def get_client(context):
"""Helper to get inspector client instance."""
session = _get_inspector_session()
# NOTE(dtantsur): openstacksdk expects config option groups to match
# service name, but we use just "inspector".
conf = dict(CONF)
conf['ironic-inspector'] = conf.pop('inspector')
# TODO(pas-ha) investigate possibility of passing user context here,
# similar to what neutron/glance-related code does
try:
return openstack.connection.Connection(
session=session,
oslo_conf=conf).baremetal_introspection
except ks_exception.DiscoveryFailure as exc:
raise exception.ConfigInvalid(
_("Could not contact ironic-inspector for version discovery: %s")
% exc)

View File

@@ -10,13 +10,6 @@
# License for the specific language governing permissions and limitations
# under the License.
"""
Modules required to work with ironic_inspector:
https://pypi.org/project/ironic-inspector
"""
import threading
from urllib import parse as urlparse
from oslo_log import log as logging
@@ -24,14 +17,11 @@ from ironic.common import exception
from ironic.common.i18n import _
from ironic.common import states
from ironic.common import utils
from ironic.conductor import periodics
from ironic.conductor import task_manager
from ironic.conductor import utils as cond_utils
from ironic.conf import CONF
from ironic.drivers import base
from ironic.drivers.modules import deploy_utils
from ironic.drivers.modules import inspect_utils
from ironic.drivers.modules.inspector import client
LOG = logging.getLogger(__name__)
@@ -39,29 +29,6 @@ LOG = logging.getLogger(__name__)
_IRONIC_MANAGES_BOOT = 'inspector_manage_boot'
def _get_callback_endpoint(client):
root = CONF.inspector.callback_endpoint_override or client.get_endpoint()
if root == 'mdns':
return root
parts = urlparse.urlsplit(root)
if utils.is_loopback(parts.hostname):
raise exception.InvalidParameterValue(
_('Loopback address %s cannot be used as an introspection '
'callback URL') % parts.hostname)
# NOTE(dtantsur): the IPA side is quite picky about the exact format.
if parts.path.endswith('/v1'):
add = '/continue'
else:
add = '/v1/continue'
return urlparse.urlunsplit((parts.scheme, parts.netloc,
parts.path.rstrip('/') + add,
parts.query, parts.fragment))
def tear_down_managed_boot(task, always_power_off=False):
errors = []
@@ -167,21 +134,6 @@ def prepare_managed_inspection(task, endpoint):
class Common(base.InspectInterface):
default_require_managed_boot = False
def __init__(self):
super().__init__()
if CONF.inspector.require_managed_boot is None:
LOG.warning("The option [inspector]require_managed_boot will "
"change its default value to True in the future. "
"Set it to an explicit boolean value to avoid a "
"potential breakage.")
def _require_managed_boot(self):
return (CONF.inspector.require_managed_boot
if CONF.inspector.require_managed_boot is not None
else self.default_require_managed_boot)
def get_properties(self):
"""Return the properties of the interface.
@@ -198,7 +150,7 @@ class Common(base.InspectInterface):
:raises: UnsupportedDriverExtension
"""
utils.parse_kernel_params(CONF.inspector.extra_kernel_params)
if self._require_managed_boot():
if CONF.inspector.require_managed_boot:
ironic_manages_boot(task, raise_exc=True)
def inspect_hardware(self, task):
@@ -217,7 +169,7 @@ class Common(base.InspectInterface):
' on node %s.', task.node.uuid)
manage_boot = ironic_manages_boot(
task, raise_exc=self._require_managed_boot())
task, raise_exc=CONF.inspector.require_managed_boot)
utils.set_node_nested_field(task.node, 'driver_internal_info',
_IRONIC_MANAGES_BOOT, manage_boot)
@@ -252,139 +204,6 @@ class Common(base.InspectInterface):
cond_utils.node_power_action(task, next_state)
class Inspector(Common):
"""In-band inspection via ironic-inspector project."""
def _start_managed_inspection(self, task):
"""Start inspection with boot managed by ironic."""
cli = client.get_client(task.context)
endpoint = _get_callback_endpoint(cli)
prepare_managed_inspection(task, endpoint)
cli.start_introspection(task.node.uuid, manage_boot=False)
self._power_on_or_reboot(task)
def _start_unmanaged_inspection(self, task):
"""Call to inspector to start inspection."""
# NOTE(cid): spawning a short-lived daemon OS thread so that
# we can release a lock as soon as possible and allow
# ironic-inspector to operate on the node.
threading.Thread(target=_start_inspection,
args=(task.node.uuid, task.context),
daemon=True).start()
def abort(self, task):
"""Abort hardware inspection.
:param task: a task from TaskManager.
"""
node_uuid = task.node.uuid
LOG.debug('Aborting inspection for node %(uuid)s using '
'ironic-inspector', {'uuid': node_uuid})
client.get_client(task.context).abort_introspection(node_uuid)
if inspect_utils.clear_lookup_addresses(task.node):
task.node.save()
@periodics.node_periodic(
purpose='checking hardware inspection status',
spacing=CONF.inspector.status_check_period,
filters={'provision_state': states.INSPECTWAIT},
)
def _periodic_check_result(self, task, manager, context):
"""Periodic task checking results of inspection."""
if isinstance(task.driver.inspect, self.__class__):
_check_status(task)
def continue_inspection(self, task, inventory, plugin_data=None):
"""Continue in-band hardware inspection.
This implementation simply defers to ironic-inspector. It only exists
to simplify the transition to Ironic-native in-band inspection.
:param task: a task from TaskManager.
:param inventory: hardware inventory from the node.
:param plugin_data: optional plugin-specific data.
"""
cli = client.get_client(task.context)
endpoint = _get_callback_endpoint(cli)
data = dict(plugin_data, inventory=inventory) # older format
task.process_event('wait')
task.downgrade_lock()
cli.post(endpoint, json=data)
return states.INSPECTWAIT
def _start_inspection(node_uuid, context):
"""Call to inspector to start inspection."""
try:
client.get_client(context).start_introspection(node_uuid)
except Exception as exc:
LOG.error('Error contacting ironic-inspector for inspection of node '
'%(node)s: %(cls)s: %(err)s',
{'node': node_uuid, 'cls': type(exc).__name__, 'err': exc})
# NOTE(dtantsur): if acquire fails our last option is to rely on
# timeout
lock_purpose = 'recording hardware inspection error'
with task_manager.acquire(context, node_uuid,
purpose=lock_purpose) as task:
error = _('Failed to start inspection: %s') % exc
inspection_error_handler(task, error)
else:
LOG.info('Node %s was sent to inspection to ironic-inspector',
node_uuid)
def _check_status(task):
"""Check inspection status from inspector for node given by a task."""
node = task.node
if node.provision_state != states.INSPECTWAIT:
return
if not isinstance(task.driver.inspect, Inspector):
return
LOG.debug('Calling to inspector to check status of node %s',
task.node.uuid)
try:
inspector_client = client.get_client(task.context)
status = inspector_client.get_introspection(node.uuid)
except Exception:
# NOTE(dtantsur): get_status should not normally raise
# let's assume it's a transient failure and retry later
LOG.exception('Unexpected exception while getting '
'inspection status for node %s, will retry later',
node.uuid)
return
if not status.error and not status.is_finished:
return
# If the inspection has finished or failed, we need to update the node, so
# upgrade our lock to an exclusive one.
task.upgrade_lock()
node = task.node
inspect_utils.clear_lookup_addresses(node)
if status.error:
LOG.error('Inspection failed for node %(uuid)s with error: %(err)s',
{'uuid': node.uuid, 'err': status.error})
error = _('ironic-inspector inspection failed: %s') % status.error
inspection_error_handler(task, error)
elif status.is_finished:
clean_up(task)
if CONF.inventory.data_backend == 'none':
LOG.debug('Inspection data storage is disabled, the data will '
'not be saved for node %s', node.uuid)
return
introspection_data = inspector_client.get_introspection_data(
node.uuid, processed=True)
# TODO(dtantsur): having no inventory is an abnormal state, handle it.
inventory = introspection_data.pop('inventory', {})
inspect_utils.store_inspection_data(node, inventory,
introspection_data,
task.context)
def clean_up(task, finish=True, always_power_off=False):
errors = tear_down_managed_boot(task, always_power_off=always_power_off)
if errors:

View File

@@ -1,65 +0,0 @@
# 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.
from unittest import mock
from keystoneauth1 import exceptions as ks_exception
import openstack
from ironic.common import context
from ironic.common import exception
from ironic.conf import CONF
from ironic.drivers.modules.inspector import client
from ironic.tests.unit.db import base as db_base
@mock.patch('ironic.common.keystone.get_auth', autospec=True,
return_value=mock.sentinel.auth)
@mock.patch('ironic.common.keystone.get_session', autospec=True,
return_value=mock.sentinel.session)
@mock.patch.object(openstack.connection, 'Connection', autospec=True)
class GetClientTestCase(db_base.DbTestCase):
def setUp(self):
super(GetClientTestCase, self).setUp()
# NOTE(pas-ha) force-reset global inspector session object
client._INSPECTOR_SESSION = None
self.context = context.RequestContext(global_request_id='global')
def test_get_client(self, mock_conn, mock_session, mock_auth):
client.get_client(self.context)
mock_conn.assert_called_once_with(
session=mock.sentinel.session,
oslo_conf=mock.ANY)
self.assertEqual(1, mock_auth.call_count)
self.assertEqual(1, mock_session.call_count)
def test_get_client_standalone(self, mock_conn, mock_session, mock_auth):
self.config(auth_strategy='noauth')
client.get_client(self.context)
self.assertEqual('none', CONF.inspector.auth_type)
mock_conn.assert_called_once_with(
session=mock.sentinel.session,
oslo_conf=mock.ANY)
self.assertEqual(1, mock_auth.call_count)
self.assertEqual(1, mock_session.call_count)
def test_get_client_connection_problem(
self, mock_conn, mock_session, mock_auth):
mock_conn.side_effect = ks_exception.DiscoveryFailure("")
self.assertRaises(exception.ConfigInvalid,
client.get_client, self.context)
mock_conn.assert_called_once_with(
session=mock.sentinel.session,
oslo_conf=mock.ANY)
self.assertEqual(1, mock_auth.call_count)
self.assertEqual(1, mock_session.call_count)

View File

@@ -1,607 +0,0 @@
# 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.
from unittest 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.conf import CONF
from ironic.drivers.modules import inspect_utils
from ironic.drivers.modules.inspector import client
from ironic.drivers.modules.inspector import interface as inspector
from ironic.drivers.modules.redfish import utils as redfish_utils
from ironic.tests.unit.db import base as db_base
from ironic.tests.unit.objects import utils as obj_utils
class BaseTestCase(db_base.DbTestCase):
def setUp(self):
super(BaseTestCase, self).setUp()
self.node = obj_utils.create_test_node(self.context,
inspect_interface='inspector')
self.iface = inspector.Inspector()
self.task = mock.MagicMock(spec=task_manager.TaskManager)
self.task.context = self.context
self.task.shared = False
self.task.node = self.node
self.task.driver = mock.Mock(
spec=['boot', 'network', 'inspect', 'power', 'management'],
inspect=self.iface)
self.driver = self.task.driver
class CommonFunctionsTestCase(BaseTestCase):
def test_validate_ok(self):
self.iface.validate(self.task)
def test_get_properties(self):
res = self.iface.get_properties()
self.assertEqual({}, res)
def test_get_callback_endpoint(self):
for catalog_endp in ['http://192.168.0.42:5050',
'http://192.168.0.42:5050/v1',
'http://192.168.0.42:5050/']:
client = mock.Mock()
client.get_endpoint.return_value = catalog_endp
self.assertEqual('http://192.168.0.42:5050/v1/continue',
inspector._get_callback_endpoint(client))
def test_get_callback_endpoint_override(self):
CONF.set_override('callback_endpoint_override', 'http://url',
group='inspector')
client = mock.Mock()
self.assertEqual('http://url/v1/continue',
inspector._get_callback_endpoint(client))
self.assertFalse(client.get_endpoint.called)
def test_get_callback_endpoint_mdns(self):
CONF.set_override('callback_endpoint_override', 'mdns',
group='inspector')
client = mock.Mock()
self.assertEqual('mdns', inspector._get_callback_endpoint(client))
self.assertFalse(client.get_endpoint.called)
def test_get_callback_endpoint_no_loopback(self):
client = mock.Mock()
client.get_endpoint.return_value = 'http://127.0.0.1:5050'
self.assertRaisesRegex(exception.InvalidParameterValue, 'Loopback',
inspector._get_callback_endpoint, client)
@mock.patch.object(client, 'get_client', autospec=True)
class InspectHardwareTestCase(BaseTestCase):
def test_validate_ok(self, mock_client):
self.iface.validate(self.task)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
@mock.patch.object(inspect_utils, 'create_ports_if_not_exist',
autospec=True)
def test_validate_require_managed_boot(self, mock_get_system,
mock_create_ports_if_not_exist,
mock_client):
CONF.set_override('require_managed_boot', True, group='inspector')
self.driver.boot.validate_inspection.side_effect = (
exception.UnsupportedDriverExtension(''))
self.assertRaises(exception.UnsupportedDriverExtension,
self.iface.validate, self.task)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
@mock.patch.object(inspect_utils, 'create_ports_if_not_exist',
autospec=True)
def test_unmanaged_ok(self, mock_create_ports_if_not_exist,
mock_get_system, mock_client):
self.driver.boot.validate_inspection.side_effect = (
exception.UnsupportedDriverExtension(''))
mock_introspect = mock_client.return_value.start_introspection
self.assertEqual(states.INSPECTWAIT,
self.iface.inspect_hardware(self.task))
mock_introspect.assert_called_once_with(self.node.uuid)
self.assertFalse(self.driver.boot.prepare_ramdisk.called)
self.assertFalse(self.driver.network.add_inspection_network.called)
self.assertFalse(self.driver.power.reboot.called)
self.assertFalse(self.driver.network.remove_inspection_network.called)
self.assertFalse(self.driver.boot.clean_up_ramdisk.called)
self.assertFalse(self.driver.power.set_power_state.called)
@mock.patch.object(task_manager, 'acquire', autospec=True)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
@mock.patch.object(inspect_utils, 'create_ports_if_not_exist',
autospec=True)
def test_unmanaged_error(self, mock_create_ports_if_not_exist,
mock_get_system, mock_acquire, mock_client):
mock_acquire.return_value.__enter__.return_value = self.task
self.driver.boot.validate_inspection.side_effect = (
exception.UnsupportedDriverExtension(''))
mock_introspect = mock_client.return_value.start_introspection
mock_introspect.side_effect = RuntimeError('boom')
self.iface.inspect_hardware(self.task)
mock_introspect.assert_called_once_with(self.node.uuid)
self.assertIn('boom', self.task.node.last_error)
self.task.process_event.assert_called_once_with('fail')
self.assertFalse(self.driver.boot.prepare_ramdisk.called)
self.assertFalse(self.driver.network.add_inspection_network.called)
self.assertFalse(self.driver.network.remove_inspection_network.called)
self.assertFalse(self.driver.boot.clean_up_ramdisk.called)
self.assertFalse(self.driver.power.set_power_state.called)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
@mock.patch.object(inspect_utils, 'create_ports_if_not_exist',
autospec=True)
def test_require_managed_boot(self, mock_create_ports_if_not_exist,
mock_get_system, mock_client):
CONF.set_override('require_managed_boot', True, group='inspector')
self.driver.boot.validate_inspection.side_effect = (
exception.UnsupportedDriverExtension(''))
mock_introspect = mock_client.return_value.start_introspection
self.assertRaises(exception.UnsupportedDriverExtension,
self.iface.inspect_hardware, self.task)
self.assertFalse(mock_introspect.called)
self.assertFalse(self.driver.boot.prepare_ramdisk.called)
self.assertFalse(self.driver.network.add_inspection_network.called)
self.assertFalse(self.driver.power.reboot.called)
self.assertFalse(self.driver.network.remove_inspection_network.called)
self.assertFalse(self.driver.boot.clean_up_ramdisk.called)
self.assertFalse(self.driver.power.set_power_state.called)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
@mock.patch.object(inspect_utils, 'create_ports_if_not_exist',
autospec=True)
def test_managed_ok(self, mock_create_ports_if_not_exist,
mock_get_system, mock_client):
endpoint = 'http://192.169.0.42:5050/v1'
mock_client.return_value.get_endpoint.return_value = endpoint
mock_introspect = mock_client.return_value.start_introspection
self.assertEqual(states.INSPECTWAIT,
self.iface.inspect_hardware(self.task))
mock_introspect.assert_called_once_with(self.node.uuid,
manage_boot=False)
self.driver.boot.prepare_ramdisk.assert_called_once_with(
self.task, ramdisk_params={
'ipa-inspection-callback-url': endpoint + '/continue',
})
self.driver.network.add_inspection_network.assert_called_once_with(
self.task)
self.driver.power.set_power_state.assert_has_calls([
mock.call(self.task, states.POWER_OFF, timeout=None),
mock.call(self.task, states.POWER_ON, timeout=None),
])
self.assertFalse(self.driver.network.remove_inspection_network.called)
self.assertFalse(self.driver.boot.clean_up_ramdisk.called)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
@mock.patch.object(inspect_utils, 'create_ports_if_not_exist',
autospec=True)
def test_managed_force_dhcp(self,
mock_create_ports_if_not_exist,
mock_get_system, mock_client):
# Enable the new toggle
CONF.set_override('force_dhcp', True, group='inspector')
endpoint = 'http://192.169.0.42:5050/v1'
mock_client.return_value.get_endpoint.return_value = endpoint
mock_introspect = mock_client.return_value.start_introspection
self.assertEqual(states.INSPECTWAIT,
self.iface.inspect_hardware(self.task))
mock_introspect.assert_called_once_with(self.node.uuid,
manage_boot=False)
# Ensure both DHCP on all interfaces and LLDP collection are present
self.driver.boot.prepare_ramdisk.assert_called_once_with(
self.task, ramdisk_params={
'ipa-inspection-callback-url': endpoint + '/continue',
'ipa-collect-lldp': '1',
})
self.driver.network.add_inspection_network.assert_called_once_with(
self.task)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
@mock.patch.object(inspect_utils, 'create_ports_if_not_exist',
autospec=True)
def test_managed_custom_params(self, mock_get_system,
mock_create_ports_if_not_exist,
mock_client):
CONF.set_override('extra_kernel_params',
'ipa-inspection-collectors=default,logs '
'ipa-collect-dhcp=1 something',
group='inspector')
endpoint = 'http://192.169.0.42:5050/v1'
mock_client.return_value.get_endpoint.return_value = endpoint
mock_introspect = mock_client.return_value.start_introspection
self.iface.validate(self.task)
self.assertEqual(states.INSPECTWAIT,
self.iface.inspect_hardware(self.task))
mock_introspect.assert_called_once_with(self.node.uuid,
manage_boot=False)
self.driver.boot.prepare_ramdisk.assert_called_once_with(
self.task, ramdisk_params={
'ipa-inspection-callback-url': endpoint + '/continue',
'ipa-inspection-collectors': 'default,logs',
'ipa-collect-dhcp': '1',
'something': None,
})
self.driver.network.add_inspection_network.assert_called_once_with(
self.task)
self.driver.power.set_power_state.assert_has_calls([
mock.call(self.task, states.POWER_OFF, timeout=None),
mock.call(self.task, states.POWER_ON, timeout=None),
])
self.assertFalse(self.driver.network.remove_inspection_network.called)
self.assertFalse(self.driver.boot.clean_up_ramdisk.called)
@mock.patch('ironic.drivers.modules.deploy_utils.get_ironic_api_url',
autospec=True)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
@mock.patch.object(inspect_utils, 'create_ports_if_not_exist',
autospec=True)
def test_managed_fast_track(self, mock_create_ports_if_not_exist,
mock_get_system, mock_ironic_url,
mock_client):
CONF.set_override('fast_track', True, group='deploy')
CONF.set_override('extra_kernel_params',
'ipa-inspection-collectors=default,logs '
'ipa-collect-dhcp=1',
group='inspector')
endpoint = 'http://192.169.0.42:5050/v1'
mock_ironic_url.return_value = 'http://192.169.0.42:6385'
mock_client.return_value.get_endpoint.return_value = endpoint
mock_introspect = mock_client.return_value.start_introspection
self.iface.validate(self.task)
self.assertEqual(states.INSPECTWAIT,
self.iface.inspect_hardware(self.task))
mock_introspect.assert_called_once_with(self.node.uuid,
manage_boot=False)
self.driver.boot.prepare_ramdisk.assert_called_once_with(
self.task, ramdisk_params={
'ipa-inspection-callback-url': endpoint + '/continue',
'ipa-inspection-collectors': 'default,logs',
'ipa-collect-dhcp': '1',
'ipa-api-url': 'http://192.169.0.42:6385',
})
self.driver.network.add_inspection_network.assert_called_once_with(
self.task)
self.driver.power.set_power_state.assert_has_calls([
mock.call(self.task, states.POWER_OFF, timeout=None),
mock.call(self.task, states.POWER_ON, timeout=None),
])
self.assertFalse(self.driver.network.remove_inspection_network.called)
self.assertFalse(self.driver.boot.clean_up_ramdisk.called)
@mock.patch('ironic.drivers.modules.deploy_utils.get_ironic_api_url',
autospec=True)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
@mock.patch.object(inspect_utils, 'create_ports_if_not_exist',
autospec=True)
def test_managed_fast_track_via_driver_info(
self, mock_create_ports_if_not_exist, mock_get_system,
mock_ironic_url, mock_client):
CONF.set_override('extra_kernel_params',
'ipa-inspection-collectors=default,logs '
'ipa-collect-dhcp=1',
group='inspector')
endpoint = 'http://192.169.0.42:5050/v1'
mock_ironic_url.return_value = 'http://192.169.0.42:6385'
mock_client.return_value.get_endpoint.return_value = endpoint
mock_introspect = mock_client.return_value.start_introspection
self.task.node.driver_info = {'fast_track': True}
self.iface.validate(self.task)
self.assertEqual(states.INSPECTWAIT,
self.iface.inspect_hardware(self.task))
mock_introspect.assert_called_once_with(self.node.uuid,
manage_boot=False)
self.driver.boot.prepare_ramdisk.assert_called_once_with(
self.task, ramdisk_params={
'ipa-inspection-callback-url': endpoint + '/continue',
'ipa-inspection-collectors': 'default,logs',
'ipa-collect-dhcp': '1',
'ipa-api-url': 'http://192.169.0.42:6385',
})
self.driver.network.add_inspection_network.assert_called_once_with(
self.task)
self.driver.power.set_power_state.assert_has_calls([
mock.call(self.task, states.POWER_OFF, timeout=None),
mock.call(self.task, states.POWER_ON, timeout=None),
])
self.assertFalse(self.driver.network.remove_inspection_network.called)
self.assertFalse(self.driver.boot.clean_up_ramdisk.called)
@mock.patch.object(task_manager, 'acquire', autospec=True)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
@mock.patch.object(inspect_utils, 'create_ports_if_not_exist',
autospec=True)
def test_managed_error(self, mock_get_system,
mock_create_ports_if_not_exist, mock_acquire,
mock_client):
endpoint = 'http://192.169.0.42:5050/v1'
mock_client.return_value.get_endpoint.return_value = endpoint
mock_acquire.return_value.__enter__.return_value = self.task
mock_introspect = mock_client.return_value.start_introspection
mock_introspect.side_effect = RuntimeError('boom')
self.assertRaises(exception.HardwareInspectionFailure,
self.iface.inspect_hardware, self.task)
mock_introspect.assert_called_once_with(self.node.uuid,
manage_boot=False)
self.assertIn('boom', self.task.node.last_error)
self.driver.boot.prepare_ramdisk.assert_called_once_with(
self.task, ramdisk_params={
'ipa-inspection-callback-url': endpoint + '/continue',
})
self.driver.network.add_inspection_network.assert_called_once_with(
self.task)
self.driver.network.remove_inspection_network.assert_called_once_with(
self.task)
self.driver.boot.clean_up_ramdisk.assert_called_once_with(self.task)
self.driver.power.set_power_state.assert_called_with(
self.task, 'power off', timeout=None)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
@mock.patch.object(inspect_utils, 'create_ports_if_not_exist',
autospec=True)
def test_managed_disable_power_off(self, mock_create_ports_if_not_exist,
mock_get_system, mock_client):
endpoint = 'http://192.169.0.42:5050/v1'
mock_client.return_value.get_endpoint.return_value = endpoint
mock_introspect = mock_client.return_value.start_introspection
self.node.disable_power_off = True
self.assertEqual(states.INSPECTWAIT,
self.iface.inspect_hardware(self.task))
mock_introspect.assert_called_once_with(self.node.uuid,
manage_boot=False)
self.driver.boot.prepare_ramdisk.assert_called_once_with(
self.task, ramdisk_params={
'ipa-inspection-callback-url': endpoint + '/continue',
})
self.driver.network.add_inspection_network.assert_called_once_with(
self.task)
self.driver.power.reboot.assert_called_once_with(
self.task, timeout=None)
self.driver.power.set_power_state.assert_not_called()
self.assertFalse(self.driver.network.remove_inspection_network.called)
self.assertFalse(self.driver.boot.clean_up_ramdisk.called)
class TearDownManagedInspectionTestCase(BaseTestCase):
def test_unmanaged(self):
inspector.tear_down_managed_boot(self.task)
self.assertFalse(self.driver.network.remove_inspection_network.called)
self.assertFalse(self.driver.boot.clean_up_ramdisk.called)
self.assertFalse(self.driver.power.set_power_state.called)
def test_unmanaged_force_power_off(self):
inspector.tear_down_managed_boot(self.task, always_power_off=True)
self.assertFalse(self.driver.network.remove_inspection_network.called)
self.assertFalse(self.driver.boot.clean_up_ramdisk.called)
self.driver.power.set_power_state.assert_called_once_with(
self.task, 'power off', timeout=None)
def test_managed(self):
utils.set_node_nested_field(self.node, 'driver_internal_info',
'inspector_manage_boot', True)
self.node.save()
inspector.tear_down_managed_boot(self.task)
self.driver.network.remove_inspection_network.assert_called_once_with(
self.task)
self.driver.boot.clean_up_ramdisk.assert_called_once_with(self.task)
self.driver.power.set_power_state.assert_called_once_with(
self.task, 'power off', timeout=None)
def test_managed_no_power_off(self):
CONF.set_override('power_off', False, group='inspector')
utils.set_node_nested_field(self.node, 'driver_internal_info',
'inspector_manage_boot', True)
self.node.save()
inspector.tear_down_managed_boot(self.task)
self.driver.network.remove_inspection_network.assert_called_once_with(
self.task)
self.driver.boot.clean_up_ramdisk.assert_called_once_with(self.task)
self.assertFalse(self.driver.power.set_power_state.called)
def test_managed_no_power_off_on_fast_track(self):
CONF.set_override('fast_track', True, group='deploy')
utils.set_node_nested_field(self.node, 'driver_internal_info',
'inspector_manage_boot', True)
self.node.save()
inspector.tear_down_managed_boot(self.task)
self.driver.network.remove_inspection_network.assert_called_once_with(
self.task)
self.driver.boot.clean_up_ramdisk.assert_called_once_with(self.task)
self.assertFalse(self.driver.power.set_power_state.called)
def test_managed_disable_power_off(self):
utils.set_node_nested_field(self.node, 'driver_internal_info',
'inspector_manage_boot', True)
self.node.disable_power_off = True
self.node.save()
inspector.tear_down_managed_boot(self.task)
self.driver.network.remove_inspection_network.assert_called_once_with(
self.task)
self.driver.boot.clean_up_ramdisk.assert_called_once_with(self.task)
self.driver.power.reboot.assert_called_once_with(
self.task, timeout=None)
def _test_clean_up_failed(self):
utils.set_node_nested_field(self.node, 'driver_internal_info',
'inspector_manage_boot', True)
self.node.save()
result = inspector.tear_down_managed_boot(self.task)
self.assertIn("boom", result[0])
def test_boot_clean_up_failed(self):
self.driver.boot.clean_up_ramdisk.side_effect = RuntimeError('boom')
self._test_clean_up_failed()
self.driver.boot.clean_up_ramdisk.assert_called_once_with(self.task)
def test_network_clean_up_failed(self):
self.driver.network.remove_inspection_network.side_effect = \
RuntimeError('boom')
self._test_clean_up_failed()
self.driver.network.remove_inspection_network.assert_called_once_with(
self.task)
self.driver.boot.clean_up_ramdisk.assert_called_once_with(self.task)
@mock.patch.object(client, 'get_client', autospec=True)
class CheckStatusTestCase(BaseTestCase):
def setUp(self):
super(CheckStatusTestCase, self).setUp()
self.node.provision_state = states.INSPECTWAIT
def test_not_inspecting(self, mock_client):
mock_get = mock_client.return_value.get_introspection
self.node.provision_state = states.MANAGEABLE
inspector._check_status(self.task)
self.assertFalse(mock_get.called)
def test_not_check_inspecting(self, mock_client):
mock_get = mock_client.return_value.get_introspection
self.node.provision_state = states.INSPECTING
inspector._check_status(self.task)
self.assertFalse(mock_get.called)
def test_not_inspector(self, mock_client):
mock_get = mock_client.return_value.get_introspection
self.task.driver.inspect = object()
inspector._check_status(self.task)
self.assertFalse(mock_get.called)
def test_not_finished(self, mock_client):
mock_get = mock_client.return_value.get_introspection
mock_get.return_value = mock.Mock(is_finished=False,
error=None,
spec=['is_finished', 'error'])
inspector._check_status(self.task)
mock_get.assert_called_once_with(self.node.uuid)
self.assertFalse(self.task.process_event.called)
def test_exception_ignored(self, mock_client):
mock_get = mock_client.return_value.get_introspection
mock_get.side_effect = RuntimeError('boom')
inspector._check_status(self.task)
mock_get.assert_called_once_with(self.node.uuid)
self.assertFalse(self.task.process_event.called)
@mock.patch.object(inspector, 'tear_down_managed_boot', autospec=True)
def test_status_ok(self, mock_tear_down, mock_client):
mock_get = mock_client.return_value.get_introspection
mock_get.return_value = mock.Mock(is_finished=True,
error=None,
spec=['is_finished', 'error'])
mock_tear_down.return_value = []
inspector._check_status(self.task)
mock_get.assert_called_once_with(self.node.uuid)
self.task.process_event.assert_called_once_with('done')
mock_tear_down.assert_called_once_with(
self.task, always_power_off=False)
@mock.patch.object(inspector, 'tear_down_managed_boot', autospec=True)
def test_status_error(self, mock_tear_down, mock_client):
mock_get = mock_client.return_value.get_introspection
mock_get.return_value = mock.Mock(is_finished=True,
error='boom',
spec=['is_finished', 'error'])
mock_tear_down.return_value = []
inspector._check_status(self.task)
mock_get.assert_called_once_with(self.node.uuid)
self.task.process_event.assert_called_once_with('fail')
self.assertIn('boom', self.node.last_error)
mock_tear_down.assert_called_once_with(self.task)
@mock.patch.object(inspector, 'tear_down_managed_boot', autospec=True)
def test_status_clean_up_failed(self, mock_tear_down, mock_client):
utils.set_node_nested_field(self.node, 'driver_internal_info',
'inspector_manage_boot', True)
self.node.save()
mock_get = mock_client.return_value.get_introspection
mock_get.return_value = mock.Mock(is_finished=True,
error=None,
spec=['is_finished', 'error'])
mock_tear_down.return_value = ["boom"]
inspector._check_status(self.task)
mock_get.assert_called_once_with(self.node.uuid)
self.task.process_event.assert_called_once_with('fail')
self.assertIn('boom', self.node.last_error)
mock_tear_down.assert_called_once_with(
self.task, always_power_off=False)
@mock.patch.object(inspect_utils, 'store_inspection_data', autospec=True)
def test_status_ok_store_inventory(self, mock_store_data, mock_client):
mock_get = mock_client.return_value.get_introspection
mock_get.return_value = mock.Mock(is_finished=True,
error=None,
spec=['is_finished', 'error'])
fake_inventory = {"cpu": "amd"}
fake_plugin_data = {"disks": [{"name": "/dev/vda"}]}
fake_introspection_data = dict(fake_plugin_data,
inventory=fake_inventory)
mock_get_data = mock_client.return_value.get_introspection_data
mock_get_data.return_value = fake_introspection_data
inspector._check_status(self.task)
mock_get.assert_called_once_with(self.node.uuid)
mock_get_data.assert_called_once_with(self.node.uuid, processed=True)
mock_store_data.assert_called_once_with(self.node,
fake_inventory,
fake_plugin_data,
self.task.context)
def test_status_ok_store_inventory_nostore(self, mock_client):
CONF.set_override('data_backend', 'none', group='inventory')
mock_get = mock_client.return_value.get_introspection
mock_get.return_value = mock.Mock(is_finished=True,
error=None,
spec=['is_finished', 'error'])
mock_get_data = mock_client.return_value.get_introspection_data
inspector._check_status(self.task)
mock_get.assert_called_once_with(self.node.uuid)
mock_get_data.assert_not_called()
def test_status_error_dont_store_inventory(self, mock_client):
CONF.set_override('data_backend', 'database',
group='inventory')
mock_get = mock_client.return_value.get_introspection
mock_get.return_value = mock.Mock(is_finished=True,
error='boom',
spec=['is_finished', 'error'])
mock_get_data = mock_client.return_value.get_introspection_data
inspector._check_status(self.task)
mock_get.assert_called_once_with(self.node.uuid)
mock_get_data.assert_not_called()
@mock.patch.object(client, 'get_client', autospec=True)
class InspectHardwareAbortTestCase(BaseTestCase):
def test_abort_ok(self, mock_client):
mock_abort = mock_client.return_value.abort_introspection
self.iface.abort(self.task)
mock_abort.assert_called_once_with(self.node.uuid)
def test_abort_error(self, mock_client):
mock_abort = mock_client.return_value.abort_introspection
mock_abort.side_effect = RuntimeError('boom')
self.assertRaises(RuntimeError, self.iface.abort, self.task)
mock_abort.assert_called_once_with(self.node.uuid)

View File

@@ -38,7 +38,7 @@ class IDRACHardwareTestCase(db_base.DbTestCase):
enabled_management_interfaces=['idrac-redfish'],
enabled_power_interfaces=['idrac-redfish', 'redfish'],
enabled_inspect_interfaces=[
'idrac-redfish', 'inspector',
'idrac-redfish', 'agent',
'no-inspect'],
enabled_network_interfaces=['flat', 'neutron', 'noop'],
enabled_raid_interfaces=[
@@ -99,10 +99,10 @@ class IDRACHardwareTestCase(db_base.DbTestCase):
def test_override_with_inspector(self):
node = obj_utils.create_test_node(self.context, driver='idrac',
inspect_interface='inspector')
inspect_interface='agent')
with task_manager.acquire(self.context, node.id) as task:
self._validate_interfaces(task.driver,
inspect=inspector.Inspector)
inspect=inspector.AgentInspect)
def test_override_with_raid(self):
for iface, impl in [('agent', agent.AgentRAID),

View File

@@ -49,7 +49,7 @@ class ManualManagementHardwareTestCase(db_base.DbTestCase):
self.assertIsInstance(task.driver.raid, noop.NoRAID)
def test_supported_interfaces(self):
self.config(enabled_inspect_interfaces=['inspector', 'no-inspect'],
self.config(enabled_inspect_interfaces=['agent', 'no-inspect'],
enabled_deploy_interfaces=['direct', 'custom-agent'],
enabled_raid_interfaces=['agent'])
node = obj_utils.create_test_node(self.context,
@@ -62,7 +62,8 @@ class ManualManagementHardwareTestCase(db_base.DbTestCase):
self.assertIsInstance(task.driver.power, fake.FakePower)
self.assertIsInstance(task.driver.boot, pxe.PXEBoot)
self.assertIsInstance(task.driver.deploy, agent.CustomAgentDeploy)
self.assertIsInstance(task.driver.inspect, inspector.Inspector)
self.assertIsInstance(task.driver.inspect,
inspector.AgentInspect)
self.assertIsInstance(task.driver.raid, agent.AgentRAID)
def test_get_properties(self):

View File

@@ -70,10 +70,10 @@ class IloHardwareTestCase(db_base.DbTestCase):
noop.NoRescue)
def test_override_with_inspector(self):
self.config(enabled_inspect_interfaces=['inspector', 'ilo'])
self.config(enabled_inspect_interfaces=['agent', 'ilo'])
node = obj_utils.create_test_node(
self.context, driver='ilo',
inspect_interface='inspector',
inspect_interface='agent',
raid_interface='agent',
vendor_interface='no-vendor')
with task_manager.acquire(self.context, node.id) as task:
@@ -84,7 +84,7 @@ class IloHardwareTestCase(db_base.DbTestCase):
self.assertIsInstance(task.driver.deploy,
agent.AgentDeploy)
self.assertIsInstance(task.driver.inspect,
inspector.Inspector)
inspector.AgentInspect)
self.assertIsInstance(task.driver.management,
ilo.management.IloManagement)
self.assertIsInstance(task.driver.power,
@@ -122,7 +122,7 @@ class IloHardwareTestCase(db_base.DbTestCase):
ilo.vendor.VendorPassthru)
def test_override_with_agent_rescue(self):
self.config(enabled_inspect_interfaces=['inspector', 'ilo'])
self.config(enabled_inspect_interfaces=['agent', 'ilo'])
node = obj_utils.create_test_node(
self.context, driver='ilo',
rescue_interface='agent',

View File

@@ -73,10 +73,10 @@ class IRMCHardwareTestCase(db_base.DbTestCase):
irmc_bios.IRMCBIOS)
def test_override_with_inspector(self, check_share_fs_mounted_mock):
self.config(enabled_inspect_interfaces=['inspector', 'irmc'])
self.config(enabled_inspect_interfaces=['agent', 'irmc'])
node = obj_utils.create_test_node(
self.context, driver='irmc',
inspect_interface='inspector',
inspect_interface='agent',
raid_interface='agent')
with task_manager.acquire(self.context, node.id) as task:
self.assertIsInstance(task.driver.boot,
@@ -86,7 +86,7 @@ class IRMCHardwareTestCase(db_base.DbTestCase):
self.assertIsInstance(task.driver.deploy,
agent.AgentDeploy)
self.assertIsInstance(task.driver.inspect,
inspector.Inspector)
inspector.AgentInspect)
self.assertIsInstance(task.driver.management,
irmc.management.IRMCManagement)
self.assertIsInstance(task.driver.power,

View File

@@ -98,7 +98,6 @@ agent = "ironic.drivers.modules.inspector:AgentInspect"
fake = "ironic.drivers.modules.fake:FakeInspect"
idrac-redfish = "ironic.drivers.modules.drac.inspect:DracRedfishInspect"
ilo = "ironic.drivers.modules.ilo.inspect:IloInspect"
inspector = "ironic.drivers.modules.inspector:Inspector"
irmc = "ironic.drivers.modules.irmc.inspect:IRMCInspect"
no-inspect = "ironic.drivers.modules.noop:NoInspect"
redfish = "ironic.drivers.modules.redfish.inspect:RedfishInspect"

View File

@@ -0,0 +1,10 @@
---
upgrade:
- |
The deprecated ``inspector`` inspect interface has been removed. Use
the ``agent`` interface instead.
See `migration guide
<https://docs.openstack.org/ironic/latest/admin/inspection/migration.html>`_
to learn how existing deployments with ironic-inspector can be migrated to
a new architecture with the built-in inspection feature.