From 96fa6965db4d31afbff060d15cc34faf1d9cc88f Mon Sep 17 00:00:00 2001 From: Mark Beierl Date: Wed, 11 Jul 2018 13:38:55 -0400 Subject: [PATCH] Delete Job API Adds the ability to delete jobs from the iDRAC job queue. Change-Id: I0e34cc850b968533782af45ef6e61206a3f04949 Co-Authored-By: Richard G. Pioso --- dracclient/client.py | 21 +++ dracclient/resources/job.py | 37 ++++++ dracclient/tests/test_job.py | 124 ++++++++++++++++++ dracclient/tests/utils.py | 12 ++ .../job_service-delete-job-id-error.xml | 21 +++ .../job_service-delete-job-id-ok.xml | 21 +++ 6 files changed, 236 insertions(+) create mode 100644 dracclient/tests/wsman_mocks/job_service-delete-job-id-error.xml create mode 100644 dracclient/tests/wsman_mocks/job_service-delete-job-id-ok.xml diff --git a/dracclient/client.py b/dracclient/client.py index f3ff34f..3b42d80 100644 --- a/dracclient/client.py +++ b/dracclient/client.py @@ -339,6 +339,27 @@ class DRACClient(object): """ return self._job_mgmt.get_job(job_id) + def delete_jobs(self, job_ids=['JID_CLEARALL']): + """Deletes the given jobs, or all jobs if none specified + + :param job_ids: a list of job ids to delete. Clearing all the + jobs may be accomplished using the keyword JID_CLEARALL + as the job_id, or JID_CLEARALL_FORCE if a job is in + Scheduled state and there is another job for the same + component in Completed or Failed state, + (http://en.community.dell.com/techcenter/extras/m/white_papers/20444501/download) + Deletion of each job id will be attempted, even if there + are errors in deleting any in the list. + :raises: WSManRequestFailure on request failures + :raises: WSManInvalidResponse when receiving invalid response + :raises: DRACOperationFailed on error reported back by the iDRAC + interface. There will be one message for each job_id + that had a failure in the exception. + :raises: DRACUnexpectedReturnValue on non-success + """ + + return self._job_mgmt.delete_jobs(job_ids) + def create_config_job(self, resource_uri, cim_creation_class_name, diff --git a/dracclient/resources/job.py b/dracclient/resources/job.py index 875775e..f161fa8 100644 --- a/dracclient/resources/job.py +++ b/dracclient/resources/job.py @@ -249,6 +249,43 @@ class JobManagement(object): job_id = doc.find(query).text return job_id + def delete_jobs(self, job_ids=['JID_CLEARALL']): + """Deletes the given jobs, or all jobs if none specified + + :raises: WSManRequestFailure on request failures + :raises: WSManInvalidResponse when receiving invalid response + :raises: DRACOperationFailed on error reported back by the iDRAC + interface + :raises: DRACUnexpectedReturnValue on non-success + """ + + selectors = {'SystemCreationClassName': 'DCIM_ComputerSystem', + 'SystemName': 'idrac', + 'CreationClassName': 'DCIM_JobService', + 'Name': 'JobService'} + + if job_ids is None: + return + + messages = [] + + for job_id in job_ids: + properties = {'JobID': job_id} + + try: + self.client.invoke( + uris.DCIM_JobService, + 'DeleteJobQueue', + selectors, + properties, + expected_return_value=utils.RET_SUCCESS) + except exceptions.DRACOperationFailed as dof: + for message in dof.args: + messages.append(message + " " + job_id) + + if len(messages): + raise exceptions.DRACOperationFailed(drac_messages=messages) + def delete_pending_config( self, resource_uri, cim_creation_class_name, cim_name, target, cim_system_creation_class_name='DCIM_ComputerSystem', diff --git a/dracclient/tests/test_job.py b/dracclient/tests/test_job.py index a4456bb..4dcbc56 100644 --- a/dracclient/tests/test_job.py +++ b/dracclient/tests/test_job.py @@ -103,6 +103,129 @@ class ClientJobManagementTestCase(base.BaseTest): filter_query=expected_filter_query) self.assertIsNone(job) + @mock.patch.object(dracclient.client.WSManClient, 'invoke', + spec_set=True, autospec=True) + def test_delete_jobs_all(self, mock_invoke): + expected_selectors = {'SystemCreationClassName': 'DCIM_ComputerSystem', + 'SystemName': 'idrac', + 'CreationClassName': 'DCIM_JobService', + 'Name': 'JobService'} + expected_properties = {'JobID': 'JID_CLEARALL'} + + self.drac_client.delete_jobs() + + mock_invoke.assert_called_once_with( + mock.ANY, uris.DCIM_JobService, 'DeleteJobQueue', + expected_selectors, expected_properties, + expected_return_value=utils.RET_SUCCESS) + + @mock.patch.object(dracclient.client.WSManClient, 'invoke', + spec_set=True, autospec=True) + def test_delete_jobs_force(self, mock_invoke): + expected_selectors = {'SystemCreationClassName': 'DCIM_ComputerSystem', + 'SystemName': 'idrac', + 'CreationClassName': 'DCIM_JobService', + 'Name': 'JobService'} + expected_properties = {'JobID': 'JID_CLEARALL_FORCE'} + + self.drac_client.delete_jobs(['JID_CLEARALL_FORCE']) + + mock_invoke.assert_called_once_with( + mock.ANY, uris.DCIM_JobService, 'DeleteJobQueue', + expected_selectors, expected_properties, + expected_return_value=utils.RET_SUCCESS) + + @mock.patch.object(dracclient.client.WSManClient, 'invoke', + spec_set=True, autospec=True) + def test_delete_jobs_one(self, mock_invoke): + expected_selectors = {'SystemCreationClassName': 'DCIM_ComputerSystem', + 'SystemName': 'idrac', + 'CreationClassName': 'DCIM_JobService', + 'Name': 'JobService'} + expected_properties = {'JobID': 'JID_442507917525'} + + self.drac_client.delete_jobs(['JID_442507917525']) + + mock_invoke.assert_called_once_with( + mock.ANY, uris.DCIM_JobService, 'DeleteJobQueue', + expected_selectors, expected_properties, + expected_return_value=utils.RET_SUCCESS) + + @mock.patch.object(dracclient.client.WSManClient, 'invoke', + spec_set=True, autospec=True) + def test_delete_jobs_multi(self, mock_invoke): + expected_selectors = {'SystemCreationClassName': 'DCIM_ComputerSystem', + 'SystemName': 'idrac', + 'CreationClassName': 'DCIM_JobService', + 'Name': 'JobService'} + + self.drac_client.delete_jobs(['JID_442507917525', + 'JID_442507917526']) + + calls_expected = [ + mock.call(mock.ANY, + uris.DCIM_JobService, + 'DeleteJobQueue', + expected_selectors, + {'JobID': 'JID_442507917525'}, + expected_return_value=utils.RET_SUCCESS), + mock.call(mock.ANY, + uris.DCIM_JobService, + 'DeleteJobQueue', + expected_selectors, + {'JobID': 'JID_442507917526'}, + expected_return_value=utils.RET_SUCCESS)] + mock_invoke.assert_has_calls(calls_expected) + + @mock.patch.object(dracclient.client.WSManClient, 'invoke', + spec_set=True, autospec=True) + def test_delete_jobs_none(self, mock_invoke): + self.drac_client.delete_jobs(None) + self.assertFalse(mock_invoke.called) + + @mock.patch.object(dracclient.client.WSManClient, 'invoke', + spec_set=True, autospec=True) + def test_delete_jobs_empty_list(self, mock_invoke): + self.drac_client.delete_jobs([]) + self.assertFalse(mock_invoke.called) + + @requests_mock.Mocker() + @mock.patch.object(dracclient.client.WSManClient, + 'wait_until_idrac_is_ready', spec_set=True, + autospec=True) + def test_delete_job_not_found( + self, mock_requests, + mock_wait_until_idrac_is_ready): + mock_requests.post( + 'https://1.2.3.4:443/wsman', + text=test_utils.JobService[uris.DCIM_JobService][ + 'DeleteJobQueue']['error']) + self.assertRaises( + exceptions.DRACOperationFailed, + self.drac_client.delete_jobs, + ['JID_1234']) + + @requests_mock.Mocker() + @mock.patch.object(dracclient.client.WSManClient, + 'wait_until_idrac_is_ready', spec_set=True, + autospec=True) + def test_delete_some_jobs_not_found( + self, mock_requests, + mock_wait_until_idrac_is_ready): + mock_requests.post( + 'https://1.2.3.4:443/wsman', + [{'text': test_utils.JobService[uris.DCIM_JobService][ + 'DeleteJobQueue']['error']}, + {'text': test_utils.JobService[uris.DCIM_JobService][ + 'DeleteJobQueue']['ok']}]) + + self.assertRaises( + exceptions.DRACOperationFailed, + self.drac_client.delete_jobs, + ['JID_1234', 'JID_442507917525']) + + self.assertEqual(mock_requests.call_count, 2) + @mock.patch.object(dracclient.client.WSManClient, 'invoke', spec_set=True, autospec=True) def test_create_config_job(self, mock_invoke): @@ -115,6 +238,7 @@ class ClientJobManagementTestCase(base.BaseTest): 'SystemName': 'DCIM:ComputerSystem'} expected_properties = {'Target': target, 'ScheduledStartTime': 'TIME_NOW'} + mock_invoke.return_value = lxml.etree.fromstring( test_utils.JobInvocations[uris.DCIM_BIOSService][ 'CreateTargetedConfigJob']['ok']) diff --git a/dracclient/tests/utils.py b/dracclient/tests/utils.py index 8eef943..c23efad 100644 --- a/dracclient/tests/utils.py +++ b/dracclient/tests/utils.py @@ -34,6 +34,7 @@ def load_wsman_xml(name): return xml_body + WSManEnumerations = { 'context': [ load_wsman_xml('wsman-enum_context-1'), @@ -135,6 +136,17 @@ JobInvocations = { } } +JobService = { + uris.DCIM_JobService: { + 'DeleteJobQueue': { + 'ok': load_wsman_xml( + 'job_service-delete-job-id-ok'), + 'error': load_wsman_xml( + 'job_service-delete-job-id-error'), + } + } +} + iDracCardEnumerations = { uris.DCIM_iDRACCardEnumeration: { 'ok': load_wsman_xml('idraccard_enumeration-enum-ok') diff --git a/dracclient/tests/wsman_mocks/job_service-delete-job-id-error.xml b/dracclient/tests/wsman_mocks/job_service-delete-job-id-error.xml new file mode 100644 index 0000000..1723f51 --- /dev/null +++ b/dracclient/tests/wsman_mocks/job_service-delete-job-id-error.xml @@ -0,0 +1,21 @@ + + + http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous + + http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_JobService/DeleteJobQueueResponse + + uuid:bd334a80-bd6f-4fc9-88cc-9c9cb79d41e8 + + uuid:ce21f862-70ac-10ac-b40d-64b5b4da6618 + + + + + Invalid Job ID + SUP011 + 2 + + + \ No newline at end of file diff --git a/dracclient/tests/wsman_mocks/job_service-delete-job-id-ok.xml b/dracclient/tests/wsman_mocks/job_service-delete-job-id-ok.xml new file mode 100644 index 0000000..319225a --- /dev/null +++ b/dracclient/tests/wsman_mocks/job_service-delete-job-id-ok.xml @@ -0,0 +1,21 @@ + + + http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous + + http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_JobService/DeleteJobQueueResponse + + uuid:5f1b62ee-19d4-4679-b083-477086856c79 + + uuid:07b2e831-70ae-10ae-b56f-64b5b4da6618 + + + + + The specified job was deleted + SUP020 + 0 + + + \ No newline at end of file