diff --git a/ironic/drivers/base.py b/ironic/drivers/base.py index 40694f3bc6..f71d3931a3 100644 --- a/ironic/drivers/base.py +++ b/ironic/drivers/base.py @@ -182,6 +182,10 @@ class BaseInterface(object): :param task: A TaskManager object, useful for interfaces overriding this function + :raises NodeCleaningFailure: if there is a problem getting the steps + from the driver. For example, when a node (using an agent driver) + has just been enrolled and the agent isn't alive yet to be queried + for the available clean steps. :returns: A list of clean step dictionaries """ return self.clean_steps diff --git a/ironic/drivers/modules/agent.py b/ironic/drivers/modules/agent.py index 2186f55297..6a1e8c0af6 100644 --- a/ironic/drivers/modules/agent.py +++ b/ironic/drivers/modules/agent.py @@ -359,7 +359,9 @@ class AgentDeploy(base.DeployInterface): """Get the list of clean steps from the agent. :param task: a TaskManager object containing the node - + :raises NodeCleaningFailure: if the clean steps are not yet + available (cached), for example, when a node has just been + enrolled and has not been cleaned yet. :returns: A list of clean step dictionaries """ new_priorities = { diff --git a/ironic/drivers/modules/agent_base_vendor.py b/ironic/drivers/modules/agent_base_vendor.py index 06525a4895..4ca4183804 100644 --- a/ironic/drivers/modules/agent_base_vendor.py +++ b/ironic/drivers/modules/agent_base_vendor.py @@ -16,12 +16,13 @@ # License for the specific language governing permissions and limitations # under the License. - +import collections import time from oslo_config import cfg from oslo_log import log from oslo_utils import excutils +from oslo_utils import timeutils import retrying from ironic.common import boot_devices @@ -215,6 +216,62 @@ class BaseAgentVendor(base.VendorInterface): task.release_resources() rpc.continue_node_clean(task.context, uuid, topic=topic) + def _refresh_clean_steps(self, task): + """Refresh the node's cached clean steps from the booted agent. + + Gets the node's clean steps from the booted agent and caches them. + The steps are cached to make get_clean_steps() calls synchronous, and + should be refreshed as soon as the agent boots to start cleaning or + if cleaning is restarted because of a cleaning version mismatch. + + :param task: a TaskManager instance + :raises: NodeCleaningFailure if the agent returns invalid results + """ + node = task.node + previous_steps = node.driver_internal_info.get( + 'agent_cached_clean_steps') + LOG.debug('Refreshing agent clean step cache for node %(node)s. ' + 'Previously cached steps: %(steps)s', + {'node': node.uuid, 'steps': previous_steps}) + + agent_result = self._client.get_clean_steps(node, task.ports).get( + 'command_result', {}) + missing = set(['clean_steps', 'hardware_manager_version']).difference( + agent_result) + if missing: + raise exception.NodeCleaningFailure(_( + 'agent get_clean_steps for node %(node)s returned an invalid ' + 'result. Keys: %(keys)s are missing from result: %(result)s.') + % ({'node': node.uuid, 'keys': missing, + 'result': agent_result})) + + # agent_result['clean_steps'] looks like + # {'HardwareManager': [{step1},{steps2}...], ...} + steps = collections.defaultdict(list) + for step_list in agent_result['clean_steps'].values(): + for step in step_list: + missing = set(['interface', 'step', 'priority']).difference( + step) + if missing: + raise exception.NodeCleaningFailure(_( + 'agent get_clean_steps for node %(node)s returned an ' + 'invalid clean step. Keys: %(keys)s are missing from ' + 'step: %(step)s.') % ({'node': node.uuid, + 'keys': missing, 'step': step})) + + steps[step['interface']].append(step) + + # Save hardware manager version, steps, and date + info = node.driver_internal_info + info['hardware_manager_version'] = agent_result[ + 'hardware_manager_version'] + info['agent_cached_clean_steps'] = dict(steps) + info['agent_cached_clean_steps_refreshed'] = str(timeutils.utcnow()) + node.driver_internal_info = info + node.save() + LOG.debug('Refreshed agent clean step cache for node %(node)s: ' + '%(steps)s', {'node': node.uuid, 'steps': steps}) + def continue_cleaning(self, task, **kwargs): """Start the next cleaning step if the previous one is complete. @@ -249,6 +306,16 @@ class BaseAgentVendor(base.VendorInterface): LOG.error(msg) return manager_utils.cleaning_error_handler(task, msg) elif command.get('command_status') == 'CLEAN_VERSION_MISMATCH': + # Cache the new clean steps (and 'hardware_manager_version') + try: + self._refresh_clean_steps(task) + except exception.NodeCleaningFailure as e: + msg = (_('Could not continue cleaning on node ' + '%(node)s: %(err)s.') % + {'node': node.uuid, 'err': e}) + LOG.exception(msg) + return manager_utils.cleaning_error_handler(task, msg) + if manual_clean: # Don't restart manual cleaning if agent reboots to a new # version. Both are operator actions, unlike automated @@ -259,27 +326,7 @@ class BaseAgentVendor(base.VendorInterface): 'continuing from current step %(step)s.'), {'node': node.uuid, 'step': node.clean_step}) - result = self._client.get_clean_steps( - task.node, task.ports).get('command_result') - - required_keys = ('clean_steps', 'hardware_manager_version') - missing_keys = [key for key in required_keys - if key not in result] - if missing_keys: - msg = (_('Could not continue manual cleaning from step ' - '%(step)s on node %(node)s. get_clean_steps ' - 'returned invalid result. The keys %(keys)s are ' - 'missing from result %(result)s.') % - {'node': node.uuid, - 'step': node.clean_step, - 'keys': missing_keys, - 'result': result}) - LOG.error(msg) - return manager_utils.cleaning_error_handler(task, msg) - driver_internal_info = node.driver_internal_info - driver_internal_info['hardware_manager_version'] = result[ - 'hardware_manager_version'] driver_internal_info['skip_current_clean_step'] = False node.driver_internal_info = driver_internal_info node.save() @@ -397,6 +444,9 @@ class BaseAgentVendor(base.VendorInterface): node.uuid) msg = _('Node failed to start the first cleaning ' 'step.') + # First, cache the clean steps + self._refresh_clean_steps(task) + # Then set/verify node clean steps and start cleaning manager_utils.set_node_cleaning_steps(task) self.notify_conductor_resume_clean(task) else: diff --git a/ironic/drivers/modules/deploy_utils.py b/ironic/drivers/modules/deploy_utils.py index 2148172290..48d1a2c7c8 100644 --- a/ironic/drivers/modules/deploy_utils.py +++ b/ironic/drivers/modules/deploy_utils.py @@ -550,10 +550,12 @@ def parse_instance_info_capabilities(node): def agent_get_clean_steps(task, interface=None, override_priorities=None): - """Get the list of clean steps from the agent. + """Get the list of cached clean steps from the agent. #TODO(JoshNang) move to BootInterface + The clean steps cache is updated at the beginning of cleaning. + :param task: a TaskManager object containing the node :param interface: The interface for which clean steps are to be returned. If this is not provided, it returns the @@ -561,42 +563,34 @@ def agent_get_clean_steps(task, interface=None, override_priorities=None): :param override_priorities: a dictionary with keys being step names and values being new priorities for them. If a step isn't in this dictionary, the step's original priority is used. - :raises: NodeCleaningFailure if the agent returns invalid results + :raises NodeCleaningFailure: if the clean steps are not yet cached, + for example, when a node has just been enrolled and has not been + cleaned yet. :returns: A list of clean step dictionaries """ - override_priorities = override_priorities or {} - client = agent_client.AgentClient() - ports = objects.Port.list_by_node_id( - task.context, task.node.id) - result = client.get_clean_steps(task.node, ports).get('command_result') + node = task.node + try: + all_steps = node.driver_internal_info['agent_cached_clean_steps'] + except KeyError: + raise exception.NodeCleaningFailure(_('Cleaning steps are not yet ' + 'available for node %(node)s') + % {'node': node.uuid}) - if ('clean_steps' not in result or - 'hardware_manager_version' not in result): - raise exception.NodeCleaningFailure(_( - 'get_clean_steps for node %(node)s returned invalid result:' - ' %(result)s') % ({'node': task.node.uuid, 'result': result})) + if interface: + steps = [step.copy() for step in all_steps.get(interface, [])] + else: + steps = [step.copy() for step_list in all_steps.values() + for step in step_list] - driver_internal_info = task.node.driver_internal_info - driver_internal_info['hardware_manager_version'] = result[ - 'hardware_manager_version'] - task.node.driver_internal_info = driver_internal_info - task.node.save() + if not steps or not override_priorities: + return steps - # Clean steps looks like {'HardwareManager': [{step1},{steps2}..]..} - # Flatten clean steps into one list - steps_list = [step for step_list in - result['clean_steps'].values() - for step in step_list] - result = [] - for step in steps_list: - if interface and step.get('interface') != interface: - continue + for step in steps: new_priority = override_priorities.get(step.get('step')) if new_priority is not None: step['priority'] = new_priority - result.append(step) - return result + return steps def agent_execute_clean_step(task, step): diff --git a/ironic/drivers/modules/ilo/deploy.py b/ironic/drivers/modules/ilo/deploy.py index 307652b25f..0fc4bddf87 100644 --- a/ironic/drivers/modules/ilo/deploy.py +++ b/ironic/drivers/modules/ilo/deploy.py @@ -213,6 +213,9 @@ class IloVirtualMediaAgentDeploy(agent.AgentDeploy): """Get the list of clean steps from the agent. :param task: a TaskManager object containing the node + :raises NodeCleaningFailure: if the clean steps are not yet + available (cached), for example, when a node has just been + enrolled and has not been cleaned yet. :returns: A list of clean step dictionaries """ diff --git a/ironic/drivers/modules/iscsi_deploy.py b/ironic/drivers/modules/iscsi_deploy.py index c187d5f1f9..cdd68d9f61 100644 --- a/ironic/drivers/modules/iscsi_deploy.py +++ b/ironic/drivers/modules/iscsi_deploy.py @@ -751,7 +751,9 @@ class ISCSIDeploy(base.DeployInterface): """Get the list of clean steps from the agent. :param task: a TaskManager object containing the node - + :raises NodeCleaningFailure: if the clean steps are not yet + available (cached), for example, when a node has just been + enrolled and has not been cleaned yet. :returns: A list of clean step dictionaries. If bash ramdisk is used for this node, it returns an empty list. """ diff --git a/ironic/tests/unit/drivers/modules/test_agent_base_vendor.py b/ironic/tests/unit/drivers/modules/test_agent_base_vendor.py index e8ed66410d..7f09f104d1 100644 --- a/ironic/tests/unit/drivers/modules/test_agent_base_vendor.py +++ b/ironic/tests/unit/drivers/modules/test_agent_base_vendor.py @@ -357,11 +357,13 @@ class TestBaseAgentVendor(db_base.DbTestCase): 'is done. Exception: LlamaException') @mock.patch.object(objects.node.Node, 'touch_provisioning', autospec=True) + @mock.patch.object(agent_base_vendor.BaseAgentVendor, + '_refresh_clean_steps', autospec=True) @mock.patch.object(manager_utils, 'set_node_cleaning_steps', autospec=True) @mock.patch.object(agent_base_vendor.BaseAgentVendor, 'notify_conductor_resume_clean', autospec=True) def test_heartbeat_resume_clean(self, mock_notify, mock_set_steps, - mock_touch): + mock_refresh, mock_touch): kwargs = { 'agent_url': 'http://127.0.0.1:9999/bar' } @@ -374,13 +376,55 @@ class TestBaseAgentVendor(db_base.DbTestCase): self.passthru.heartbeat(task, **kwargs) mock_touch.assert_called_once_with(mock.ANY) + mock_refresh.assert_called_once_with(mock.ANY, task) mock_notify.assert_called_once_with(mock.ANY, task) mock_set_steps.assert_called_once_with(task) # Reset mocks for the next interaction mock_touch.reset_mock() + mock_refresh.reset_mock() mock_notify.reset_mock() mock_set_steps.reset_mock() + @mock.patch.object(manager_utils, 'cleaning_error_handler') + @mock.patch.object(objects.node.Node, 'touch_provisioning', autospec=True) + @mock.patch.object(agent_base_vendor.BaseAgentVendor, + '_refresh_clean_steps', autospec=True) + @mock.patch.object(manager_utils, 'set_node_cleaning_steps', autospec=True) + @mock.patch.object(agent_base_vendor.BaseAgentVendor, + 'notify_conductor_resume_clean', autospec=True) + def test_heartbeat_resume_clean_fails(self, mock_notify, mock_set_steps, + mock_refresh, mock_touch, + mock_handler): + mocks = [mock_refresh, mock_set_steps, mock_notify] + kwargs = { + 'agent_url': 'http://127.0.0.1:9999/bar' + } + self.node.clean_step = {} + self.node.save() + for state in (states.CLEANWAIT, states.CLEANING): + self.node.provision_state = state + self.node.save() + for i in range(len(mocks)): + before_failed_mocks = mocks[:i] + failed_mock = mocks[i] + after_failed_mocks = mocks[i + 1:] + failed_mock.side_effect = Exception() + with task_manager.acquire( + self.context, self.node.uuid, shared=True) as task: + self.passthru.heartbeat(task, **kwargs) + + mock_touch.assert_called_once_with(mock.ANY) + mock_handler.assert_called_once_with(task, mock.ANY) + for called in before_failed_mocks + [failed_mock]: + self.assertTrue(called.called) + for not_called in after_failed_mocks: + self.assertFalse(not_called.called) + + # Reset mocks for the next interaction + for m in mocks + [mock_touch, mock_handler]: + m.reset_mock() + failed_mock.side_effect = None + @mock.patch.object(objects.node.Node, 'touch_provisioning', autospec=True) @mock.patch.object(agent_base_vendor.BaseAgentVendor, 'continue_cleaning', autospec=True) @@ -897,25 +941,13 @@ class TestBaseAgentVendor(db_base.DbTestCase): @mock.patch.object(manager_utils, 'set_node_cleaning_steps', autospec=True) @mock.patch.object(agent_base_vendor.BaseAgentVendor, 'notify_conductor_resume_clean', autospec=True) - @mock.patch.object(agent_client.AgentClient, 'get_clean_steps', - autospec=True) + @mock.patch.object(agent_base_vendor.BaseAgentVendor, + '_refresh_clean_steps', autospec=True) @mock.patch.object(agent_client.AgentClient, 'get_commands_status', autospec=True) def _test_continue_cleaning_clean_version_mismatch( - self, status_mock, get_steps_mock, notify_mock, steps_mock, + self, status_mock, refresh_steps_mock, notify_mock, steps_mock, manual=False): - get_steps_mock.return_value = { - 'command_status': 'CLEAN_VERSION_MISMATCH', - 'command_name': 'get_clean_step', - 'command_result': { - 'hardware_manager_version': {'Generic': '1'}, - 'clean_steps': { - 'GenericHardwareManager': [ - {'interface': 'deploy', - 'step': 'erase_devices', - 'priority': 20}]} - } - } status_mock.return_value = [{ 'command_status': 'CLEAN_VERSION_MISMATCH', 'command_name': 'execute_clean_step', @@ -928,17 +960,12 @@ class TestBaseAgentVendor(db_base.DbTestCase): shared=False) as task: self.passthru.continue_cleaning(task) notify_mock.assert_called_once_with(mock.ANY, task) + refresh_steps_mock.assert_called_once_with(mock.ANY, task) if manual: - get_steps_mock.assert_called_once_with(mock.ANY, task.node, - task.ports) self.assertFalse( task.node.driver_internal_info['skip_current_clean_step']) - self.assertEqual( - {'Generic': '1'}, - task.node.driver_internal_info['hardware_manager_version']) self.assertFalse(steps_mock.called) else: - self.assertFalse(get_steps_mock.called) steps_mock.assert_called_once_with(task) self.assertFalse('skip_current_clean_step' in task.node.driver_internal_info) @@ -950,25 +977,23 @@ class TestBaseAgentVendor(db_base.DbTestCase): self._test_continue_cleaning_clean_version_mismatch(manual=True) @mock.patch.object(manager_utils, 'cleaning_error_handler', autospec=True) + @mock.patch.object(manager_utils, 'set_node_cleaning_steps', autospec=True) @mock.patch.object(agent_base_vendor.BaseAgentVendor, 'notify_conductor_resume_clean', autospec=True) - @mock.patch.object(agent_client.AgentClient, 'get_clean_steps', - autospec=True) + @mock.patch.object(agent_base_vendor.BaseAgentVendor, + '_refresh_clean_steps', autospec=True) @mock.patch.object(agent_client.AgentClient, 'get_commands_status', autospec=True) - def test_continue_cleaning_manual_version_mismatch_bad( - self, status_mock, get_steps_mock, notify_mock, error_mock): - get_steps_mock.return_value = { - 'command_status': 'CLEAN_VERSION_MISMATCH', - 'command_name': 'get_clean_step', - 'command_result': { - 'hardware_manager_version': {'Generic': '1'}} - } + def test_continue_cleaning_clean_version_mismatch_fail( + self, status_mock, refresh_steps_mock, notify_mock, steps_mock, + error_mock, manual=False): status_mock.return_value = [{ 'command_status': 'CLEAN_VERSION_MISMATCH', 'command_name': 'execute_clean_step', + 'command_result': {'hardware_manager_version': {'Generic': '1'}} }] - tgt_prov_state = states.MANAGEABLE + refresh_steps_mock.side_effect = exception.NodeCleaningFailure("boo") + tgt_prov_state = states.MANAGEABLE if manual else states.AVAILABLE self.node.provision_state = states.CLEANWAIT self.node.target_provision_state = tgt_prov_state self.node.save() @@ -976,12 +1001,11 @@ class TestBaseAgentVendor(db_base.DbTestCase): shared=False) as task: self.passthru.continue_cleaning(task) - get_steps_mock.assert_called_once_with(mock.ANY, task.node, - task.ports) + status_mock.assert_called_once_with(mock.ANY, task.node) + refresh_steps_mock.assert_called_once_with(mock.ANY, task) error_mock.assert_called_once_with(task, mock.ANY) self.assertFalse(notify_mock.called) - self.assertFalse('skip_current_clean_step' in - task.node.driver_internal_info) + self.assertFalse(steps_mock.called) @mock.patch.object(manager_utils, 'cleaning_error_handler', autospec=True) @mock.patch.object(agent_client.AgentClient, 'get_commands_status', @@ -1076,3 +1100,91 @@ class TestBaseAgentVendor(db_base.DbTestCase): self.node.save() hook_returned = agent_base_vendor._get_post_clean_step_hook(self.node) self.assertIsNone(hook_returned) + + +class TestRefreshCleanSteps(TestBaseAgentVendor): + + def setUp(self): + super(TestRefreshCleanSteps, self).setUp() + self.node.driver_internal_info['agent_url'] = 'http://127.0.0.1:9999' + self.ports = [object_utils.create_test_port(self.context, + node_id=self.node.id)] + + self.clean_steps = { + 'hardware_manager_version': '1', + 'clean_steps': { + 'GenericHardwareManager': [ + {'interface': 'deploy', + 'step': 'erase_devices', + 'priority': 20}, + ], + 'SpecificHardwareManager': [ + {'interface': 'deploy', + 'step': 'update_firmware', + 'priority': 30}, + {'interface': 'raid', + 'step': 'create_configuration', + 'priority': 10}, + ] + } + } + + @mock.patch.object(agent_client.AgentClient, 'get_clean_steps', + autospec=True) + def test__refresh_clean_steps(self, client_mock): + client_mock.return_value = { + 'command_result': self.clean_steps} + + with task_manager.acquire( + self.context, self.node.uuid, shared=False) as task: + self.passthru._refresh_clean_steps(task) + + client_mock.assert_called_once_with(mock.ANY, task.node, + task.ports) + self.assertEqual('1', task.node.driver_internal_info[ + 'hardware_manager_version']) + self.assertTrue('agent_cached_clean_steps_refreshed' in + task.node.driver_internal_info) + steps = task.node.driver_internal_info['agent_cached_clean_steps'] + # Since steps are returned in dicts, they have non-deterministic + # ordering + self.assertEqual(2, len(steps)) + self.assertIn(self.clean_steps['clean_steps'][ + 'GenericHardwareManager'][0], steps['deploy']) + self.assertIn(self.clean_steps['clean_steps'][ + 'SpecificHardwareManager'][0], steps['deploy']) + self.assertEqual([self.clean_steps['clean_steps'][ + 'SpecificHardwareManager'][1]], steps['raid']) + + @mock.patch.object(agent_client.AgentClient, 'get_clean_steps', + autospec=True) + def test__refresh_clean_steps_missing_steps(self, client_mock): + del self.clean_steps['clean_steps'] + client_mock.return_value = { + 'command_result': self.clean_steps} + + with task_manager.acquire( + self.context, self.node.uuid, shared=False) as task: + self.assertRaisesRegex(exception.NodeCleaningFailure, + 'invalid result', + self.passthru._refresh_clean_steps, + task) + client_mock.assert_called_once_with(mock.ANY, task.node, + task.ports) + + @mock.patch.object(agent_client.AgentClient, 'get_clean_steps', + autospec=True) + def test__refresh_clean_steps_missing_interface(self, client_mock): + step = self.clean_steps['clean_steps']['SpecificHardwareManager'][1] + del step['interface'] + client_mock.return_value = { + 'command_result': self.clean_steps} + + with task_manager.acquire( + self.context, self.node.uuid, shared=False) as task: + self.assertRaisesRegex(exception.NodeCleaningFailure, + 'invalid clean step', + self.passthru._refresh_clean_steps, + task) + client_mock.assert_called_once_with(mock.ANY, task.node, + task.ports) diff --git a/ironic/tests/unit/drivers/modules/test_deploy_utils.py b/ironic/tests/unit/drivers/modules/test_deploy_utils.py index 3309432283..ad5b690111 100644 --- a/ironic/tests/unit/drivers/modules/test_deploy_utils.py +++ b/ironic/tests/unit/drivers/modules/test_deploy_utils.py @@ -1521,142 +1521,75 @@ class AgentMethodsTestCase(db_base.DbTestCase): def setUp(self): super(AgentMethodsTestCase, self).setUp() mgr_utils.mock_the_extension_manager(driver='fake_agent') - n = {'driver': 'fake_agent', - 'driver_internal_info': {'agent_url': 'http://127.0.0.1:9999'}} + self.clean_steps = { + 'deploy': [ + {'interface': 'deploy', + 'step': 'erase_devices', + 'priority': 20}, + {'interface': 'deploy', + 'step': 'update_firmware', + 'priority': 30} + ], + 'raid': [ + {'interface': 'raid', + 'step': 'create_configuration', + 'priority': 10} + ] + } + n = {'driver': 'fake_agent', + 'driver_internal_info': { + 'agent_cached_clean_steps': self.clean_steps}} self.node = obj_utils.create_test_node(self.context, **n) self.ports = [obj_utils.create_test_port(self.context, node_id=self.node.id)] - self.clean_steps = { - 'hardware_manager_version': '1', - 'clean_steps': { - 'GenericHardwareManager': [ - {'interface': 'deploy', - 'step': 'erase_devices', - 'priority': 20}, - ], - 'SpecificHardwareManager': [ - {'interface': 'deploy', - 'step': 'update_firmware', - 'priority': 30}, - {'interface': 'raid', - 'step': 'create_configuration', - 'priority': 10}, - ] - } - } - - @mock.patch('ironic.objects.Port.list_by_node_id', - spec_set=types.FunctionType) - @mock.patch.object(agent_client.AgentClient, 'get_clean_steps', - autospec=True) - def test_get_clean_steps(self, client_mock, list_ports_mock): - client_mock.return_value = { - 'command_result': self.clean_steps} - list_ports_mock.return_value = self.ports - + def test_agent_get_clean_steps(self): with task_manager.acquire( - self.context, self.node['uuid'], shared=False) as task: + self.context, self.node.uuid, shared=False) as task: response = utils.agent_get_clean_steps(task) - client_mock.assert_called_once_with(mock.ANY, task.node, - self.ports) - self.assertEqual('1', task.node.driver_internal_info[ - 'hardware_manager_version']) # Since steps are returned in dicts, they have non-deterministic # ordering self.assertThat(response, matchers.HasLength(3)) - self.assertIn(self.clean_steps['clean_steps'][ - 'GenericHardwareManager'][0], response) - self.assertIn(self.clean_steps['clean_steps'][ - 'SpecificHardwareManager'][0], response) - self.assertIn(self.clean_steps['clean_steps'][ - 'SpecificHardwareManager'][1], response) - - @mock.patch('ironic.objects.Port.list_by_node_id', - spec_set=types.FunctionType) - @mock.patch.object(agent_client.AgentClient, 'get_clean_steps', - autospec=True) - def test_get_clean_steps_custom_interface( - self, client_mock, list_ports_mock): - client_mock.return_value = { - 'command_result': self.clean_steps} - list_ports_mock.return_value = self.ports + self.assertIn(self.clean_steps['deploy'][0], response) + self.assertIn(self.clean_steps['deploy'][1], response) + self.assertIn(self.clean_steps['raid'][0], response) + def test_get_clean_steps_custom_interface(self): with task_manager.acquire( self.context, self.node.uuid, shared=False) as task: response = utils.agent_get_clean_steps(task, interface='raid') - client_mock.assert_called_once_with(mock.ANY, task.node, - self.ports) - self.assertEqual('1', task.node.driver_internal_info[ - 'hardware_manager_version']) - self.assertThat(response, matchers.HasLength(1)) - self.assertIn(self.clean_steps['clean_steps'][ - 'SpecificHardwareManager'][1], response) - - @mock.patch('ironic.objects.Port.list_by_node_id', - spec_set=types.FunctionType) - @mock.patch.object(agent_client.AgentClient, 'get_clean_steps', - autospec=True) - def test_get_clean_steps_override_priorities( - self, client_mock, list_ports_mock): - client_mock.return_value = { - 'command_result': self.clean_steps} - list_ports_mock.return_value = self.ports + self.assertEqual(self.clean_steps['raid'], response) + def test_get_clean_steps_override_priorities(self): with task_manager.acquire( self.context, self.node.uuid, shared=False) as task: new_priorities = {'create_configuration': 42} response = utils.agent_get_clean_steps( task, interface='raid', override_priorities=new_priorities) - client_mock.assert_called_once_with(mock.ANY, task.node, - self.ports) - self.assertEqual('1', task.node.driver_internal_info[ - 'hardware_manager_version']) self.assertEqual(42, response[0]['priority']) - @mock.patch('ironic.objects.Port.list_by_node_id', - spec_set=types.FunctionType) - @mock.patch.object(agent_client.AgentClient, 'get_clean_steps', - autospec=True) - def test_get_clean_steps_override_priorities_none( - self, client_mock, list_ports_mock): - client_mock.return_value = { - 'command_result': self.clean_steps} - list_ports_mock.return_value = self.ports - + def test_get_clean_steps_override_priorities_none(self): with task_manager.acquire( self.context, self.node.uuid, shared=False) as task: # this is simulating the default value of a configuration option new_priorities = {'create_configuration': None} response = utils.agent_get_clean_steps( task, interface='raid', override_priorities=new_priorities) - client_mock.assert_called_once_with(mock.ANY, task.node, - self.ports) - self.assertEqual('1', task.node.driver_internal_info[ - 'hardware_manager_version']) self.assertEqual(10, response[0]['priority']) - @mock.patch('ironic.objects.Port.list_by_node_id', - spec_set=types.FunctionType) - @mock.patch.object(agent_client.AgentClient, 'get_clean_steps', - autospec=True) - def test_get_clean_steps_missing_steps(self, client_mock, - list_ports_mock): - del self.clean_steps['clean_steps'] - client_mock.return_value = { - 'command_result': self.clean_steps} - list_ports_mock.return_value = self.ports - + def test_get_clean_steps_missing_steps(self): + info = self.node.driver_internal_info + del info['agent_cached_clean_steps'] + self.node.driver_internal_info = info + self.node.save() with task_manager.acquire( self.context, self.node.uuid, shared=False) as task: self.assertRaises(exception.NodeCleaningFailure, utils.agent_get_clean_steps, task) - client_mock.assert_called_once_with(mock.ANY, task.node, - self.ports) @mock.patch('ironic.objects.Port.list_by_node_id', spec_set=types.FunctionType) @@ -1668,10 +1601,10 @@ class AgentMethodsTestCase(db_base.DbTestCase): list_ports_mock.return_value = self.ports with task_manager.acquire( - self.context, self.node['uuid'], shared=False) as task: + self.context, self.node.uuid, shared=False) as task: response = utils.agent_execute_clean_step( task, - self.clean_steps['clean_steps']['GenericHardwareManager'][0]) + self.clean_steps['deploy'][0]) self.assertEqual(states.CLEANWAIT, response) @mock.patch('ironic.objects.Port.list_by_node_id', @@ -1684,10 +1617,10 @@ class AgentMethodsTestCase(db_base.DbTestCase): list_ports_mock.return_value = self.ports with task_manager.acquire( - self.context, self.node['uuid'], shared=False) as task: + self.context, self.node.uuid, shared=False) as task: response = utils.agent_execute_clean_step( task, - self.clean_steps['clean_steps']['GenericHardwareManager'][0]) + self.clean_steps['deploy'][0]) self.assertEqual(states.CLEANWAIT, response) @mock.patch('ironic.objects.Port.list_by_node_id', @@ -1701,16 +1634,16 @@ class AgentMethodsTestCase(db_base.DbTestCase): list_ports_mock.return_value = self.ports with task_manager.acquire( - self.context, self.node['uuid'], shared=False) as task: + self.context, self.node.uuid, shared=False) as task: response = utils.agent_execute_clean_step( task, - self.clean_steps['clean_steps']['GenericHardwareManager'][0]) + self.clean_steps['deploy'][0]) self.assertEqual(states.CLEANWAIT, response) def test_agent_add_clean_params(self): cfg.CONF.deploy.erase_devices_iterations = 2 with task_manager.acquire( - self.context, self.node['uuid'], shared=False) as task: + self.context, self.node.uuid, shared=False) as task: utils.agent_add_clean_params(task) self.assertEqual(task.node.driver_internal_info.get( 'agent_erase_devices_iterations'), 2)