summaryrefslogtreecommitdiff
path: root/ironic/drivers/modules/inspector.py
diff options
context:
space:
mode:
authorDmitry Tantsur <dtantsur@redhat.com>2015-05-29 16:28:33 +0200
committerDmitry Tantsur <dtantsur@redhat.com>2015-06-01 12:32:14 +0200
commit06c8fc9ad1619d34feb1a45f0b81754721af4a72 (patch)
treefbdd80dfe41f7ebcc08ecfd64bc488a3db937f82 /ironic/drivers/modules/inspector.py
parentd1b586cbf673af3c05257bd16339aadc0f61d7f4 (diff)
downloadironic-06c8fc9ad1619d34feb1a45f0b81754721af4a72.tar.gz
ironic-discoverd is being renamed to ironic-inspector
This patch changes module names, allows importing new module name for inspector (will be later changed to ironic_inspector_client). We need this patch to unbreak devstack support. Change-Id: I34cc4336dcd7e8d44aebecf5c85b52c83f0c717b
Diffstat (limited to 'ironic/drivers/modules/inspector.py')
-rw-r--r--ironic/drivers/modules/inspector.py213
1 files changed, 213 insertions, 0 deletions
diff --git a/ironic/drivers/modules/inspector.py b/ironic/drivers/modules/inspector.py
new file mode 100644
index 000000000..d3a552324
--- /dev/null
+++ b/ironic/drivers/modules/inspector.py
@@ -0,0 +1,213 @@
+# 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.
+
+"""
+Modules required to work with ironic_inspector:
+ https://pypi.python.org/pypi/ironic-discoverd
+"""
+
+import eventlet
+from oslo_config import cfg
+from oslo_log import log as logging
+from oslo_utils import importutils
+
+from ironic.common import exception
+from ironic.common.i18n import _
+from ironic.common.i18n import _LE
+from ironic.common.i18n import _LI
+from ironic.common import keystone
+from ironic.common import states
+from ironic.conductor import task_manager
+from ironic.drivers import base
+
+
+LOG = logging.getLogger(__name__)
+
+
+inspector_opts = [
+ cfg.BoolOpt('enabled', default=False,
+ help='whether to enable inspection using ironic-inspector',
+ deprecated_group='discoverd'),
+ cfg.StrOpt('service_url',
+ help='ironic-inspector HTTP endpoint. If this is not set, the '
+ 'ironic-inspector client default (http://127.0.0.1:5050) '
+ 'will be used.',
+ deprecated_group='discoverd'),
+ cfg.IntOpt('status_check_period', default=60,
+ help='period (in seconds) to check status of nodes '
+ 'on inspection',
+ deprecated_group='discoverd'),
+]
+
+CONF = cfg.CONF
+CONF.register_opts(inspector_opts, group='inspector')
+
+
+# TODO(dtantsur): change this to ironic_inspector_client once it's available
+ironic_inspector = importutils.try_import('ironic_inspector')
+if not ironic_inspector:
+ # NOTE(dtantsur): old name for ironic-inspector
+ ironic_inspector = importutils.try_import('ironic_discoverd')
+if ironic_inspector:
+ from ironic_inspector import client
+
+
+class Inspector(base.InspectInterface):
+ """In-band inspection via ironic-inspector project."""
+
+ @classmethod
+ def create_if_enabled(cls, driver_name):
+ """Create instance of Inspector if it's enabled.
+
+ Reports log warning with given driver_name if it's not.
+
+ :return: Inspector instance or None
+ """
+ if CONF.inspector.enabled:
+ return cls()
+ else:
+ LOG.info(_LI("Inspection via ironic-inspector is disabled in "
+ "configuration for driver %s. To enable, change "
+ "[inspector] enabled = True."), driver_name)
+
+ def __init__(self):
+ if not CONF.inspector.enabled:
+ raise exception.DriverLoadError(
+ _('ironic-inspector support is disabled'))
+
+ if not ironic_inspector:
+ raise exception.DriverLoadError(
+ _('ironic-inspector Python module not found'))
+
+ # NOTE(dtantsur): __version_info__ attribute appeared in 1.0.0
+ version = getattr(ironic_inspector, '__version_info__', (0, 2))
+ if version < (1, 0):
+ raise exception.DriverLoadError(
+ _('ironic-inspector version is too old: required >= 1.0.0, '
+ 'got %s') % '.'.join(str(x) for x in version))
+
+ def get_properties(self):
+ """Return the properties of the interface.
+
+ :returns: dictionary of <property name>:<property description> entries.
+ """
+ return {} # no properties
+
+ def validate(self, task):
+ """Validate the driver-specific inspection information.
+
+ If invalid, raises an exception; otherwise returns None.
+
+ :param task: a task from TaskManager.
+ """
+ # NOTE(deva): this is not callable if inspector is disabled
+ # so don't raise an exception -- just pass.
+ pass
+
+ def inspect_hardware(self, task):
+ """Inspect hardware to obtain the hardware properties.
+
+ This particular implementation only starts inspection using
+ ironic-inspector. Results will be checked in a periodic task.
+
+ :param task: a task from TaskManager.
+ :returns: states.INSPECTING
+ """
+ LOG.debug('Starting inspection for node %(uuid)s using '
+ 'ironic-inspector client %(version)s',
+ {'uuid': task.node.uuid, 'version':
+ ironic_inspector.__version__})
+
+ # NOTE(dtantsur): we're spawning a short-living green thread so that
+ # we can release a lock as soon as possible and allow ironic-inspector
+ # to operate on a node.
+ eventlet.spawn_n(_start_inspection, task.node.uuid, task.context)
+ return states.INSPECTING
+
+ @base.driver_periodic_task(spacing=CONF.inspector.status_check_period,
+ enabled=CONF.inspector.enabled)
+ def _periodic_check_result(self, manager, context):
+ """Periodic task checking results of inspection."""
+ filters = {'provision_state': states.INSPECTING}
+ node_iter = manager.iter_nodes(filters=filters)
+
+ for node_uuid, driver in node_iter:
+ try:
+ # TODO(dtantsur): we need an exclusive lock only once
+ # inspection is finished.
+ with task_manager.acquire(context, node_uuid) as task:
+ _check_status(task)
+ except (exception.NodeLocked, exception.NodeNotFound):
+ continue
+
+
+def _call_inspector(func, uuid, context):
+ """Wrapper around calls to inspector."""
+ # NOTE(dtantsur): due to bug #1428652 None is not accepted for base_url.
+ kwargs = {}
+ if CONF.inspector.service_url:
+ kwargs['base_url'] = CONF.inspector.service_url
+ return func(uuid, auth_token=context.auth_token, **kwargs)
+
+
+def _start_inspection(node_uuid, context):
+ """Call to inspector to start inspection."""
+ try:
+ _call_inspector(client.introspect, node_uuid, context)
+ except Exception as exc:
+ LOG.exception(_LE('Exception during contacting ironic-inspector '
+ 'for inspection of node %(node)s: %(err)s'),
+ {'node': node_uuid, 'err': exc})
+ # NOTE(dtantsur): if acquire fails our last option is to rely on
+ # timeout
+ with task_manager.acquire(context, node_uuid) as task:
+ task.node.last_error = _('Failed to start inspection: %s') % exc
+ task.process_event('fail')
+ else:
+ LOG.info(_LI('Node %s was sent to inspection to ironic-inspector'),
+ node_uuid)
+
+
+def _check_status(task):
+ """Check inspection status for node given by a task."""
+ node = task.node
+ if node.provision_state != states.INSPECTING:
+ return
+ if not isinstance(task.driver.inspect, Inspector):
+ return
+
+ LOG.debug('Calling to inspector to check status of node %s',
+ task.node.uuid)
+
+ # NOTE(dtantsur): periodic tasks do not have proper tokens in context
+ task.context.auth_token = keystone.get_admin_auth_token()
+ try:
+ status = _call_inspector(client.get_status, node.uuid, task.context)
+ except Exception:
+ # NOTE(dtantsur): get_status should not normally raise
+ # let's assume it's a transient failure and retry later
+ LOG.exception(_LE('Unexpected exception while getting '
+ 'inspection status for node %s, will retry later'),
+ node.uuid)
+ return
+
+ if status.get('error'):
+ LOG.error(_LE('Inspection failed for node %(uuid)s '
+ 'with error: %(err)s'),
+ {'uuid': node.uuid, 'err': status['error']})
+ node.last_error = (_('ironic-inspector inspection failed: %s')
+ % status['error'])
+ task.process_event('fail')
+ elif status.get('finished'):
+ LOG.info(_LI('Inspection finished successfully for node %s'),
+ node.uuid)
+ task.process_event('done')