diff --git a/dracclient/client.py b/dracclient/client.py index 2c352bd..140d555 100644 --- a/dracclient/client.py +++ b/dracclient/client.py @@ -19,6 +19,7 @@ import logging from dracclient import exceptions from dracclient.resources import bios +from dracclient.resources import job from dracclient import utils from dracclient import wsman @@ -41,6 +42,7 @@ class DRACClient(object): """ self.client = WSManClient(host, username, password, port, path, protocol) + self._job_mgmt = job.JobManagement(self.client) self._power_mgmt = bios.PowerManagement(self.client) def get_power_state(self): @@ -69,6 +71,98 @@ class DRACClient(object): """ self._power_mgmt.set_power_state(target_state) + def list_jobs(self, only_unfinished=False): + """Returns a list of jobs from the job queue + + :param only_unfinished: indicates whether only unfinished jobs should + be returned + :returns: a list of Job objects + :raises: WSManRequestFailure on request failures + :raises: WSManInvalidResponse when receiving invalid response + :raises: DRACOperationFailed on error reported back by the DRAC + interface + """ + return self._job_mgmt.list_jobs(only_unfinished) + + def get_job(self, job_id): + """Returns a job from the job queue + + :param job_id: id of the job + :returns: a Job object on successful query, None otherwise + :raises: WSManRequestFailure on request failures + :raises: WSManInvalidResponse when receiving invalid response + :raises: DRACOperationFailed on error reported back by the DRAC + interface + """ + return self._job_mgmt.get_job(job_id) + + def create_config_job(self, resource_uri, cim_creation_class_name, + cim_name, target, + cim_system_creation_class_name='DCIM_ComputerSystem', + cim_system_name='DCIM:ComputerSystem', + reboot=False): + """Creates a config job + + In CIM (Common Information Model), weak association is used to name an + instance of one class in the context of an instance of another class. + SystemName and SystemCreationClassName are the attributes of the + scoping system, while Name and CreationClassName are the attributes of + the instance of the class, on which the CreateTargetedConfigJob method + is invoked. + + :param: resource_uri: URI of resource to invoke + :param: cim_creation_class_name: creation class name of the CIM object + :param: cim_name: name of the CIM object + :param: target: target device + :param: cim_system_creation_class_name: creation class name of the + scoping system + :param: cim_system_name: name of the scoping system + :param: reboot: indicates whether a RebootJob should be also be + created or not + :returns: id of the created job + :raises: WSManRequestFailure on request failures + :raises: WSManInvalidResponse when receiving invalid response + :raises: DRACOperationFailed on error reported back by the DRAC + interface + :raises: DRACUnexpectedReturnValue on return value mismatch + """ + return self._job_mgmt.create_config_job( + resource_uri, cim_creation_class_name, cim_name, target, + cim_system_creation_class_name, cim_system_name, reboot) + + def delete_pending_config( + self, resource_uri, cim_creation_class_name, cim_name, target, + cim_system_creation_class_name='DCIM_ComputerSystem', + cim_system_name='DCIM:ComputerSystem'): + """Cancels pending configuration + + Configuration can only be canceled until a config job hasn't been + submitted. + + In CIM (Common Information Model), weak association is used to name an + instance of one class in the context of an instance of another class. + SystemName and SystemCreationClassName are the attributes of the + scoping system, while Name and CreationClassName are the attributes of + the instance of the class, on which the CreateTargetedConfigJob method + is invoked. + + :param: resource_uri: URI of resource to invoke + :param: cim_creation_class_name: creation class name of the CIM object + :param: cim_name: name of the CIM object + :param: target: target device + :param: cim_system_creation_class_name: creation class name of the + scoping system + :param: cim_system_name: name of the scoping system + :raises: WSManRequestFailure on request failures + :raises: WSManInvalidResponse when receiving invalid response + :raises: DRACOperationFailed on error reported back by the DRAC + interface + :raises: DRACUnexpectedReturnValue on return value mismatch + """ + self._job_mgmt.delete_pending_config( + resource_uri, cim_creation_class_name, cim_name, target, + cim_system_creation_class_name, cim_system_name) + class WSManClient(wsman.Client): """Wrapper for wsman.Client with return value checking""" diff --git a/dracclient/resources/job.py b/dracclient/resources/job.py new file mode 100644 index 0000000..c8f1785 --- /dev/null +++ b/dracclient/resources/job.py @@ -0,0 +1,192 @@ +# +# 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. + +import collections + +from dracclient.resources import uris +from dracclient import utils +from dracclient import wsman + +Job = collections.namedtuple('Job', ['id', 'name', 'start_time', 'until_time', + 'message', 'state', 'percent_complete']) + + +class JobManagement(object): + + def __init__(self, client): + """Creates JobManagement object + + :param client: an instance of WSManClient + """ + self.client = client + + def list_jobs(self, only_unfinished=False): + """Returns a list of jobs from the job queue + + :param only_unfinished: indicates whether only unfinished jobs should + be returned + :returns: a list of Job objects + :raises: WSManRequestFailure on request failures + :raises: WSManInvalidResponse when receiving invalid response + :raises: DRACOperationFailed on error reported back by the DRAC + interface + """ + + filter_query = None + if only_unfinished: + filter_query = ('select * from DCIM_LifecycleJob ' + 'where Name != "CLEARALL" and ' + 'JobStatus != "Reboot Completed" and ' + 'JobStatus != "Completed" and ' + 'JobStatus != "Completed with Errors" and ' + 'JobStatus != "Failed"') + + doc = self.client.enumerate(uris.DCIM_LifecycleJob, + filter_query=filter_query) + + drac_jobs = utils.find_xml(doc, 'DCIM_LifecycleJob', + uris.DCIM_LifecycleJob, find_all=True) + + return [self._parse_drac_job(drac_job) for drac_job in drac_jobs] + + def get_job(self, job_id): + """Returns a job from the job queue + + :param job_id: id of the job + :returns: a Job object on successful query, None otherwise + :raises: WSManRequestFailure on request failures + :raises: WSManInvalidResponse when receiving invalid response + :raises: DRACOperationFailed on error reported back by the DRAC + interface + """ + + filter_query = ('select * from DCIM_LifecycleJob where InstanceID="%s"' + % job_id) + + doc = self.client.enumerate(uris.DCIM_LifecycleJob, + filter_query=filter_query) + + drac_job = utils.find_xml(doc, 'DCIM_LifecycleJob', + uris.DCIM_LifecycleJob) + + if drac_job: + return self._parse_drac_job(drac_job) + + def create_config_job(self, resource_uri, cim_creation_class_name, + cim_name, target, + cim_system_creation_class_name='DCIM_ComputerSystem', + cim_system_name='DCIM:ComputerSystem', + reboot=False): + """Creates a config job + + In CIM (Common Information Model), weak association is used to name an + instance of one class in the context of an instance of another class. + SystemName and SystemCreationClassName are the attributes of the + scoping system, while Name and CreationClassName are the attributes of + the instance of the class, on which the CreateTargetedConfigJob method + is invoked. + + :param: resource_uri: URI of resource to invoke + :param: cim_creation_class_name: creation class name of the CIM object + :param: cim_name: name of the CIM object + :param: target: target device + :param: cim_system_creation_class_name: creation class name of the + scoping system + :param: cim_system_name: name of the scoping system + :param: reboot: indicates whether a RebootJob should be also be + created or not + :returns: id of the created job + :raises: WSManRequestFailure on request failures + :raises: WSManInvalidResponse when receiving invalid response + :raises: DRACOperationFailed on error reported back by the DRAC + interface + :raises: DRACUnexpectedReturnValue on return value mismatch + """ + + selectors = {'SystemCreationClassName': cim_system_creation_class_name, + 'SystemName': cim_system_name, + 'CreationClassName': cim_creation_class_name, + 'Name': cim_name} + + properties = {'Target': target, + 'ScheduledStartTime': 'TIME_NOW'} + + if reboot: + properties['RebootJobType'] = '3' + + doc = self.client.invoke(resource_uri, 'CreateTargetedConfigJob', + selectors, properties, + expected_return_value=utils.RET_CREATED) + + query = ('.//{%(namespace)s}%(item)s[@%(attribute_name)s=' + '"%(attribute_value)s"]' % + {'namespace': wsman.NS_WSMAN, 'item': 'Selector', + 'attribute_name': 'Name', + 'attribute_value': 'InstanceID'}) + job_id = doc.find(query).text + return job_id + + def delete_pending_config( + self, resource_uri, cim_creation_class_name, cim_name, target, + cim_system_creation_class_name='DCIM_ComputerSystem', + cim_system_name='DCIM:ComputerSystem'): + """Cancels pending configuration + + Configuration can only be canceled until a config job hasn't been + submitted. + + In CIM (Common Information Model), weak association is used to name an + instance of one class in the context of an instance of another class. + SystemName and SystemCreationClassName are the attributes of the + scoping system, while Name and CreationClassName are the attributes of + the instance of the class, on which the CreateTargetedConfigJob method + is invoked. + + :param: resource_uri: URI of resource to invoke + :param: cim_creation_class_name: creation class name of the CIM object + :param: cim_name: name of the CIM object + :param: target: target device + :param: cim_system_creation_class_name: creation class name of the + scoping system + :param: cim_system_name: name of the scoping system + :raises: WSManRequestFailure on request failures + :raises: WSManInvalidResponse when receiving invalid response + :raises: DRACOperationFailed on error reported back by the DRAC + interface + :raises: DRACUnexpectedReturnValue on return value mismatch + """ + + selectors = {'SystemCreationClassName': cim_system_creation_class_name, + 'SystemName': cim_system_name, + 'CreationClassName': cim_creation_class_name, + 'Name': cim_name} + + properties = {'Target': target} + + self.client.invoke(resource_uri, 'DeletePendingConfiguration', + selectors, properties, + expected_return_value=utils.RET_SUCCESS) + + def _parse_drac_job(self, drac_job): + return Job(id=self._get_job_attr(drac_job, 'InstanceID'), + name=self._get_job_attr(drac_job, 'Name'), + start_time=self._get_job_attr(drac_job, 'JobStartTime'), + until_time=self._get_job_attr(drac_job, 'JobUntilTime'), + message=self._get_job_attr(drac_job, 'Message'), + state=self._get_job_attr(drac_job, 'JobStatus'), + percent_complete=self._get_job_attr(drac_job, + 'PercentComplete')) + + def _get_job_attr(self, drac_job, attr_name): + return utils.get_wsman_resource_attr(drac_job, uris.DCIM_LifecycleJob, + attr_name) diff --git a/dracclient/resources/uris.py b/dracclient/resources/uris.py index 75f2486..c77b2b5 100644 --- a/dracclient/resources/uris.py +++ b/dracclient/resources/uris.py @@ -40,5 +40,8 @@ DCIM_ComputerSystem = ('http://schemas.dell.com/wbem/wscim/1/cim-schema/2' DCIM_LifecycleJob = ('http://schemas.dell.com/wbem/wscim/1/cim-schema/2/' 'DCIM_LifecycleJob') +DCIM_RAIDService = ('http://schemas.dell.com/wbem/wscim/1/cim-schema/2/' + 'DCIM_RAIDService') + DCIM_SystemView = ('http://schemas.dell.com/wbem/wscim/1/cim-schema/2/' 'DCIM_SystemView') diff --git a/dracclient/tests/test_client.py b/dracclient/tests/test_client.py index 4cff3c8..c451d32 100644 --- a/dracclient/tests/test_client.py +++ b/dracclient/tests/test_client.py @@ -11,13 +11,17 @@ # License for the specific language governing permissions and limitations # under the License. +import lxml.etree +import mock import requests_mock import dracclient.client from dracclient import exceptions +import dracclient.resources.job from dracclient.resources import uris from dracclient.tests import base from dracclient.tests import utils as test_utils +from dracclient import utils @requests_mock.Mocker() @@ -57,6 +61,185 @@ class ClientPowerManagementTestCase(base.BaseTest): self.drac_client.set_power_state, 'foo') +class ClientJobManagementTestCase(base.BaseTest): + + def setUp(self): + super(ClientJobManagementTestCase, self).setUp() + self.drac_client = dracclient.client.DRACClient( + **test_utils.FAKE_ENDPOINT) + + @requests_mock.Mocker() + def test_list_jobs(self, mock_requests): + mock_requests.post( + 'https://1.2.3.4:443/wsman', + text=test_utils.JobEnumerations[uris.DCIM_LifecycleJob]['ok']) + + jobs = self.drac_client.list_jobs() + + self.assertEqual(6, len(jobs)) + + @mock.patch.object(dracclient.client.WSManClient, 'enumerate', + spec_set=True, autospec=True) + def test_list_jobs_only_unfinished(self, mock_enumerate): + expected_filter_query = ('select * from DCIM_LifecycleJob ' + 'where Name != "CLEARALL" and ' + 'JobStatus != "Reboot Completed" and ' + 'JobStatus != "Completed" and ' + 'JobStatus != "Completed with Errors" and ' + 'JobStatus != "Failed"') + mock_enumerate.return_value = lxml.etree.fromstring( + test_utils.JobEnumerations[uris.DCIM_LifecycleJob]['ok']) + + self.drac_client.list_jobs(only_unfinished=True) + + mock_enumerate.assert_called_once_with( + mock.ANY, uris.DCIM_LifecycleJob, + filter_query=expected_filter_query) + + @mock.patch.object(dracclient.client.WSManClient, 'enumerate', + spec_set=True, autospec=True) + def test_get_job(self, mock_enumerate): + expected_filter_query = ('select * from DCIM_LifecycleJob' + ' where InstanceID="42"') + # NOTE: This is the first job in the xml. Filtering the job is the + # responsibility of the controller, so not testing it. + expected_job = dracclient.resources.job.Job(id='JID_CLEARALL', + name='CLEARALL', + start_time='TIME_NA', + until_time='TIME_NA', + message='NA', + state='Pending', + percent_complete='0') + mock_enumerate.return_value = lxml.etree.fromstring( + test_utils.JobEnumerations[uris.DCIM_LifecycleJob]['ok']) + + job = self.drac_client.get_job(42) + + mock_enumerate.assert_called_once_with( + mock.ANY, uris.DCIM_LifecycleJob, + filter_query=expected_filter_query) + self.assertEqual(expected_job, job) + + @mock.patch.object(dracclient.client.WSManClient, 'enumerate', + spec_set=True, autospec=True) + def test_get_job_not_found(self, mock_enumerate): + expected_filter_query = ('select * from DCIM_LifecycleJob' + ' where InstanceID="42"') + mock_enumerate.return_value = lxml.etree.fromstring( + test_utils.JobEnumerations[uris.DCIM_LifecycleJob]['not_found']) + + job = self.drac_client.get_job(42) + + mock_enumerate.assert_called_once_with( + mock.ANY, uris.DCIM_LifecycleJob, + filter_query=expected_filter_query) + self.assertIsNone(job) + + @mock.patch.object(dracclient.client.WSManClient, 'invoke', + spec_set=True, autospec=True) + def test_create_config_job(self, mock_invoke): + cim_creation_class_name = 'DCIM_BIOSService' + cim_name = 'DCIM:BIOSService' + target = 'BIOS.Setup.1-1' + expected_selectors = {'CreationClassName': cim_creation_class_name, + 'Name': cim_name, + 'SystemCreationClassName': 'DCIM_ComputerSystem', + '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']) + + job_id = self.drac_client.create_config_job( + uris.DCIM_BIOSService, cim_creation_class_name, cim_name, target) + + mock_invoke.assert_called_once_with( + mock.ANY, uris.DCIM_BIOSService, 'CreateTargetedConfigJob', + expected_selectors, expected_properties, + expected_return_value=utils.RET_CREATED) + self.assertEqual('JID_442507917525', job_id) + + @requests_mock.Mocker() + def test_create_config_job_failed(self, mock_requests): + cim_creation_class_name = 'DCIM_BIOSService' + cim_name = 'DCIM:BIOSService' + target = 'BIOS.Setup.1-1' + mock_requests.post( + 'https://1.2.3.4:443/wsman', + text=test_utils.JobInvocations[uris.DCIM_BIOSService][ + 'CreateTargetedConfigJob']['error']) + + self.assertRaises( + exceptions.DRACOperationFailed, self.drac_client.create_config_job, + uris.DCIM_BIOSService, cim_creation_class_name, cim_name, target) + + @mock.patch.object(dracclient.client.WSManClient, 'invoke', spec_set=True, + autospec=True) + def test_create_config_job_with_reboot(self, mock_invoke): + cim_creation_class_name = 'DCIM_BIOSService' + cim_name = 'DCIM:BIOSService' + target = 'BIOS.Setup.1-1' + expected_selectors = {'CreationClassName': cim_creation_class_name, + 'Name': cim_name, + 'SystemCreationClassName': 'DCIM_ComputerSystem', + 'SystemName': 'DCIM:ComputerSystem'} + expected_properties = {'Target': target, + 'RebootJobType': '3', + 'ScheduledStartTime': 'TIME_NOW'} + mock_invoke.return_value = lxml.etree.fromstring( + test_utils.JobInvocations[uris.DCIM_BIOSService][ + 'CreateTargetedConfigJob']['ok']) + + job_id = self.drac_client.create_config_job( + uris.DCIM_BIOSService, cim_creation_class_name, cim_name, target, + reboot=True) + + mock_invoke.assert_called_once_with( + mock.ANY, uris.DCIM_BIOSService, 'CreateTargetedConfigJob', + expected_selectors, expected_properties, + expected_return_value=utils.RET_CREATED) + self.assertEqual('JID_442507917525', job_id) + + @mock.patch.object(dracclient.client.WSManClient, 'invoke', spec_set=True, + autospec=True) + def test_delete_pending_config(self, mock_invoke): + cim_creation_class_name = 'DCIM_BIOSService' + cim_name = 'DCIM:BIOSService' + target = 'BIOS.Setup.1-1' + expected_selectors = {'CreationClassName': cim_creation_class_name, + 'Name': cim_name, + 'SystemCreationClassName': 'DCIM_ComputerSystem', + 'SystemName': 'DCIM:ComputerSystem'} + expected_properties = {'Target': target} + mock_invoke.return_value = lxml.etree.fromstring( + test_utils.JobInvocations[uris.DCIM_BIOSService][ + 'DeletePendingConfiguration']['ok']) + + self.drac_client.delete_pending_config( + uris.DCIM_BIOSService, cim_creation_class_name, cim_name, target) + + mock_invoke.assert_called_once_with( + mock.ANY, uris.DCIM_BIOSService, 'DeletePendingConfiguration', + expected_selectors, expected_properties, + expected_return_value=utils.RET_SUCCESS) + + @requests_mock.Mocker() + def test_delete_pending_config_failed(self, mock_requests): + cim_creation_class_name = 'DCIM_BIOSService' + cim_name = 'DCIM:BIOSService' + target = 'BIOS.Setup.1-1' + mock_requests.post( + 'https://1.2.3.4:443/wsman', + text=test_utils.JobInvocations[uris.DCIM_BIOSService][ + 'DeletePendingConfiguration']['error']) + + self.assertRaises( + exceptions.DRACOperationFailed, + self.drac_client.delete_pending_config, uris.DCIM_BIOSService, + cim_creation_class_name, cim_name, target) + + @requests_mock.Mocker() class WSManClientTestCase(base.BaseTest): diff --git a/dracclient/tests/utils.py b/dracclient/tests/utils.py index 4c0c778..146e699 100644 --- a/dracclient/tests/utils.py +++ b/dracclient/tests/utils.py @@ -59,3 +59,27 @@ BIOSInvocations = { }, }, } + +JobEnumerations = { + uris.DCIM_LifecycleJob: { + 'ok': load_wsman_xml('lifecycle_job-enum-ok'), + 'not_found': load_wsman_xml('lifecycle_job-enum-not_found'), + }, +} + +JobInvocations = { + uris.DCIM_BIOSService: { + 'CreateTargetedConfigJob': { + 'ok': load_wsman_xml( + 'bios_service-invoke-create_targeted_config_job-ok'), + 'error': load_wsman_xml( + 'bios_service-invoke-create_targeted_config_job-error'), + }, + 'DeletePendingConfiguration': { + 'ok': load_wsman_xml( + 'bios_service-invoke-delete_pending_configuration-ok'), + 'error': load_wsman_xml( + 'bios_service-invoke-delete_pending_configuration-error'), + }, + } +} diff --git a/dracclient/tests/wsman_mocks/bios_service-invoke-create_targeted_config_job-error.xml b/dracclient/tests/wsman_mocks/bios_service-invoke-create_targeted_config_job-error.xml new file mode 100644 index 0000000..4b034c8 --- /dev/null +++ b/dracclient/tests/wsman_mocks/bios_service-invoke-create_targeted_config_job-error.xml @@ -0,0 +1,17 @@ + + + http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous + http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_BIOSService/CreateTargetedConfigJobResponse + uuid:80cf5e1b-b109-4ef5-87c8-5b03ce6ba117 + uuid:e57fa514-2189-1189-8ec1-a36fc6fe83b0 + + + + Configuration job already created, cannot create another config job on specified target until existing job is completed or is cancelled + BIOS007 + 2 + + + \ No newline at end of file diff --git a/dracclient/tests/wsman_mocks/bios_service-invoke-create_targeted_config_job-ok.xml b/dracclient/tests/wsman_mocks/bios_service-invoke-create_targeted_config_job-ok.xml new file mode 100644 index 0000000..c47ba46 --- /dev/null +++ b/dracclient/tests/wsman_mocks/bios_service-invoke-create_targeted_config_job-ok.xml @@ -0,0 +1,28 @@ + + + http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous + http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_BIOSService/CreateTargetedConfigJobResponse + uuid:fc2fdae5-6ac2-4338-9b2e-e69b813af829 + uuid:d7d89957-2189-1189-8ec0-a36fc6fe83b0 + + + + + + http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous + + http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_LifecycleJob + + JID_442507917525 + root/dcim + + + + + 4096 + + + \ No newline at end of file diff --git a/dracclient/tests/wsman_mocks/bios_service-invoke-delete_pending_configuration-error.xml b/dracclient/tests/wsman_mocks/bios_service-invoke-delete_pending_configuration-error.xml new file mode 100644 index 0000000..4b72535 --- /dev/null +++ b/dracclient/tests/wsman_mocks/bios_service-invoke-delete_pending_configuration-error.xml @@ -0,0 +1,17 @@ + + + http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous + http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_BIOSService/DeletePendingConfigurationResponse + uuid:6bd49922-f63b-44d9-abf7-b902f08ec932 + uuid:87a16ea4-2189-1189-8eb5-a36fc6fe83b0 + + + + Configuration job already created, pending data cannot be deleted + BIOS011 + 2 + + + \ No newline at end of file diff --git a/dracclient/tests/wsman_mocks/bios_service-invoke-delete_pending_configuration-ok.xml b/dracclient/tests/wsman_mocks/bios_service-invoke-delete_pending_configuration-ok.xml new file mode 100644 index 0000000..b8c09e0 --- /dev/null +++ b/dracclient/tests/wsman_mocks/bios_service-invoke-delete_pending_configuration-ok.xml @@ -0,0 +1,15 @@ + + + http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous + http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_BIOSService/DeletePendingConfigurationResponse + uuid:33b4948b-79e6-42ff-8670-19fbb25702c8 + uuid:d578f646-2189-1189-8ebe-a36fc6fe83b0 + + + + The command was successful. + BIOS001 + 0 + + + \ No newline at end of file diff --git a/dracclient/tests/wsman_mocks/lifecycle_job-enum-not_found.xml b/dracclient/tests/wsman_mocks/lifecycle_job-enum-not_found.xml new file mode 100644 index 0000000..4272a23 --- /dev/null +++ b/dracclient/tests/wsman_mocks/lifecycle_job-enum-not_found.xml @@ -0,0 +1,19 @@ + + + http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous + http://schemas.xmlsoap.org/ws/2004/09/enumeration/EnumerateResponse + uuid:2453cc4f-104c-104c-8002-fd0aa2bdb228 + uuid:28c00f71-1051-1051-8003-fcc71555dbe0 + + + + + + + + + \ No newline at end of file diff --git a/dracclient/tests/wsman_mocks/lifecycle_job-enum-ok.xml b/dracclient/tests/wsman_mocks/lifecycle_job-enum-ok.xml new file mode 100644 index 0000000..f727681 --- /dev/null +++ b/dracclient/tests/wsman_mocks/lifecycle_job-enum-ok.xml @@ -0,0 +1,76 @@ + + + http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous + http://schemas.xmlsoap.org/ws/2004/09/enumeration/EnumerateResponse + uuid:f3ee3b82-210b-110b-8002-fd0aa2bdb228 + uuid:f4327d65-210b-110b-836c-0581b4d9bed4 + + + + + + JID_CLEARALL + TIME_NA + Pending + TIME_NA + NA + NA + CLEARALL + 0 + + + JID_001436912645 + 00000101000000 + Completed + TIME_NA + Job completed successfully + PR19 + ConfigBIOS:BIOS.Setup.1-1 + 100 + + + JID_001436960861 + 00000101000000 + Completed + TIME_NA + Job completed successfully + PR19 + ConfigBIOS:BIOS.Setup.1-1 + 100 + + + JID_001436966148 + 00000101000000 + Completed + TIME_NA + Job completed successfully + PR19 + ConfigBIOS:BIOS.Setup.1-1 + 100 + + + JID_001436980372 + 00000101000000 + Completed + TIME_NA + Job completed successfully + PR19 + ConfigBIOS:BIOS.Setup.1-1 + 100 + + + JID_001436981582 + 00000101000000 + Running + TIME_NA + Job in progress + PR20 + ConfigBIOS:BIOS.Setup.1-1 + 34 + + + + + + + diff --git a/dracclient/utils.py b/dracclient/utils.py index 9d16f76..39ffbf6 100644 --- a/dracclient/utils.py +++ b/dracclient/utils.py @@ -40,3 +40,9 @@ def find_xml(doc, item, namespace, find_all=False): if find_all: return doc.findall(query) return doc.find(query) + + +def get_wsman_resource_attr(doc, resource_uri, attr_name): + """Find an attribute of a resource in an ElementTree object""" + + return find_xml(doc, attr_name, resource_uri).text.strip()