From b327b0811f412bf022863c01fcadf95759805998 Mon Sep 17 00:00:00 2001 From: Christopher Dearborn Date: Fri, 17 Nov 2017 15:41:29 -0500 Subject: [PATCH] Add ability to set iDRAC card settings This patch adds the ability to set iDRAC card settings. Change-Id: I117e5ed0af1469dc316a30abcfa384d3c773ed1f --- dracclient/client.py | 88 ++++++- dracclient/resources/bios.py | 3 +- dracclient/resources/idrac_card.py | 122 ++++++--- dracclient/resources/raid.py | 3 + dracclient/tests/test_idrac_card.py | 238 +++++++++++++++++- dracclient/tests/utils.py | 9 + ...idrac_service-invoke-set_attributes-ok.xml | 19 ++ .../idraccard_enumeration-enum-ok.xml | 21 ++ dracclient/utils.py | 55 +++- 9 files changed, 504 insertions(+), 54 deletions(-) create mode 100644 dracclient/tests/wsman_mocks/idrac_service-invoke-set_attributes-ok.xml diff --git a/dracclient/client.py b/dracclient/client.py index f325a24..36b1908 100644 --- a/dracclient/client.py +++ b/dracclient/client.py @@ -40,6 +40,7 @@ class DRACClient(object): """Client for managing DRAC nodes""" BIOS_DEVICE_FQDD = 'BIOS.Setup.1-1' + IDRAC_FQDD = 'iDRAC.Embedded.1' def __init__( self, host, username, password, port=443, path='/wsman', @@ -191,19 +192,94 @@ class DRACClient(object): """ return self._bios_cfg.set_bios_settings(settings) - def list_idrac_settings(self): + def list_idrac_settings(self, by_name=False, fqdd_filter=IDRAC_FQDD): """List the iDRAC configuration settings - :returns: a dictionary with the iDRAC settings using InstanceID as the - key. The attributes are either iDRACCArdEnumerableAttribute, - iDRACCardStringAttribute or iDRACCardIntegerAttribute - objects. + :param by_name: Controls whether returned dictionary uses iDRAC card + attribute name as key. If set to False, instance_id + will be used. If set to True the keys will be of the + form "group_id#name". + :param fqdd_filter: An FQDD used to filter the instances. Note that + this is only used when by_name is True. + :returns: a dictionary with the iDRAC settings using instance_id as the + key except when by_name is True. The attributes are either + iDRACCardEnumerableAttribute, iDRACCardStringAttribute or + iDRACCardIntegerAttribute objects. :raises: WSManRequestFailure on request failures :raises: WSManInvalidResponse when receiving invalid response :raises: DRACOperationFailed on error reported back by the DRAC interface """ - return self._idrac_cfg.list_idrac_settings() + return self._idrac_cfg.list_idrac_settings(by_name=by_name, + fqdd_filter=fqdd_filter) + + def set_idrac_settings(self, settings, idrac_fqdd=IDRAC_FQDD): + """Sets the iDRAC configuration settings + + To be more precise, it sets the pending_value parameter for each of the + attributes passed in. For the values to be applied, a config job may + need to be created and the node may need to be rebooted. + + :param settings: a dictionary containing the proposed values, with + each key being the name of attribute qualified with + the group ID in the form "group_id#name" and the value + being the proposed value. + :param idrac_fqdd: the FQDD of the iDRAC. + :returns: a dictionary containing: + - The is_commit_required key with a boolean value indicating + whether a config job must be created for the values to be + applied. + - The is_reboot_required key with a RebootRequired enumerated + value indicating whether the server must be rebooted for the + values to be applied. Possible values are true and false. + :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 + :raises: InvalidParameterValue on invalid attribute + """ + return self._idrac_cfg.set_idrac_settings(settings, idrac_fqdd) + + def commit_pending_idrac_changes( + self, + idrac_fqdd=IDRAC_FQDD, + reboot=False): + """Creates a config job for applying all pending changes to an iDRAC + + :param idrac_fqdd: the FQDD of the iDRAC. + :param reboot: indication of whether to also create a reboot job + :returns: id of the created configuration job + :raises: WSManRequestFailure on request failures + :raises: WSManInvalidResponse when receiving invalid response + :raises: DRACOperationFailed on error reported back by the iDRAC + interface + :raises: DRACUnexpectedReturnValue on return value mismatch + """ + return self._job_mgmt.create_config_job( + resource_uri=uris.DCIM_iDRACCardService, + cim_creation_class_name='DCIM_iDRACCardService', + cim_name='DCIM:iDRACCardService', + target=idrac_fqdd, + reboot=reboot) + + def abandon_pending_idrac_changes(self, idrac_fqdd=IDRAC_FQDD): + """Abandon all pending changes to an iDRAC + + Once a config job has been submitted, it can no longer be abandoned. + + :param idrac_fqdd: the FQDD of the iDRAC. + :raises: WSManRequestFailure on request failures + :raises: WSManInvalidResponse when receiving invalid response + :raises: DRACOperationFailed on error reported back by the iDRAC + interface + :raises: DRACUnexpectedReturnValue on return value mismatch + """ + self._job_mgmt.delete_pending_config( + resource_uri=uris.DCIM_iDRACCardService, + cim_creation_class_name='DCIM_iDRACCardService', + cim_name='DCIM:iDRACCardService', + target=idrac_fqdd) def list_lifecycle_settings(self): """List the Lifecycle Controller configuration settings diff --git a/dracclient/resources/bios.py b/dracclient/resources/bios.py index eacb835..978e882 100644 --- a/dracclient/resources/bios.py +++ b/dracclient/resources/bios.py @@ -526,4 +526,5 @@ class BIOSConfiguration(object): uris.DCIM_BIOSService, "DCIM_BIOSService", "DCIM:BIOSService", - 'BIOS.Setup.1-1') + 'BIOS.Setup.1-1', + include_commit_required=True) diff --git a/dracclient/resources/idrac_card.py b/dracclient/resources/idrac_card.py index b8c5561..e412cbf 100644 --- a/dracclient/resources/idrac_card.py +++ b/dracclient/resources/idrac_card.py @@ -15,34 +15,6 @@ from dracclient.resources import uris from dracclient import utils -class iDRACCardConfiguration(object): - - def __init__(self, client): - """Creates iDRACCardManagement object - - :param client: an instance of WSManClient - """ - self.client = client - - def list_idrac_settings(self): - """List the iDRACCard configuration settings - - :returns: a dictionary with the iDRACCard settings using its name as - the key. The attributes are either - iDRACCardEnumerableAttribute, iDRACCardStringAttribute - or iDRACCardIntegerAttribute objects. - :raises: WSManRequestFailure on request failures - :raises: WSManInvalidResponse when receiving invalid response - :raises: DRACOperationFailed on error reported back by the DRAC - interface - """ - namespaces = [(uris.DCIM_iDRACCardEnumeration, - iDRACCardEnumerableAttribute), - (uris.DCIM_iDRACCardString, iDRACCardStringAttribute), - (uris.DCIM_iDRACCardInteger, iDRACCardIntegerAttribute)] - return utils.list_settings(self.client, namespaces, by_name=False) - - class iDRACCardAttribute(object): """Generic iDRACCard attribute class""" @@ -195,6 +167,20 @@ class iDRACCardStringAttribute(iDRACCardAttribute): idrac_attr.read_only, idrac_attr.fqdd, idrac_attr.group_id, min_length, max_length) + def validate(self, new_value): + """Validates new value""" + + val_len = len(new_value) + if val_len < self.min_length or val_len > self.max_length: + msg = ("Attribute '%(attr)s' cannot be set to value '%(val)s'." + " It must be between %(lower)d and %(upper)d characters in " + "length.") % { + 'attr': self.name, + 'val': new_value, + 'lower': self.min_length, + 'upper': self.max_length} + return msg + class iDRACCardIntegerAttribute(iDRACCardAttribute): """Integer iDRACCard attribute class""" @@ -258,3 +244,83 @@ class iDRACCardIntegerAttribute(iDRACCardAttribute): 'lower': self.lower_bound, 'upper': self.upper_bound} return msg + + +class iDRACCardConfiguration(object): + + NAMESPACES = [(uris.DCIM_iDRACCardEnumeration, + iDRACCardEnumerableAttribute), + (uris.DCIM_iDRACCardString, iDRACCardStringAttribute), + (uris.DCIM_iDRACCardInteger, iDRACCardIntegerAttribute)] + + def __init__(self, client): + """Creates an iDRACCardConfiguration object + + :param client: an instance of WSManClient + """ + self.client = client + + def list_idrac_settings(self, by_name=False, fqdd_filter=None): + """List the iDRACCard configuration settings + + :param by_name: Controls whether returned dictionary uses iDRAC card + attribute name as key. If set to False, instance_id + will be used. If set to True the keys will be of the + form "group_id#name". + :param fqdd_filter: An FQDD used to filter the instances. Note that + this is only used when by_name is True. + :returns: a dictionary with the iDRAC settings using instance_id as the + key except when by_name is True. The attributes are either + iDRACCArdEnumerableAttribute, iDRACCardStringAttribute or + iDRACCardIntegerAttribute objects. + :raises: WSManRequestFailure on request failures + :raises: WSManInvalidResponse when receiving invalid response + :raises: DRACOperationFailed on error reported back by the DRAC + interface + """ + + return utils.list_settings(self.client, + self.NAMESPACES, + by_name=by_name, + fqdd_filter=fqdd_filter, + name_formatter=_name_formatter) + + def set_idrac_settings(self, new_settings, idrac_fqdd): + """Set the iDRACCard configuration settings + + To be more precise, it sets the pending_value parameter for each of the + attributes passed in. For the values to be applied, a config job may + need to be created and the node may need to be rebooted. + + :param new_settings: a dictionary containing the proposed values, with + each key being the name of attribute qualified + with the group ID in the form "group_id#name" and + the value being the proposed value. + :param idrac_fqdd: the FQDD of the iDRAC. + :returns: a dictionary containing: + - The is_commit_required key with a boolean value indicating + whether a config job must be created for the values to be + applied. + - The is_reboot_required key with a RebootRequired enumerated + value indicating whether the server must be rebooted for the + values to be applied. Possible values are true and false. + :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 + :raises: InvalidParameterValue on invalid attribute + """ + return utils.set_settings('iDRAC Card', + self.client, + self.NAMESPACES, + new_settings, + uris.DCIM_iDRACCardService, + "DCIM_iDRACCardService", + "DCIM:iDRACCardService", + idrac_fqdd, + name_formatter=_name_formatter) + + +def _name_formatter(attribute): + return "{}#{}".format(attribute.group_id, attribute.name) diff --git a/dracclient/resources/raid.py b/dracclient/resources/raid.py index b8fccc4..5d085d8 100644 --- a/dracclient/resources/raid.py +++ b/dracclient/resources/raid.py @@ -345,6 +345,7 @@ class RAIDManagement(object): expected_return_value=utils.RET_SUCCESS) return utils.build_return_dict(doc, uris.DCIM_RAIDService, + include_commit_required=True, is_commit_required_value=True) def create_virtual_disk(self, raid_controller, physical_disks, raid_level, @@ -445,6 +446,7 @@ class RAIDManagement(object): expected_return_value=utils.RET_SUCCESS) return utils.build_return_dict(doc, uris.DCIM_RAIDService, + include_commit_required=True, is_commit_required_value=True) def delete_virtual_disk(self, virtual_disk): @@ -484,4 +486,5 @@ class RAIDManagement(object): expected_return_value=utils.RET_SUCCESS) return utils.build_return_dict(doc, uris.DCIM_RAIDService, + include_commit_required=True, is_commit_required_value=True) diff --git a/dracclient/tests/test_idrac_card.py b/dracclient/tests/test_idrac_card.py index db49ab5..f6fcb98 100644 --- a/dracclient/tests/test_idrac_card.py +++ b/dracclient/tests/test_idrac_card.py @@ -11,16 +11,25 @@ # License for the specific language governing permissions and limitations # under the License. +import lxml.etree import mock +import re import requests_mock import dracclient.client +from dracclient import constants +from dracclient import exceptions from dracclient.resources import idrac_card +from dracclient.resources import job from dracclient.resources import uris from dracclient.tests import base from dracclient.tests import utils as test_utils +@requests_mock.Mocker() +@mock.patch.object(dracclient.client.WSManClient, + 'wait_until_idrac_is_ready', spec_set=True, + autospec=True) class ClientiDRACCardConfigurationTestCase(base.BaseTest): def setUp(self): @@ -28,12 +37,8 @@ class ClientiDRACCardConfigurationTestCase(base.BaseTest): self.drac_client = dracclient.client.DRACClient( **test_utils.FAKE_ENDPOINT) - @requests_mock.Mocker() - @mock.patch.object(dracclient.client.WSManClient, - 'wait_until_idrac_is_ready', spec_set=True, - autospec=True) - def test_list_idrac_settings(self, mock_requests, - mock_wait_until_idrac_is_ready): + def test_list_idrac_settings_by_instance_id( + self, mock_requests, mock_wait_until_idrac_is_ready): expected_enum_attr = idrac_card.iDRACCardEnumerableAttribute( name='Type', instance_id='iDRAC.Embedded.1#Info.1#Type', @@ -75,7 +80,7 @@ class ClientiDRACCardConfigurationTestCase(base.BaseTest): idrac_settings = self.drac_client.list_idrac_settings() - self.assertEqual(630, len(idrac_settings)) + self.assertEqual(631, len(idrac_settings)) # enumerable attribute self.assertIn('iDRAC.Embedded.1#Info.1#Type', idrac_settings) self.assertEqual(expected_enum_attr, idrac_settings[ @@ -88,3 +93,222 @@ class ClientiDRACCardConfigurationTestCase(base.BaseTest): self.assertIn('iDRAC.Embedded.1#SSH.1#Port', idrac_settings) self.assertEqual(expected_integer_attr, idrac_settings[ 'iDRAC.Embedded.1#SSH.1#Port']) + + def test_list_idrac_settings_by_name( + self, mock_requests, mock_wait_until_idrac_is_ready): + expected_enum_attr = idrac_card.iDRACCardEnumerableAttribute( + name='Type', + instance_id='iDRAC.Embedded.1#Info.1#Type', + read_only=True, + current_value='13G Monolithic', + pending_value=None, + fqdd='iDRAC.Embedded.1', + group_id='Info.1', + possible_values=['12G/13G', '12G Monolithic', '12G Modular', + '13G Monolithic', '13G Modular', '12G DCS', + '13G DCS']) + expected_string_attr = idrac_card.iDRACCardStringAttribute( + name='Version', + instance_id='iDRAC.Embedded.1#Info.1#Version', + read_only=True, + current_value='2.40.40.40', + pending_value=None, + fqdd='iDRAC.Embedded.1', + group_id='Info.1', + min_length=0, + max_length=63) + expected_integer_attr = idrac_card.iDRACCardIntegerAttribute( + name='Port', + instance_id='iDRAC.Embedded.1#SSH.1#Port', + read_only=False, + current_value=22, + pending_value=None, + fqdd='iDRAC.Embedded.1', + group_id='SSH.1', + lower_bound=1, + upper_bound=65535) + mock_requests.post('https://1.2.3.4:443/wsman', [ + {'text': test_utils.iDracCardEnumerations[ + uris.DCIM_iDRACCardEnumeration]['ok']}, + {'text': test_utils.iDracCardEnumerations[ + uris.DCIM_iDRACCardString]['ok']}, + {'text': test_utils.iDracCardEnumerations[ + uris.DCIM_iDRACCardInteger]['ok']}]) + + idrac_settings = self.drac_client.list_idrac_settings(by_name=True) + + self.assertEqual(630, len(idrac_settings)) + + # enumerable attribute + self.assertIn('Info.1#Type', idrac_settings) + self.assertEqual(expected_enum_attr, idrac_settings[ + 'Info.1#Type']) + # string attribute + self.assertIn('Info.1#Version', idrac_settings) + self.assertEqual(expected_string_attr, + idrac_settings['Info.1#Version']) + # integer attribute + self.assertIn('SSH.1#Port', idrac_settings) + self.assertEqual(expected_integer_attr, idrac_settings[ + 'SSH.1#Port']) + + def test_list_multi_idrac_settings_by_name( + self, mock_requests, mock_wait_until_idrac_is_ready): + expected_enum_attr = idrac_card.iDRACCardEnumerableAttribute( + name='Type', + instance_id='iDRAC.Embedded.2#Info.1#Type', + read_only=True, + current_value='13G Monolithic', + pending_value=None, + fqdd='iDRAC.Embedded.2', + group_id='Info.1', + possible_values=['12G/13G', '12G Monolithic', '12G Modular', + '13G Monolithic', '13G Modular', '12G DCS', + '13G DCS']) + mock_requests.post('https://1.2.3.4:443/wsman', [ + {'text': test_utils.iDracCardEnumerations[ + uris.DCIM_iDRACCardEnumeration]['ok']}, + {'text': test_utils.iDracCardEnumerations[ + uris.DCIM_iDRACCardString]['ok']}, + {'text': test_utils.iDracCardEnumerations[ + uris.DCIM_iDRACCardInteger]['ok']}]) + + idrac_settings = self.drac_client.list_idrac_settings( + by_name=True, fqdd_filter='iDRAC.Embedded.2') + + self.assertEqual(1, len(idrac_settings)) + + # enumerable attribute + self.assertIn('Info.1#Type', idrac_settings) + self.assertEqual(expected_enum_attr, idrac_settings[ + 'Info.1#Type']) + + @mock.patch.object(dracclient.client.WSManClient, 'invoke', + spec_set=True, autospec=True) + def test_set_idrac_settings( + self, mock_requests, mock_invoke, mock_wait_until_idrac_is_ready): + expected_selectors = {'CreationClassName': 'DCIM_iDRACCardService', + 'SystemName': 'DCIM:ComputerSystem', + 'Name': 'DCIM:iDRACCardService', + 'SystemCreationClassName': 'DCIM_ComputerSystem'} + expected_properties = {'Target': 'iDRAC.Embedded.1', + 'AttributeName': ['LDAP.1#GroupAttributeIsDN'], + 'AttributeValue': ['Disabled']} + mock_requests.post('https://1.2.3.4:443/wsman', [ + {'text': test_utils.iDracCardEnumerations[ + uris.DCIM_iDRACCardEnumeration]['ok']}, + {'text': test_utils.iDracCardEnumerations[ + uris.DCIM_iDRACCardString]['ok']}, + {'text': test_utils.iDracCardEnumerations[ + uris.DCIM_iDRACCardInteger]['ok']}]) + mock_invoke.return_value = lxml.etree.fromstring( + test_utils.iDracCardInvocations[uris.DCIM_iDRACCardService][ + 'SetAttributes']['ok']) + + result = self.drac_client.set_idrac_settings( + {'LDAP.1#GroupAttributeIsDN': 'Disabled'}) + + self.assertEqual({'is_commit_required': True, + 'is_reboot_required': + constants.RebootRequired.false}, + result) + mock_invoke.assert_called_once_with( + mock.ANY, uris.DCIM_iDRACCardService, 'SetAttributes', + expected_selectors, expected_properties) + + @mock.patch.object(dracclient.client.WSManClient, 'invoke', + spec_set=True, autospec=True) + def test_set_idrac_settings_with_valid_length_string( + self, mock_requests, mock_invoke, mock_wait_until_idrac_is_ready): + expected_selectors = {'CreationClassName': 'DCIM_iDRACCardService', + 'SystemName': 'DCIM:ComputerSystem', + 'Name': 'DCIM:iDRACCardService', + 'SystemCreationClassName': 'DCIM_ComputerSystem'} + expected_properties = {'Target': 'iDRAC.Embedded.1', + 'AttributeName': ['Users.16#Password'], + 'AttributeValue': ['12345678901234567890']} + mock_requests.post('https://1.2.3.4:443/wsman', [ + {'text': test_utils.iDracCardEnumerations[ + uris.DCIM_iDRACCardEnumeration]['ok']}, + {'text': test_utils.iDracCardEnumerations[ + uris.DCIM_iDRACCardString]['ok']}, + {'text': test_utils.iDracCardEnumerations[ + uris.DCIM_iDRACCardInteger]['ok']}]) + mock_invoke.return_value = lxml.etree.fromstring( + test_utils.iDracCardInvocations[uris.DCIM_iDRACCardService][ + 'SetAttributes']['ok']) + result = self.drac_client.set_idrac_settings( + {'Users.16#Password': '12345678901234567890'}) + self.assertEqual({'is_commit_required': True, + 'is_reboot_required': + constants.RebootRequired.false}, + result) + mock_invoke.assert_called_once_with( + mock.ANY, uris.DCIM_iDRACCardService, 'SetAttributes', + expected_selectors, expected_properties) + + def test_set_idrac_settings_with_too_long_string( + self, mock_requests, mock_wait_until_idrac_is_ready): + expected_message = ("Attribute 'Password' cannot be set to " + "value '123456789012345678901'. It must be " + "between 0 and 20 characters in length.") + mock_requests.post('https://1.2.3.4:443/wsman', [ + {'text': test_utils.iDracCardEnumerations[ + uris.DCIM_iDRACCardEnumeration]['ok']}, + {'text': test_utils.iDracCardEnumerations[ + uris.DCIM_iDRACCardString]['ok']}, + {'text': test_utils.iDracCardEnumerations[ + uris.DCIM_iDRACCardInteger]['ok']}]) + self.assertRaisesRegexp( + exceptions.DRACOperationFailed, re.escape(expected_message), + self.drac_client.set_idrac_settings, + {'Users.16#Password': '123456789012345678901'}) + + +class ClientiDRACCardChangesTestCase(base.BaseTest): + + def setUp(self): + super(ClientiDRACCardChangesTestCase, self).setUp() + self.drac_client = dracclient.client.DRACClient( + **test_utils.FAKE_ENDPOINT) + + @mock.patch.object(job.JobManagement, 'create_config_job', spec_set=True, + autospec=True) + def test_commit_pending_idrac_changes(self, mock_create_config_job): + self.drac_client.commit_pending_idrac_changes() + + mock_create_config_job.assert_called_once_with( + mock.ANY, + resource_uri=uris.DCIM_iDRACCardService, + cim_creation_class_name='DCIM_iDRACCardService', + cim_name='DCIM:iDRACCardService', + target=dracclient.client.DRACClient.IDRAC_FQDD, + reboot=False) + + @mock.patch.object(job.JobManagement, 'create_config_job', spec_set=True, + autospec=True) + def test_commit_pending_idrac_changes_with_reboot( + self, mock_create_config_job): + + self.drac_client.commit_pending_idrac_changes( + reboot=True) + + mock_create_config_job.assert_called_once_with( + mock.ANY, + resource_uri=uris.DCIM_iDRACCardService, + cim_creation_class_name='DCIM_iDRACCardService', + cim_name='DCIM:iDRACCardService', + target=dracclient.client.DRACClient.IDRAC_FQDD, + reboot=True) + + @mock.patch.object(job.JobManagement, 'delete_pending_config', + spec_set=True, autospec=True) + def test_abandon_pending_idrac_changes(self, mock_delete_pending_config): + self.drac_client.abandon_pending_idrac_changes() + + mock_delete_pending_config.assert_called_once_with( + mock.ANY, + resource_uri=uris.DCIM_iDRACCardService, + cim_creation_class_name='DCIM_iDRACCardService', + cim_name='DCIM:iDRACCardService', + target=dracclient.client.DRACClient.IDRAC_FQDD) diff --git a/dracclient/tests/utils.py b/dracclient/tests/utils.py index e82e944..8eef943 100644 --- a/dracclient/tests/utils.py +++ b/dracclient/tests/utils.py @@ -147,6 +147,15 @@ iDracCardEnumerations = { }, } +iDracCardInvocations = { + uris.DCIM_iDRACCardService: { + 'SetAttributes': { + 'ok': load_wsman_xml( + 'idrac_service-invoke-set_attributes-ok') + } + } +} + LifecycleControllerEnumerations = { uris.DCIM_SystemView: { 'ok': load_wsman_xml('system_view-enum-ok') diff --git a/dracclient/tests/wsman_mocks/idrac_service-invoke-set_attributes-ok.xml b/dracclient/tests/wsman_mocks/idrac_service-invoke-set_attributes-ok.xml new file mode 100644 index 0000000..3f52ea8 --- /dev/null +++ b/dracclient/tests/wsman_mocks/idrac_service-invoke-set_attributes-ok.xml @@ -0,0 +1,19 @@ + + + http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous + http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_iDRACCardService/SetAttributesResponse + uuid:305ed6f4-2269-43c5-8c28-00b85e81099d + uuid:683c34bc-53c4-13c4-8064-17c1f0d9bed4 + + + + The command was successful + RAC001 + No + 0 + Set PendingValue + + + diff --git a/dracclient/tests/wsman_mocks/idraccard_enumeration-enum-ok.xml b/dracclient/tests/wsman_mocks/idraccard_enumeration-enum-ok.xml index a5a537d..35bc8b9 100644 --- a/dracclient/tests/wsman_mocks/idraccard_enumeration-enum-ok.xml +++ b/dracclient/tests/wsman_mocks/idraccard_enumeration-enum-ok.xml @@ -13,6 +13,27 @@ + + iDRAC Type + Type + 13G Monolithic + 12G/13G + + 6 + iDRAC.Embedded.2 + RAC Information + Info.1 + iDRAC.Embedded.2#Info.1#Type + true + + 12G/13G + 12G Monolithic + 12G Modular + 13G Monolithic + 13G Modular + 12G DCS + 13G DCS + iDRAC Type Type diff --git a/dracclient/utils.py b/dracclient/utils.py index 5ed16e8..dbf4588 100644 --- a/dracclient/utils.py +++ b/dracclient/utils.py @@ -133,7 +133,8 @@ def get_all_wsman_resource_attrs(doc, resource_uri, attr_name, nullable=False): def build_return_dict(doc, resource_uri, is_commit_required_value=None, is_reboot_required_value=None, - commit_required_value=None): + commit_required_value=None, + include_commit_required=False): """Builds a dictionary to be returned Build a dictionary to be returned from WSMAN operations that are not @@ -150,6 +151,8 @@ def build_return_dict(doc, resource_uri, :param commit_required_value: The value to be returned for commit_required, or None if the value should be determined from the doc. + :parm include_commit_required: Indicates if the deprecated commit_required + should be returned in the result. :returns: a dictionary containing: - is_commit_required: indicates if a commit is required. - is_reboot_required: indicates if a reboot is required. @@ -179,10 +182,11 @@ def build_return_dict(doc, resource_uri, # Include commit_required in the response for backwards compatibility # TBD: Remove this parameter in the future - if commit_required_value is None: - commit_required_value = is_reboot_required(doc, resource_uri) + if include_commit_required: + if commit_required_value is None: + commit_required_value = is_reboot_required(doc, resource_uri) - result['commit_required'] = commit_required_value + result['commit_required'] = commit_required_value return result @@ -246,13 +250,19 @@ def validate_integer_value(value, attr_name, error_msgs): error_msgs.append("'%s' is not an integer value" % attr_name) -def list_settings(client, namespaces, by_name=True): +def list_settings(client, namespaces, by_name=True, fqdd_filter=None, + name_formatter=None): """List the configuration settings :param client: an instance of WSManClient. :param namespaces: a list of URI/class pairs to retrieve. :param by_name: controls whether returned dictionary uses attribute name or instance_id as key. + :param fqdd_filter: An FQDD used to filter the instances. Note that + this is only used when by_name is True. + :param name_formatter: a method used to format the keys in the + returned dictionary. By default, + attribute.name will be used. :returns: a dictionary with the settings using name or instance_id as the key. :raises: WSManRequestFailure on request failures @@ -263,7 +273,8 @@ def list_settings(client, namespaces, by_name=True): result = {} for (namespace, attr_cls) in namespaces: - attribs = _get_config(client, namespace, attr_cls, by_name) + attribs = _get_config(client, namespace, attr_cls, by_name, + fqdd_filter, name_formatter) if not set(result).isdisjoint(set(attribs)): raise exceptions.DRACOperationFailed( drac_messages=('Colliding attributes %r' % ( @@ -272,7 +283,8 @@ def list_settings(client, namespaces, by_name=True): return result -def _get_config(client, resource, attr_cls, by_name): +def _get_config(client, resource, attr_cls, by_name, fqdd_filter, + name_formatter): result = {} doc = client.enumerate(resource) @@ -281,7 +293,14 @@ def _get_config(client, resource, attr_cls, by_name): for item in items: attribute = attr_cls.parse(item) if by_name: - result[attribute.name] = attribute + # Filter out all instances without a matching FQDD + if fqdd_filter is None or fqdd_filter == attribute.fqdd: + if name_formatter is None: + name = attribute.name + else: + name = name_formatter(attribute) + + result[name] = attribute else: result[attribute.instance_id] = attribute @@ -295,7 +314,9 @@ def set_settings(settings_type, resource_uri, cim_creation_class_name, cim_name, - target): + target, + name_formatter=None, + include_commit_required=False): """Generically handles setting various types of settings on the iDRAC This method pulls the current list of settings from the iDRAC then compares @@ -313,6 +334,11 @@ def set_settings(settings_type, :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 name_formatter: a method used to format the keys in the + returned dictionary. By default, + attribute.name will be used. + :parm include_commit_required: Indicates if the deprecated commit_required + should be returned in the result. :returns: a dictionary containing: - The commit_required key with a boolean value indicating whether a config job must be created for the values to be @@ -334,7 +360,8 @@ def set_settings(settings_type, :raises: InvalidParameterValue on invalid new setting """ - current_settings = list_settings(client, namespaces, by_name=True) + current_settings = list_settings(client, namespaces, by_name=True, + name_formatter=name_formatter) unknown_keys = set(new_settings) - set(current_settings) if unknown_keys: @@ -383,7 +410,10 @@ def set_settings(settings_type, if not attrib_names: return build_return_dict( - None, resource_uri, is_commit_required_value=False, + None, + resource_uri, + include_commit_required=include_commit_required, + is_commit_required_value=False, is_reboot_required_value=constants.RebootRequired.false, commit_required_value=False) @@ -398,4 +428,5 @@ def set_settings(settings_type, doc = client.invoke(resource_uri, 'SetAttributes', selectors, properties) - return build_return_dict(doc, resource_uri) + return build_return_dict(doc, resource_uri, + include_commit_required=include_commit_required)