From 4be3fcef26e63aafcefa49c8e8611bb405d0e372 Mon Sep 17 00:00:00 2001 From: Pradip Kadam <pradip.kadam001@gmail.com> Date: Thu, 19 Sep 2019 09:39:40 -0400 Subject: [PATCH] DRAC: Fix a bug for clear_job_queue clean step with non-BIOS pending job If we create pending non-bios config job(E.g create/delete virtual disk) with "At next reboot" option and execute "clear_job_queue" clean step, then that job gets deleted after job execution instead of getting deleted before job execution. Change-Id: Ibeae18798a25e33fa070f1120ce0cc0981f66850 Story: #2006580 Task: #36695 --- ironic/drivers/modules/drac/management.py | 29 ++-- .../drivers/modules/drac/test_management.py | 139 ++++++++++++++++-- ...n_bios_job_execution-4b22e168ac915f4f.yaml | 10 ++ 3 files changed, 156 insertions(+), 22 deletions(-) create mode 100644 releasenotes/notes/fix_pending_non_bios_job_execution-4b22e168ac915f4f.yaml diff --git a/ironic/drivers/modules/drac/management.py b/ironic/drivers/modules/drac/management.py index 37f9ca846c..68b09ecabb 100644 --- a/ironic/drivers/modules/drac/management.py +++ b/ironic/drivers/modules/drac/management.py @@ -81,8 +81,8 @@ _NON_PERSISTENT_BOOT_MODE = 'OneTime' # Clear job id's constant _CLEAR_JOB_IDS = 'JID_CLEARALL_FORCE' -# BIOS pending job constant -_BIOS_JOB_NAME = 'BIOS.Setup.1-1' +# Clean steps constant +_CLEAR_JOBS_CLEAN_STEPS = ['clear_job_queue', 'known_good_state'] def _get_boot_device(node, drac_boot_devices=None): @@ -190,19 +190,24 @@ def set_boot_device(node, device, persistent=False): Default: False. :raises: DracOperationError on an error from python-dracclient. """ - client = drac_common.get_drac_client(node) - # If pending BIOS job found in job queue, we need to clear that job - # before executing cleaning step of management interface. + # If pending BIOS job or pending non-BIOS job found in job queue, + # we need to clear that jobs before executing clear_job_queue or + # known_good_state clean step of management interface. # Otherwise, pending BIOS config job can cause creating new config jobs - # to fail. - unfinished_jobs = drac_job.list_unfinished_jobs(node) - if unfinished_jobs: - unfinished_bios_jobs = [job.id for job in unfinished_jobs if - _BIOS_JOB_NAME in job.name] - if unfinished_bios_jobs: - client.delete_jobs(job_ids=unfinished_bios_jobs) + # to fail and pending non-BIOS job can execute on reboot the node. + validate_job_queue = True + if node.driver_internal_info.get("clean_steps"): + if node.driver_internal_info.get("clean_steps")[0].get( + 'step') in _CLEAR_JOBS_CLEAN_STEPS: + unfinished_jobs = drac_job.list_unfinished_jobs(node) + if unfinished_jobs: + validate_job_queue = False + client.delete_jobs(job_ids=[job.id for job in unfinished_jobs]) + + if validate_job_queue: + drac_job.validate_job_queue(node) try: drac_boot_devices = client.list_boot_devices() diff --git a/ironic/tests/unit/drivers/modules/drac/test_management.py b/ironic/tests/unit/drivers/modules/drac/test_management.py index c16662e3c4..74f2ea3728 100644 --- a/ironic/tests/unit/drivers/modules/drac/test_management.py +++ b/ironic/tests/unit/drivers/modules/drac/test_management.py @@ -250,7 +250,10 @@ class DracManagementInternalMethodsTestCase(test_utils.BaseDracTest): autospec=True) @mock.patch.object(drac_job, 'list_unfinished_jobs', spec_set=True, autospec=True) - def test_set_boot_device(self, mock_list_unfinished_jobs, + @mock.patch.object(drac_job, 'validate_job_queue', spec_set=True, + autospec=True) + def test_set_boot_device(self, mock_validate_job_queue, + mock_list_unfinished_jobs, mock__get_boot_device, mock__get_next_persistent_boot_mode, mock_get_drac_client): @@ -267,11 +270,14 @@ class DracManagementInternalMethodsTestCase(test_utils.BaseDracTest): 'persistent': True} mock__get_boot_device.return_value = boot_device mock__get_next_persistent_boot_mode.return_value = 'IPL' + self.node.driver_internal_info['clean_steps'] = [] boot_device = drac_mgmt.set_boot_device( self.node, ironic.common.boot_devices.PXE, persistent=False) - mock_list_unfinished_jobs.assert_called_once_with(self.node) + self.assertEqual(0, mock_list_unfinished_jobs.call_count) + self.assertEqual(0, mock_client.delete_jobs.call_count) + mock_validate_job_queue.assert_called_once_with(self.node) mock_client.change_boot_device_order.assert_called_once_with( 'OneTime', 'BIOS.Setup.1-1#BootSeq#NIC.Embedded.1-1-1') self.assertEqual(0, mock_client.set_bios_settings.call_count) @@ -495,15 +501,18 @@ class DracManagementInternalMethodsTestCase(test_utils.BaseDracTest): self.assertEqual(0, mock_client.set_bios_settings.call_count) self.assertEqual(0, mock_client.commit_pending_bios_changes.call_count) + @mock.patch.object(drac_job, 'validate_job_queue', spec_set=True, + autospec=True) @mock.patch.object(drac_job, 'list_unfinished_jobs', spec_set=True, autospec=True) @mock.patch.object(drac_mgmt, '_get_boot_device', spec_set=True, autospec=True) @mock.patch.object(drac_mgmt, '_get_next_persistent_boot_mode', spec_set=True, autospec=True) - def test_set_boot_device_with_list_unfinished_jobs( + def test_set_boot_device_with_list_unfinished_jobs_without_clean_step( self, mock__get_next_persistent_boot_mode, mock__get_boot_device, - mock_list_unfinished_jobs, mock_get_drac_client): + mock_list_unfinished_jobs, mock_validate_job_queue, + mock_get_drac_client): mock_client = mock.Mock() mock_get_drac_client.return_value = mock_client @@ -525,21 +534,27 @@ class DracManagementInternalMethodsTestCase(test_utils.BaseDracTest): mock__get_boot_device.return_value = boot_device mock__get_next_persistent_boot_mode.return_value = 'IPL' + self.node.driver_internal_info['clean_steps'] = [] + drac_mgmt.set_boot_device(self.node, ironic.common.boot_devices.DISK, persistent=True) - mock_list_unfinished_jobs.assert_called_once_with(self.node) - mock_client.delete_jobs.assert_called_once_with( - job_ids=['JID_602553293345']) + self.assertEqual(0, mock_list_unfinished_jobs.call_count) + self.assertEqual(0, mock_client.delete_jobs.call_count) + mock_validate_job_queue.assert_called_once_with(self.node) + + @mock.patch.object(drac_job, 'validate_job_queue', spec_set=True, + autospec=True) @mock.patch.object(drac_job, 'list_unfinished_jobs', spec_set=True, autospec=True) @mock.patch.object(drac_mgmt, '_get_boot_device', spec_set=True, autospec=True) @mock.patch.object(drac_mgmt, '_get_next_persistent_boot_mode', spec_set=True, autospec=True) - def test_set_boot_device_with_multiple_unfinished_jobs( + def test_set_boot_device_with_multiple_unfinished_jobs_without_clean_step( self, mock__get_next_persistent_boot_mode, mock__get_boot_device, - mock_list_unfinished_jobs, mock_get_drac_client): + mock_list_unfinished_jobs, mock_validate_job_queue, + mock_get_drac_client): mock_client = mock.Mock() mock_get_drac_client.return_value = mock_client @@ -571,11 +586,115 @@ class DracManagementInternalMethodsTestCase(test_utils.BaseDracTest): mock__get_boot_device.return_value = boot_device mock__get_next_persistent_boot_mode.return_value = 'IPL' + self.node.driver_internal_info['clean_steps'] = [] + drac_mgmt.set_boot_device(self.node, ironic.common.boot_devices.DISK, + persistent=True) + self.assertEqual(0, mock_list_unfinished_jobs.call_count) + self.assertEqual(0, mock_client.delete_jobs.call_count) + + mock_validate_job_queue.assert_called_once_with(self.node) + + @mock.patch.object(drac_mgmt, '_get_next_persistent_boot_mode', + spec_set=True, autospec=True) + @mock.patch.object(drac_mgmt, '_get_boot_device', spec_set=True, + autospec=True) + @mock.patch.object(drac_job, 'list_unfinished_jobs', spec_set=True, + autospec=True) + @mock.patch.object(drac_job, 'validate_job_queue', spec_set=True, + autospec=True) + def test_set_boot_device_with_list_unfinished_jobs_with_clean_step( + self, mock_validate_job_queue, + mock_list_unfinished_jobs, + mock__get_boot_device, + mock__get_next_persistent_boot_mode, + mock_get_drac_client): + mock_client = mock.Mock() + mock_get_drac_client.return_value = mock_client + mock_client.list_boot_devices.return_value = self.boot_devices['IPL'] + + boot_device = {'boot_device': ironic.common.boot_devices.DISK, + 'persistent': True} + mock__get_boot_device.return_value = boot_device + mock__get_next_persistent_boot_mode.return_value = 'IPL' + + mock_job = mock.Mock() + mock_job.status = "Scheduled" + mock_client.get_job.return_value = mock_job + + bios_job_dict = { + 'id': 'JID_602553293345', + 'name': 'ConfigBIOS:BIOS.Setup.1-1', + 'start_time': 'TIME_NOW', + 'until_time': 'TIME_NA', + 'message': 'Task successfully scheduled.', + 'status': 'Scheduled', + 'percent_complete': 0} + bios_job = test_utils.make_job(bios_job_dict) + mock_list_unfinished_jobs.return_value = [bios_job] + + self.node.driver_internal_info['clean_steps'] = [{ + u'interface': u'management', u'step': u'clear_job_queue'}] + boot_device = drac_mgmt.set_boot_device( + self.node, ironic.common.boot_devices.PXE, persistent=False) + mock_list_unfinished_jobs.assert_called_once_with(self.node) + mock_client.delete_jobs.assert_called_once_with( + job_ids=['JID_602553293345']) + + self.assertEqual(0, mock_validate_job_queue.call_count) + + @mock.patch.object(drac_job, 'validate_job_queue', spec_set=True, + autospec=True) + @mock.patch.object(drac_job, 'list_unfinished_jobs', spec_set=True, + autospec=True) + @mock.patch.object(drac_mgmt, '_get_boot_device', spec_set=True, + autospec=True) + @mock.patch.object(drac_mgmt, '_get_next_persistent_boot_mode', + spec_set=True, autospec=True) + def test_set_boot_device_with_multiple_unfinished_jobs_with_clean_step( + self, mock__get_next_persistent_boot_mode, mock__get_boot_device, + mock_list_unfinished_jobs, mock_validate_job_queue, + mock_get_drac_client): + mock_client = mock.Mock() + mock_get_drac_client.return_value = mock_client + + job_dict = { + 'id': 'JID_602553293345', + 'name': 'Config:RAID:RAID.Integrated.1-1', + 'start_time': 'TIME_NOW', + 'until_time': 'TIME_NA', + 'message': 'Task successfully scheduled.', + 'status': 'Scheduled', + 'percent_complete': 0} + job = test_utils.make_job(job_dict) + + bios_job_dict = { + 'id': 'JID_602553293346', + 'name': 'ConfigBIOS:BIOS.Setup.1-1', + 'start_time': 'TIME_NOW', + 'until_time': 'TIME_NA', + 'message': 'Task successfully scheduled.', + 'status': 'Scheduled', + 'percent_complete': 0} + bios_job = test_utils.make_job(bios_job_dict) + + mock_list_unfinished_jobs.return_value = [job, bios_job] + mock_client.list_boot_devices.return_value = self.boot_devices['IPL'] + boot_device = {'boot_device': ironic.common.boot_devices.DISK, + 'persistent': True} + + mock__get_boot_device.return_value = boot_device + mock__get_next_persistent_boot_mode.return_value = 'IPL' + + self.node.driver_internal_info['clean_steps'] = [{ + u'interface': u'management', u'step': u'clear_job_queue'}] + drac_mgmt.set_boot_device(self.node, ironic.common.boot_devices.DISK, persistent=True) mock_list_unfinished_jobs.assert_called_once_with(self.node) mock_client.delete_jobs.assert_called_once_with( - job_ids=['JID_602553293346']) + job_ids=['JID_602553293345', 'JID_602553293346']) + + self.assertEqual(0, mock_validate_job_queue.call_count) @mock.patch.object(drac_common, 'get_drac_client', spec_set=True, diff --git a/releasenotes/notes/fix_pending_non_bios_job_execution-4b22e168ac915f4f.yaml b/releasenotes/notes/fix_pending_non_bios_job_execution-4b22e168ac915f4f.yaml new file mode 100644 index 0000000000..1722c263e6 --- /dev/null +++ b/releasenotes/notes/fix_pending_non_bios_job_execution-4b22e168ac915f4f.yaml @@ -0,0 +1,10 @@ +--- +fixes: + - | + Fixes a bug in the ``idrac`` hardware type where executing + the ``clear_job_queue`` clean step, pending non-BIOS config jobs + (E.g. create/delete virtual disk) were not being deleted before + job execution. + + See bug `2006580 https://storyboard.openstack.org/#!/story/2006580` + for details