Switch Inspector interface to pass keystoneauth sessions
This enables fetching the service URL from keystone catalog. Change-Id: If862481533cf121bc6e829f0f39893134578ec30
This commit is contained in:
parent
e57707ae4a
commit
08e66ef8ec
@ -7,7 +7,7 @@
|
|||||||
proliantutils>=2.1.10
|
proliantutils>=2.1.10
|
||||||
pyghmi>=0.8.0
|
pyghmi>=0.8.0
|
||||||
pysnmp
|
pysnmp
|
||||||
python-ironic-inspector-client
|
python-ironic-inspector-client>=1.5.0
|
||||||
python-oneviewclient<3.0.0,>=2.0.2
|
python-oneviewclient<3.0.0,>=2.0.2
|
||||||
python-scciclient>=0.3.0
|
python-scciclient>=0.3.0
|
||||||
python-seamicroclient>=0.4.0
|
python-seamicroclient>=0.4.0
|
||||||
|
@ -1396,8 +1396,7 @@
|
|||||||
#project_name = <None>
|
#project_name = <None>
|
||||||
|
|
||||||
# ironic-inspector HTTP endpoint. If this is not set, the
|
# ironic-inspector HTTP endpoint. If this is not set, the
|
||||||
# ironic-inspector client default (http://127.0.0.1:5050) will
|
# service catalog will be used. (string value)
|
||||||
# be used. (string value)
|
|
||||||
#service_url = <None>
|
#service_url = <None>
|
||||||
|
|
||||||
# period (in seconds) to check status of nodes on inspection
|
# period (in seconds) to check status of nodes on inspection
|
||||||
|
@ -22,8 +22,7 @@ opts = [
|
|||||||
help=_('whether to enable inspection using ironic-inspector')),
|
help=_('whether to enable inspection using ironic-inspector')),
|
||||||
cfg.StrOpt('service_url',
|
cfg.StrOpt('service_url',
|
||||||
help=_('ironic-inspector HTTP endpoint. If this is not set, '
|
help=_('ironic-inspector HTTP endpoint. If this is not set, '
|
||||||
'the ironic-inspector client default '
|
'the service catalog will be used.')),
|
||||||
'(http://127.0.0.1:5050) will be used.')),
|
|
||||||
cfg.IntOpt('status_check_period', default=60,
|
cfg.IntOpt('status_check_period', default=60,
|
||||||
help=_('period (in seconds) to check status of nodes '
|
help=_('period (in seconds) to check status of nodes '
|
||||||
'on inspection')),
|
'on inspection')),
|
||||||
|
@ -44,12 +44,22 @@ _INSPECTOR_SESSION = None
|
|||||||
|
|
||||||
|
|
||||||
def _get_inspector_session():
|
def _get_inspector_session():
|
||||||
|
if CONF.auth_strategy != 'keystone':
|
||||||
|
return
|
||||||
|
|
||||||
global _INSPECTOR_SESSION
|
global _INSPECTOR_SESSION
|
||||||
if not _INSPECTOR_SESSION:
|
if not _INSPECTOR_SESSION:
|
||||||
_INSPECTOR_SESSION = keystone.get_session('inspector')
|
_INSPECTOR_SESSION = keystone.get_session('inspector')
|
||||||
return _INSPECTOR_SESSION
|
return _INSPECTOR_SESSION
|
||||||
|
|
||||||
|
|
||||||
|
def _get_client():
|
||||||
|
"""Helper to get inspector client instance."""
|
||||||
|
return client.ClientV1(api_version=INSPECTOR_API_VERSION,
|
||||||
|
inspector_url=CONF.inspector.service_url,
|
||||||
|
session=_get_inspector_session())
|
||||||
|
|
||||||
|
|
||||||
class Inspector(base.InspectInterface):
|
class Inspector(base.InspectInterface):
|
||||||
"""In-band inspection via ironic-inspector project."""
|
"""In-band inspection via ironic-inspector project."""
|
||||||
|
|
||||||
@ -131,20 +141,10 @@ class Inspector(base.InspectInterface):
|
|||||||
continue
|
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 = {'api_version': INSPECTOR_API_VERSION}
|
|
||||||
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):
|
def _start_inspection(node_uuid, context):
|
||||||
"""Call to inspector to start inspection."""
|
"""Call to inspector to start inspection."""
|
||||||
context.ensure_thread_contain_context()
|
|
||||||
try:
|
try:
|
||||||
_call_inspector(client.introspect, node_uuid, context)
|
_get_client().introspect(node_uuid)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
LOG.exception(_LE('Exception during contacting ironic-inspector '
|
LOG.exception(_LE('Exception during contacting ironic-inspector '
|
||||||
'for inspection of node %(node)s: %(err)s'),
|
'for inspection of node %(node)s: %(err)s'),
|
||||||
@ -172,13 +172,8 @@ def _check_status(task):
|
|||||||
LOG.debug('Calling to inspector to check status of node %s',
|
LOG.debug('Calling to inspector to check status of node %s',
|
||||||
task.node.uuid)
|
task.node.uuid)
|
||||||
|
|
||||||
# NOTE(dtantsur): periodic tasks do not have proper tokens in context
|
|
||||||
if CONF.auth_strategy == 'keystone':
|
|
||||||
session = _get_inspector_session()
|
|
||||||
task.context.auth_token = keystone.get_admin_auth_token(session)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
status = _call_inspector(client.get_status, node.uuid, task.context)
|
status = _get_client().get_status(node.uuid)
|
||||||
except Exception:
|
except Exception:
|
||||||
# NOTE(dtantsur): get_status should not normally raise
|
# NOTE(dtantsur): get_status should not normally raise
|
||||||
# let's assume it's a transient failure and retry later
|
# let's assume it's a transient failure and retry later
|
||||||
|
@ -93,151 +93,129 @@ class CommonFunctionsTestCase(BaseTestCase):
|
|||||||
self.assertTrue(warn_mock.called)
|
self.assertTrue(warn_mock.called)
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch('ironic.common.keystone.get_session',
|
||||||
|
lambda _n: mock.sentinel.session)
|
||||||
@mock.patch.object(eventlet, 'spawn_n', lambda f, *a, **kw: f(*a, **kw))
|
@mock.patch.object(eventlet, 'spawn_n', lambda f, *a, **kw: f(*a, **kw))
|
||||||
@mock.patch.object(client, 'introspect')
|
@mock.patch.object(client.ClientV1, 'introspect')
|
||||||
|
@mock.patch.object(client.ClientV1, '__init__', return_value=None)
|
||||||
class InspectHardwareTestCase(BaseTestCase):
|
class InspectHardwareTestCase(BaseTestCase):
|
||||||
def test_ok(self, mock_introspect):
|
def test_ok(self, mock_init, mock_introspect):
|
||||||
self.assertEqual(states.INSPECTING,
|
self.assertEqual(states.INSPECTING,
|
||||||
self.driver.inspect.inspect_hardware(self.task))
|
self.driver.inspect.inspect_hardware(self.task))
|
||||||
mock_introspect.assert_called_once_with(
|
mock_init.assert_called_once_with(
|
||||||
self.node.uuid,
|
session=mock.sentinel.session,
|
||||||
api_version=self.api_version,
|
api_version=self.api_version,
|
||||||
auth_token=self.task.context.auth_token)
|
inspector_url=None)
|
||||||
|
mock_introspect.assert_called_once_with(self.node.uuid)
|
||||||
|
|
||||||
def test_url(self, mock_introspect):
|
def test_url(self, mock_init, mock_introspect):
|
||||||
self.config(service_url='meow', group='inspector')
|
self.config(service_url='meow', group='inspector')
|
||||||
self.assertEqual(states.INSPECTING,
|
self.assertEqual(states.INSPECTING,
|
||||||
self.driver.inspect.inspect_hardware(self.task))
|
self.driver.inspect.inspect_hardware(self.task))
|
||||||
mock_introspect.assert_called_once_with(
|
mock_init.assert_called_once_with(
|
||||||
self.node.uuid,
|
session=mock.sentinel.session,
|
||||||
api_version=self.api_version,
|
api_version=self.api_version,
|
||||||
auth_token=self.task.context.auth_token,
|
inspector_url='meow')
|
||||||
base_url='meow')
|
mock_introspect.assert_called_once_with(self.node.uuid)
|
||||||
|
|
||||||
@mock.patch.object(task_manager, 'acquire', autospec=True)
|
@mock.patch.object(task_manager, 'acquire', autospec=True)
|
||||||
def test_error(self, mock_acquire, mock_introspect):
|
def test_error(self, mock_acquire, mock_init, mock_introspect):
|
||||||
mock_introspect.side_effect = RuntimeError('boom')
|
mock_introspect.side_effect = RuntimeError('boom')
|
||||||
self.driver.inspect.inspect_hardware(self.task)
|
self.driver.inspect.inspect_hardware(self.task)
|
||||||
mock_introspect.assert_called_once_with(
|
mock_introspect.assert_called_once_with(self.node.uuid)
|
||||||
self.node.uuid,
|
|
||||||
api_version=self.api_version,
|
|
||||||
auth_token=self.task.context.auth_token)
|
|
||||||
task = mock_acquire.return_value.__enter__.return_value
|
task = mock_acquire.return_value.__enter__.return_value
|
||||||
self.assertIn('boom', task.node.last_error)
|
self.assertIn('boom', task.node.last_error)
|
||||||
task.process_event.assert_called_once_with('fail')
|
task.process_event.assert_called_once_with('fail')
|
||||||
|
|
||||||
|
def test_is_standalone(self, mock_init, mock_introspect):
|
||||||
|
self.config(auth_strategy='noauth')
|
||||||
|
self.assertEqual(states.INSPECTING,
|
||||||
|
self.driver.inspect.inspect_hardware(self.task))
|
||||||
|
mock_init.assert_called_once_with(
|
||||||
|
session=None,
|
||||||
|
api_version=self.api_version,
|
||||||
|
inspector_url=None)
|
||||||
|
mock_introspect.assert_called_once_with(self.node.uuid)
|
||||||
|
|
||||||
@mock.patch.object(client, 'get_status')
|
|
||||||
|
@mock.patch('ironic.common.keystone.get_session',
|
||||||
|
lambda _n: mock.sentinel.session)
|
||||||
|
@mock.patch.object(client.ClientV1, 'get_status')
|
||||||
|
@mock.patch.object(client.ClientV1, '__init__', return_value=None)
|
||||||
class CheckStatusTestCase(BaseTestCase):
|
class CheckStatusTestCase(BaseTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(CheckStatusTestCase, self).setUp()
|
super(CheckStatusTestCase, self).setUp()
|
||||||
self.node.provision_state = states.INSPECTING
|
self.node.provision_state = states.INSPECTING
|
||||||
mock_session = mock.Mock()
|
|
||||||
mock_session.get_token.return_value = 'the token'
|
|
||||||
sess_patch = mock.patch.object(inspector, '_get_inspector_session',
|
|
||||||
return_value=mock_session)
|
|
||||||
sess_patch.start()
|
|
||||||
self.addCleanup(sess_patch.stop)
|
|
||||||
|
|
||||||
def test_not_inspecting(self, mock_get):
|
def test_not_inspecting(self, mock_init, mock_get):
|
||||||
self.node.provision_state = states.MANAGEABLE
|
self.node.provision_state = states.MANAGEABLE
|
||||||
inspector._check_status(self.task)
|
inspector._check_status(self.task)
|
||||||
self.assertFalse(mock_get.called)
|
self.assertFalse(mock_get.called)
|
||||||
|
|
||||||
def test_not_inspector(self, mock_get):
|
def test_not_inspector(self, mock_init, mock_get):
|
||||||
self.task.driver.inspect = object()
|
self.task.driver.inspect = object()
|
||||||
inspector._check_status(self.task)
|
inspector._check_status(self.task)
|
||||||
self.assertFalse(mock_get.called)
|
self.assertFalse(mock_get.called)
|
||||||
|
|
||||||
def test_not_finished(self, mock_get):
|
def test_not_finished(self, mock_init, mock_get):
|
||||||
mock_get.return_value = {}
|
mock_get.return_value = {}
|
||||||
inspector._check_status(self.task)
|
inspector._check_status(self.task)
|
||||||
mock_get.assert_called_once_with(self.node.uuid,
|
mock_init.assert_called_once_with(
|
||||||
|
session=mock.sentinel.session,
|
||||||
api_version=self.api_version,
|
api_version=self.api_version,
|
||||||
auth_token='the token')
|
inspector_url=None)
|
||||||
|
mock_get.assert_called_once_with(self.node.uuid)
|
||||||
self.assertFalse(self.task.process_event.called)
|
self.assertFalse(self.task.process_event.called)
|
||||||
|
|
||||||
def test_exception_ignored(self, mock_get):
|
def test_exception_ignored(self, mock_init, mock_get):
|
||||||
mock_get.side_effect = RuntimeError('boom')
|
mock_get.side_effect = RuntimeError('boom')
|
||||||
inspector._check_status(self.task)
|
inspector._check_status(self.task)
|
||||||
mock_get.assert_called_once_with(self.node.uuid,
|
mock_init.assert_called_once_with(
|
||||||
|
session=mock.sentinel.session,
|
||||||
api_version=self.api_version,
|
api_version=self.api_version,
|
||||||
auth_token='the token')
|
inspector_url=None)
|
||||||
|
mock_get.assert_called_once_with(self.node.uuid)
|
||||||
self.assertFalse(self.task.process_event.called)
|
self.assertFalse(self.task.process_event.called)
|
||||||
|
|
||||||
def test_status_ok(self, mock_get):
|
def test_status_ok(self, mock_init, mock_get):
|
||||||
mock_get.return_value = {'finished': True}
|
mock_get.return_value = {'finished': True}
|
||||||
inspector._check_status(self.task)
|
inspector._check_status(self.task)
|
||||||
mock_get.assert_called_once_with(self.node.uuid,
|
mock_init.assert_called_once_with(
|
||||||
|
session=mock.sentinel.session,
|
||||||
api_version=self.api_version,
|
api_version=self.api_version,
|
||||||
auth_token='the token')
|
inspector_url=None)
|
||||||
|
mock_get.assert_called_once_with(self.node.uuid)
|
||||||
self.task.process_event.assert_called_once_with('done')
|
self.task.process_event.assert_called_once_with('done')
|
||||||
|
|
||||||
def test_status_error(self, mock_get):
|
def test_status_error(self, mock_init, mock_get):
|
||||||
mock_get.return_value = {'error': 'boom'}
|
mock_get.return_value = {'error': 'boom'}
|
||||||
inspector._check_status(self.task)
|
inspector._check_status(self.task)
|
||||||
mock_get.assert_called_once_with(self.node.uuid,
|
mock_init.assert_called_once_with(
|
||||||
|
session=mock.sentinel.session,
|
||||||
api_version=self.api_version,
|
api_version=self.api_version,
|
||||||
auth_token='the token')
|
inspector_url=None)
|
||||||
|
mock_get.assert_called_once_with(self.node.uuid)
|
||||||
self.task.process_event.assert_called_once_with('fail')
|
self.task.process_event.assert_called_once_with('fail')
|
||||||
self.assertIn('boom', self.node.last_error)
|
self.assertIn('boom', self.node.last_error)
|
||||||
|
|
||||||
def test_service_url(self, mock_get):
|
def test_service_url(self, mock_init, mock_get):
|
||||||
self.config(service_url='meow', group='inspector')
|
self.config(service_url='meow', group='inspector')
|
||||||
mock_get.return_value = {'finished': True}
|
mock_get.return_value = {'finished': True}
|
||||||
inspector._check_status(self.task)
|
inspector._check_status(self.task)
|
||||||
mock_get.assert_called_once_with(self.node.uuid,
|
mock_init.assert_called_once_with(
|
||||||
|
session=mock.sentinel.session,
|
||||||
api_version=self.api_version,
|
api_version=self.api_version,
|
||||||
auth_token='the token',
|
inspector_url='meow')
|
||||||
base_url='meow')
|
mock_get.assert_called_once_with(self.node.uuid)
|
||||||
self.task.process_event.assert_called_once_with('done')
|
self.task.process_event.assert_called_once_with('done')
|
||||||
|
|
||||||
def test_is_standalone(self, mock_get):
|
def test_is_standalone(self, mock_init, mock_get):
|
||||||
self.config(auth_strategy='noauth')
|
self.config(auth_strategy='noauth')
|
||||||
mock_get.return_value = {'finished': True}
|
mock_get.return_value = {'finished': True}
|
||||||
inspector._check_status(self.task)
|
inspector._check_status(self.task)
|
||||||
mock_get.assert_called_once_with(
|
mock_init.assert_called_once_with(
|
||||||
self.node.uuid,
|
session=None,
|
||||||
api_version=self.api_version,
|
api_version=self.api_version,
|
||||||
auth_token=self.task.context.auth_token)
|
inspector_url=None)
|
||||||
|
mock_get.assert_called_once_with(self.node.uuid)
|
||||||
self.task.process_event.assert_called_once_with('done')
|
self.task.process_event.assert_called_once_with('done')
|
||||||
|
|
||||||
def test_not_standalone(self, mock_get):
|
|
||||||
self.config(auth_strategy='keystone')
|
|
||||||
mock_get.return_value = {'finished': True}
|
|
||||||
inspector._check_status(self.task)
|
|
||||||
mock_get.assert_called_once_with(self.node.uuid,
|
|
||||||
api_version=self.api_version,
|
|
||||||
auth_token='the token')
|
|
||||||
self.task.process_event.assert_called_once_with('done')
|
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(eventlet.greenthread, 'spawn_n',
|
|
||||||
lambda f, *a, **kw: f(*a, **kw))
|
|
||||||
@mock.patch.object(task_manager, 'acquire', autospec=True)
|
|
||||||
@mock.patch.object(inspector, '_check_status', autospec=True)
|
|
||||||
class PeriodicTaskTestCase(BaseTestCase):
|
|
||||||
def test_ok(self, mock_check, mock_acquire):
|
|
||||||
mgr = mock.MagicMock(spec=['iter_nodes'])
|
|
||||||
mgr.iter_nodes.return_value = [('1', 'd1'), ('2', 'd2')]
|
|
||||||
tasks = [mock.sentinel.task1, mock.sentinel.task2]
|
|
||||||
mock_acquire.side_effect = (
|
|
||||||
mock.MagicMock(__enter__=mock.MagicMock(return_value=task))
|
|
||||||
for task in tasks
|
|
||||||
)
|
|
||||||
inspector.Inspector()._periodic_check_result(
|
|
||||||
mgr, mock.sentinel.context)
|
|
||||||
mock_check.assert_any_call(tasks[0])
|
|
||||||
mock_check.assert_any_call(tasks[1])
|
|
||||||
self.assertEqual(2, mock_acquire.call_count)
|
|
||||||
|
|
||||||
def test_node_locked(self, mock_check, mock_acquire):
|
|
||||||
iter_nodes_ret = [('1', 'd1'), ('2', 'd2')]
|
|
||||||
mock_acquire.side_effect = ([exception.NodeLocked("boom")] *
|
|
||||||
len(iter_nodes_ret))
|
|
||||||
mgr = mock.MagicMock(spec=['iter_nodes'])
|
|
||||||
mgr.iter_nodes.return_value = iter_nodes_ret
|
|
||||||
inspector.Inspector()._periodic_check_result(
|
|
||||||
mgr, mock.sentinel.context)
|
|
||||||
self.assertFalse(mock_check.called)
|
|
||||||
self.assertEqual(2, mock_acquire.call_count)
|
|
||||||
|
@ -40,10 +40,21 @@ IBOOT_SPEC = (
|
|||||||
|
|
||||||
# ironic_inspector
|
# ironic_inspector
|
||||||
IRONIC_INSPECTOR_CLIENT_SPEC = (
|
IRONIC_INSPECTOR_CLIENT_SPEC = (
|
||||||
'introspect',
|
'ClientV1',
|
||||||
'get_status',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class InspectorClientV1Specs(object):
|
||||||
|
def __init__(self, session, inspector_url, api_version):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def introspect(self, uuid):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_status(self, uuid):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
# proliantutils
|
# proliantutils
|
||||||
PROLIANTUTILS_SPEC = (
|
PROLIANTUTILS_SPEC = (
|
||||||
'exception',
|
'exception',
|
||||||
|
@ -261,6 +261,7 @@ ironic_inspector_client = importutils.try_import('ironic_inspector_client')
|
|||||||
if not ironic_inspector_client:
|
if not ironic_inspector_client:
|
||||||
ironic_inspector_client = mock.MagicMock(
|
ironic_inspector_client = mock.MagicMock(
|
||||||
spec_set=mock_specs.IRONIC_INSPECTOR_CLIENT_SPEC)
|
spec_set=mock_specs.IRONIC_INSPECTOR_CLIENT_SPEC)
|
||||||
|
ironic_inspector_client.ClientV1 = mock_specs.InspectorClientV1Specs
|
||||||
sys.modules['ironic_inspector_client'] = ironic_inspector_client
|
sys.modules['ironic_inspector_client'] = ironic_inspector_client
|
||||||
if 'ironic.drivers.modules.inspector' in sys.modules:
|
if 'ironic.drivers.modules.inspector' in sys.modules:
|
||||||
six.moves.reload_module(
|
six.moves.reload_module(
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
fixes:
|
||||||
|
- Ironic Inspector inspection interface will not fetch the service endpoint
|
||||||
|
for the service catalog, if "service_url" is not provided and keystone
|
||||||
|
support is enabled.
|
||||||
|
upgrade:
|
||||||
|
- Minimum required version of python-ironic-inspector-client was bumped
|
||||||
|
to 1.5.0 (released as part of the Mitaka cycle).
|
Loading…
Reference in New Issue
Block a user