Provide hook into process when a node isn't found
Nodes that don't exist in the inspector node cache maybe still be booted and return inspection information to process, this patch allows an operator to hook into the point when the not found error is thrown so that they may handle that information in anyway they decide to. Change-Id: Ib17491a8201cf534d498c68d916b5b37b07f5ff6 Implements: blueprint node-not-found-hook
This commit is contained in:
parent
3464210adf
commit
5b969e0a31
|
@ -360,6 +360,11 @@
|
|||
# Deprecated group/name - [discoverd]/always_store_ramdisk_logs
|
||||
#always_store_ramdisk_logs = false
|
||||
|
||||
# The name of the hook to run when inspector receives inspection
|
||||
# information from a node it isn't already aware of. This hook is
|
||||
# ignored by default. (string value)
|
||||
#node_not_found_hook = <None>
|
||||
|
||||
|
||||
[swift]
|
||||
|
||||
|
|
|
@ -145,6 +145,11 @@ PROCESSING_OPTS = [
|
|||
'an error message (dependent upon "ramdisk_logs_dir" option '
|
||||
'being set).',
|
||||
deprecated_group='discoverd'),
|
||||
cfg.StrOpt('node_not_found_hook',
|
||||
default=None,
|
||||
help='The name of the hook to run when inspector receives '
|
||||
'inspection information from a node it isn\'t already '
|
||||
'aware of. This hook is ignored by default.')
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -256,8 +256,8 @@ def find_node(**attributes):
|
|||
found.update(item[0] for item in rows)
|
||||
|
||||
if not found:
|
||||
raise utils.Error(_(
|
||||
'Could not find a node for attributes %s') % attributes, code=404)
|
||||
raise utils.NotFoundInCacheError(_(
|
||||
'Could not find a node for attributes %s') % attributes)
|
||||
elif len(found) > 1:
|
||||
raise utils.Error(_(
|
||||
'Multiple matching nodes found for attributes %(attr)s: %(found)s')
|
||||
|
|
|
@ -17,6 +17,7 @@ import abc
|
|||
|
||||
from oslo_config import cfg
|
||||
import six
|
||||
from stevedore import driver
|
||||
from stevedore import named
|
||||
|
||||
|
||||
|
@ -71,6 +72,7 @@ class ProcessingHook(object): # pragma: no cover
|
|||
|
||||
|
||||
_HOOKS_MGR = None
|
||||
_NOT_FOUND_HOOK_MGR = None
|
||||
|
||||
|
||||
def processing_hooks_manager(*args):
|
||||
|
@ -90,3 +92,15 @@ def processing_hooks_manager(*args):
|
|||
invoke_args=args,
|
||||
name_order=True)
|
||||
return _HOOKS_MGR
|
||||
|
||||
|
||||
def node_not_found_hook_manager(*args):
|
||||
global _NOT_FOUND_HOOK_MGR
|
||||
if _NOT_FOUND_HOOK_MGR is None:
|
||||
name = CONF.processing.node_not_found_hook
|
||||
if name:
|
||||
_NOT_FOUND_HOOK_MGR = driver.DriverManager(
|
||||
'ironic_inspector.hooks.node_not_found',
|
||||
name=name)
|
||||
|
||||
return _NOT_FOUND_HOOK_MGR
|
||||
|
|
|
@ -29,3 +29,14 @@ class ExampleProcessingHook(base.ProcessingHook): # pragma: no cover
|
|||
ports_patches, **kwargs):
|
||||
LOG.debug('before_update: %s (node %s)', introspection_data,
|
||||
node_info.uuid)
|
||||
|
||||
|
||||
def example_not_found_hook(self, introspection_data):
|
||||
"""Hook to run when the node cache query returns not found.
|
||||
|
||||
:param node_info: raw information sent by the ramdisk, shouldn't be
|
||||
modified by this hook.
|
||||
:returns: NodeInfo object representing a newly cached node
|
||||
object or None
|
||||
"""
|
||||
LOG.debug('Processing node not found %s', introspection_data)
|
||||
|
|
|
@ -31,6 +31,31 @@ _CREDENTIALS_WAIT_RETRIES = 10
|
|||
_CREDENTIALS_WAIT_PERIOD = 3
|
||||
|
||||
|
||||
def _find_node_info(introspection_data, failures):
|
||||
try:
|
||||
return node_cache.find_node(
|
||||
bmc_address=introspection_data.get('ipmi_address'),
|
||||
mac=introspection_data.get('macs'))
|
||||
except utils.NotFoundInCacheError as exc:
|
||||
not_found_hook = plugins_base.node_not_found_hook_manager()
|
||||
if not_found_hook is None:
|
||||
failures.append(_('Look up error: %s') % exc)
|
||||
return
|
||||
# NOTE(sambetts): If not_found_hook is not none it means that we were
|
||||
# unable to find the node in the node cache and there is a node not
|
||||
# found hook defined so we should try to send the introspection data
|
||||
# to that hook to generate the node info before bubbling up the error.
|
||||
try:
|
||||
node_info = not_found_hook.driver(introspection_data)
|
||||
if node_info:
|
||||
return node_info
|
||||
failures.append(_("Node not found hook returned nothing"))
|
||||
except Exception as exc:
|
||||
failures.append(_("Node not found hook failed: %s") % exc)
|
||||
except utils.Error as exc:
|
||||
failures.append(_('Look up error: %s') % exc)
|
||||
|
||||
|
||||
def process(introspection_data):
|
||||
"""Process data from the ramdisk.
|
||||
|
||||
|
@ -56,16 +81,7 @@ def process(introspection_data):
|
|||
failures.append(_('Unexpected exception during preprocessing '
|
||||
'in hook %s') % hook_ext.name)
|
||||
|
||||
try:
|
||||
node_info = node_cache.find_node(
|
||||
bmc_address=introspection_data.get('ipmi_address'),
|
||||
mac=introspection_data.get('macs'))
|
||||
except utils.Error as exc:
|
||||
if failures:
|
||||
failures.append(_('Look up error: %s') % exc)
|
||||
node_info = None
|
||||
else:
|
||||
raise
|
||||
node_info = _find_node_info(introspection_data, failures)
|
||||
|
||||
if failures and node_info:
|
||||
msg = _('The following failures happened during running '
|
||||
|
@ -75,7 +91,7 @@ def process(introspection_data):
|
|||
}
|
||||
node_info.finished(error=_('Data pre-processing failed'))
|
||||
raise utils.Error(msg)
|
||||
elif failures:
|
||||
elif not node_info:
|
||||
msg = _('The following failures happened during running '
|
||||
'pre-processing hooks for unknown node:\n%(failures)s') % {
|
||||
'failures': '\n'.join(failures)
|
||||
|
|
|
@ -304,6 +304,54 @@ class TestProcess(BaseTest):
|
|||
|
||||
self.assertFalse(pop_mock.return_value.finished.called)
|
||||
|
||||
@prepare_mocks
|
||||
def test_error_if_node_not_found_hook(self, cli, pop_mock, process_mock):
|
||||
plugins_base._NOT_FOUND_HOOK_MGR = None
|
||||
pop_mock.side_effect = utils.NotFoundInCacheError('BOOM')
|
||||
self.assertRaisesRegexp(utils.Error,
|
||||
'Look up error: BOOM',
|
||||
process.process, self.data)
|
||||
|
||||
@prepare_mocks
|
||||
def test_node_not_found_hook_run_ok(self, cli, pop_mock, process_mock):
|
||||
CONF.set_override('node_not_found_hook', 'example', 'processing')
|
||||
plugins_base._NOT_FOUND_HOOK_MGR = None
|
||||
pop_mock.side_effect = utils.NotFoundInCacheError('BOOM')
|
||||
with mock.patch.object(example_plugin,
|
||||
'example_not_found_hook') as hook_mock:
|
||||
hook_mock.return_value = node_cache.NodeInfo(
|
||||
uuid=self.node.uuid,
|
||||
started_at=self.started_at)
|
||||
res = process.process(self.data)
|
||||
self.assertEqual(self.fake_result_json, res)
|
||||
hook_mock.assert_called_once_with(self.data)
|
||||
|
||||
@prepare_mocks
|
||||
def test_node_not_found_hook_run_none(self, cli, pop_mock, process_mock):
|
||||
CONF.set_override('node_not_found_hook', 'example', 'processing')
|
||||
plugins_base._NOT_FOUND_HOOK_MGR = None
|
||||
pop_mock.side_effect = utils.NotFoundInCacheError('BOOM')
|
||||
with mock.patch.object(example_plugin,
|
||||
'example_not_found_hook') as hook_mock:
|
||||
hook_mock.return_value = None
|
||||
self.assertRaisesRegexp(utils.Error,
|
||||
'Node not found hook returned nothing',
|
||||
process.process, self.data)
|
||||
hook_mock.assert_called_once_with(self.data)
|
||||
|
||||
@prepare_mocks
|
||||
def test_node_not_found_hook_exception(self, cli, pop_mock, process_mock):
|
||||
CONF.set_override('node_not_found_hook', 'example', 'processing')
|
||||
plugins_base._NOT_FOUND_HOOK_MGR = None
|
||||
pop_mock.side_effect = utils.NotFoundInCacheError('BOOM')
|
||||
with mock.patch.object(example_plugin,
|
||||
'example_not_found_hook') as hook_mock:
|
||||
hook_mock.side_effect = Exception('Hook Error')
|
||||
self.assertRaisesRegexp(utils.Error,
|
||||
'Node not found hook failed: Hook Error',
|
||||
process.process, self.data)
|
||||
hook_mock.assert_called_once_with(self.data)
|
||||
|
||||
|
||||
@mock.patch.object(node_cache.NodeInfo, 'invalidate_cache', lambda self: None)
|
||||
@mock.patch.object(utils, 'spawn_n',
|
||||
|
|
|
@ -46,6 +46,13 @@ class Error(Exception):
|
|||
self.http_code = code
|
||||
|
||||
|
||||
class NotFoundInCacheError(Error):
|
||||
"""Exception when node was not found in cache during processing."""
|
||||
|
||||
def __init__(self, msg, code=404):
|
||||
super(NotFoundInCacheError, self).__init__(msg, code)
|
||||
|
||||
|
||||
def spawn_n(*args, **kwargs):
|
||||
global GREEN_POOL
|
||||
if not GREEN_POOL:
|
||||
|
|
3
setup.py
3
setup.py
|
@ -42,6 +42,9 @@ setup(
|
|||
"extra_hardware = ironic_inspector.plugins.extra_hardware:ExtraHardwareHook",
|
||||
"root_device_hint = ironic_inspector.plugins.root_device_hint:RootDeviceHintHook",
|
||||
],
|
||||
'ironic_inspector.hooks.node_not_found': [
|
||||
"example = ironic_inspector.plugins.example:example_not_found_hook",
|
||||
],
|
||||
'openstack.cli.extension': [
|
||||
'baremetal-introspection = ironic_inspector.shell',
|
||||
],
|
||||
|
|
Loading…
Reference in New Issue