ironic/ironic/tests/unit/drivers/modules/test_inspector.py

467 lines
22 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.
from unittest import mock
import eventlet
import openstack
from ironic.common import context
from ironic.common import exception
from ironic.common import states
from ironic.common import utils
from ironic.conductor import task_manager
from ironic.drivers.modules import inspector
from ironic.tests.unit.db import base as db_base
from ironic.tests.unit.objects import utils as obj_utils
CONF = inspector.CONF
@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
inspector._INSPECTOR_SESSION = None
self.context = context.RequestContext(global_request_id='global')
def test__get_client(self, mock_conn, mock_session, mock_auth):
inspector._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')
inspector._get_client(self.context)
self.assertEqual('none', inspector.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)
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'],
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(eventlet, 'spawn_n', lambda f, *a, **kw: f(*a, **kw))
@mock.patch('ironic.drivers.modules.inspector._get_client', autospec=True)
class InspectHardwareTestCase(BaseTestCase):
def test_validate_ok(self, mock_client):
self.iface.validate(self.task)
def test_validate_invalid_kernel_params(self, mock_client):
CONF.set_override('extra_kernel_params', 'abcdef', group='inspector')
self.assertRaises(exception.InvalidParameterValue,
self.iface.validate, self.task)
def test_validate_require_managed_boot(self, 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)
def test_unmanaged_ok(self, 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)
def test_unmanaged_error(self, 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)
def test_require_managed_boot(self, 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)
def test_managed_ok(self, 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)
def test_managed_custom_params(self, 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_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',
})
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)
def test_managed_fast_track(self, 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.object(task_manager, 'acquire', autospec=True)
def test_managed_error(self, 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('ironic.drivers.modules.inspector._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)
def test_status_ok(self, 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'])
inspector._check_status(self.task)
mock_get.assert_called_once_with(self.node.uuid)
self.task.process_event.assert_called_once_with('done')
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_status_ok_managed(self, 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'])
inspector._check_status(self.task)
mock_get.assert_called_once_with(self.node.uuid)
self.task.process_event.assert_called_once_with('done')
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_status_ok_managed_no_power_off(self, mock_client):
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()
mock_get = mock_client.return_value.get_introspection
mock_get.return_value = mock.Mock(is_finished=True,
error=None,
spec=['is_finished', 'error'])
inspector._check_status(self.task)
mock_get.assert_called_once_with(self.node.uuid)
self.task.process_event.assert_called_once_with('done')
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_status_error(self, 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'])
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)
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_status_error_managed(self, 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='boom',
spec=['is_finished', 'error'])
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)
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_status_error_managed_no_power_off(self, mock_client):
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()
mock_get = mock_client.return_value.get_introspection
mock_get.return_value = mock.Mock(is_finished=True,
error='boom',
spec=['is_finished', 'error'])
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)
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_status_clean_up_failed(self, 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'])
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)
def test_status_boot_clean_up_failed(self, mock_client):
self.driver.boot.clean_up_ramdisk.side_effect = RuntimeError('boom')
self._test_status_clean_up_failed(mock_client)
self.driver.boot.clean_up_ramdisk.assert_called_once_with(self.task)
def test_status_network_clean_up_failed(self, mock_client):
self.driver.network.remove_inspection_network.side_effect = \
RuntimeError('boom')
self._test_status_clean_up_failed(mock_client)
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('ironic.drivers.modules.inspector._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)