# 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.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)