d6774c294d
Change-Id: Ibe4f6bf0b38364b5dd214e6c7e58d45a4d71ffdf
189 lines
7.0 KiB
Python
189 lines
7.0 KiB
Python
# 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.org/project/ironic-inspector
|
|
"""
|
|
|
|
import eventlet
|
|
from futurist import periodics
|
|
import openstack
|
|
from oslo_log import log as logging
|
|
|
|
from ironic.common import exception
|
|
from ironic.common.i18n import _
|
|
from ironic.common import keystone
|
|
from ironic.common import states
|
|
from ironic.conductor import task_manager
|
|
from ironic.conf import CONF
|
|
from ironic.drivers import base
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
_INSPECTOR_SESSION = None
|
|
|
|
|
|
def _get_inspector_session(**kwargs):
|
|
global _INSPECTOR_SESSION
|
|
if not _INSPECTOR_SESSION:
|
|
if CONF.auth_strategy == 'noauth':
|
|
# 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
|
|
return openstack.connection.Connection(
|
|
session=session,
|
|
oslo_conf=conf).baremetal_introspection
|
|
|
|
|
|
class Inspector(base.InspectInterface):
|
|
"""In-band inspection via ironic-inspector project."""
|
|
|
|
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.INSPECTWAIT
|
|
"""
|
|
LOG.debug('Starting inspection for node %(uuid)s using '
|
|
'ironic-inspector', {'uuid': task.node.uuid})
|
|
|
|
# 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.INSPECTWAIT
|
|
|
|
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})
|
|
_get_client(task.context).abort_introspection(node_uuid)
|
|
|
|
@periodics.periodic(spacing=CONF.inspector.status_check_period)
|
|
def _periodic_check_result(self, manager, context):
|
|
"""Periodic task checking results of inspection."""
|
|
filters = {'provision_state': states.INSPECTWAIT}
|
|
node_iter = manager.iter_nodes(filters=filters)
|
|
|
|
for node_uuid, driver, conductor_group in node_iter:
|
|
try:
|
|
lock_purpose = 'checking hardware inspection status'
|
|
with task_manager.acquire(context, node_uuid,
|
|
shared=True,
|
|
purpose=lock_purpose) as task:
|
|
_check_status(task)
|
|
except (exception.NodeLocked, exception.NodeNotFound):
|
|
continue
|
|
|
|
|
|
def _start_inspection(node_uuid, context):
|
|
"""Call to inspector to start inspection."""
|
|
try:
|
|
_get_client(context).start_introspection(node_uuid)
|
|
except Exception as exc:
|
|
LOG.exception('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
|
|
lock_purpose = 'recording hardware inspection error'
|
|
with task_manager.acquire(context, node_uuid,
|
|
purpose=lock_purpose) as task:
|
|
task.node.last_error = _('Failed to start inspection: %s') % exc
|
|
task.process_event('fail')
|
|
else:
|
|
LOG.info('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.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:
|
|
status = _get_client(task.context).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
|
|
|
|
if status.error:
|
|
LOG.error('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.is_finished:
|
|
LOG.info('Inspection finished successfully for node %s', node.uuid)
|
|
task.process_event('done')
|