From 03278551f15d3d51033e14c056628f2edb7b7674 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aija=20Jaunt=C4=93va?= Date: Fri, 23 Oct 2020 07:42:44 -0400 Subject: [PATCH] Update export system configuration - Add optional parameters export_use and include_in_export to private export system configuration method. - Add public export_system_configuration method. Story: 2003594 Task: 41575 Change-Id: Id8d66cdec203308771ab4cb8909505f96b984296 --- .../resources/manager/constants.py | 53 +++++++++ sushy_oem_idrac/resources/manager/manager.py | 92 ++++++++++++++-- sushy_oem_idrac/resources/manager/mappings.py | 20 ++++ sushy_oem_idrac/tests/unit/test_manager.py | 102 +++++++++++++++++- 4 files changed, 259 insertions(+), 8 deletions(-) diff --git a/sushy_oem_idrac/resources/manager/constants.py b/sushy_oem_idrac/resources/manager/constants.py index e94a4b3..9883fd9 100644 --- a/sushy_oem_idrac/resources/manager/constants.py +++ b/sushy_oem_idrac/resources/manager/constants.py @@ -58,3 +58,56 @@ IMPORT_SHUTDOWN_NO_REBOOT = 'no shutdown' No shutdown performed. Explicit reboot is necessary to apply changes. """ + +# ExportUse in ExportSystemConfiguration +EXPORT_USE_DEFAULT = 'Default' +"""Default export type + +Leaves some attributes commented out and requires user to enable them before +they can be applied during import. +""" + +EXPORT_USE_CLONE = 'Clone' +"""Clone export type suitable for cloning a 'golden' configuration. + +Compared to Default export type, more attributes are enabled and +storage settings adjusted to aid in cloning process. +""" + +EXPORT_USE_REPLACE = 'Replace' +"""Replace export type suited for retiring or replacing complete configuration. + +Compared to Clone export type, most attributes are enabled and storage settings +adjusted to aid in the replace process. +""" + +# IncludeInExport in ExportSystemConfiguration +INCLUDE_EXPORT_DEFAULT = 'Default' +"""Default for what to include in export. + +Does not include read-only attributes, and depending on Export Use, passwords +are marked as ****** (for Default) or are set to default password values (for +Clone and Replace). +""" + +INCLUDE_EXPORT_READ_ONLY = 'Include read only attributes' +"""Includes read-only attributes. + +In addition to values included by Default option, this also includes read-only +attributes that cannot be changed via Import and are provided for informational +purposes only. +""" + +INCLUDE_EXPORT_PASSWORD_HASHES = 'Include password hash values' +"""Include password hashes. + +When using Clone or Replace, include password hashes, instead of default +password. Can be used to replicate passwords across systems. +""" + +INCLUDE_EXPORT_READ_ONLY_PASSWORD_HASHES = ('Include read only attributes and ' + 'password hash values') +"""Includes both read-only attributes and password hashes. + +INCLUDE_EXPORT_READ_ONLY and INCLUDE_EXPORT_PASSWORD_HASHES combined +""" diff --git a/sushy_oem_idrac/resources/manager/manager.py b/sushy_oem_idrac/resources/manager/manager.py index 5257806..3c183e9 100644 --- a/sushy_oem_idrac/resources/manager/manager.py +++ b/sushy_oem_idrac/resources/manager/manager.py @@ -47,6 +47,10 @@ class SharedParameters(base.CompositeField): class ExportActionField(common.ActionField): shared_parameters = SharedParameters('ShareParameters') + allowed_export_use_values = base.Field( + 'ExportUse@Redfish.AllowableValues', adapter=list) + allowed_include_in_export_values = base.Field( + 'IncludeInExport@Redfish.AllowableValues', adapter=list) class ImportActionField(common.ActionField): @@ -270,18 +274,60 @@ VFDD\ set(mgr_maps.EXPORT_CONFIG_VALUE_MAP). intersection(allowed_values)]) - def _export_system_configuration(self, target): + def get_allowed_export_use_values(self): + """Get allowed export use values of export system configuration. + + :returns: A set of allowed export use values. + """ + export_action = self._actions.export_system_configuration + allowed_values = export_action.allowed_export_use_values + + if not allowed_values: + LOG.warning('Could not figure out the allowed values for the ' + 'export use of export system configuration at %s', + self.path) + return set(mgr_maps.EXPORT_USE_VALUE_MAP_REV) + + return set([mgr_maps.EXPORT_USE_VALUE_MAP[value] for value in + set(mgr_maps.EXPORT_USE_VALUE_MAP). + intersection(allowed_values)]) + + def get_allowed_include_in_export_values(self): + """Get allowed include in export values of export system configuration. + + :returns: A set of allowed include in export values. + """ + export_action = self._actions.export_system_configuration + allowed_values = export_action.allowed_include_in_export_values + + if not allowed_values: + LOG.warning('Could not figure out the allowed values for the ' + 'include in export of export system configuration at ' + '%s', self.path) + return set(mgr_maps.INCLUDE_EXPORT_VALUE_MAP_REV) + + return set([mgr_maps.INCLUDE_EXPORT_VALUE_MAP[value] for value + in set(mgr_maps.INCLUDE_EXPORT_VALUE_MAP). + intersection(allowed_values)]) + + def _export_system_configuration( + self, target, export_use=mgr_cons.EXPORT_USE_DEFAULT, + include_in_export=mgr_cons.INCLUDE_EXPORT_DEFAULT): """Export system configuration. - It exports system configuration for specified target - like NIC, BIOS, RAID. + It exports system configuration for specified target like NIC, BIOS, + RAID and allows to configure purpose for export and what to include. :param target: Component of the system to export the configuration from. Can be the entire system. Valid values can be gotten from - `get_allowed_export_target_values`. - :returns: a response object containing configuration details for - the specified target. + `get_allowed_export_system_config_values`. + :param export_use: Export use. Optional, defaults to "Default". + Valid values can be gotten from `get_allowed_export_use_values`. + :param include_in_export: What to include in export. Optional. Defaults + to "Default". Valid values can be gotten from + `get_allowed_include_in_export_values`. + :returns: Response object containing configuration details. :raises: InvalidParameterValueError on invalid target. :raises: ExtensionError on failure to perform requested operation @@ -292,13 +338,30 @@ VFDD\ parameter='target', value=target, valid_values=valid_allowed_targets) + allowed_export_use = self.get_allowed_export_use_values() + if export_use not in allowed_export_use: + raise sushy.exceptions.InvalidParameterValueError( + parameter='export_use', value=export_use, + valid_values=allowed_export_use) + + allowed_include_in_export = self.get_allowed_include_in_export_values() + if include_in_export not in allowed_include_in_export: + raise sushy.exceptions.InvalidParameterValueError( + parameter='include_in_export', value=include_in_export, + valid_values=allowed_include_in_export) + target = mgr_maps.EXPORT_CONFIG_VALUE_MAP_REV[target] + export_use = mgr_maps.EXPORT_USE_VALUE_MAP_REV[export_use] + include_in_export =\ + mgr_maps.INCLUDE_EXPORT_VALUE_MAP_REV[include_in_export] action_data = { 'ShareParameters': { 'Target': target }, - 'ExportFormat': "JSON" + 'ExportFormat': "JSON", + 'ExportUse': export_use, + 'IncludeInExport': include_in_export } try: @@ -318,6 +381,21 @@ VFDD\ LOG.error('Dell OEM export system configuration failed : %s', exc) raise + def export_system_configuration(self): + """Export system configuration. + + Exports ALL targets for cloning and includes password hashes. + + :returns: Response object containing configuration details. + :raises: InvalidParameterValueError on invalid target. + :raises: ExtensionError on failure to perform requested + operation + """ + return self._export_system_configuration( + mgr_cons.EXPORT_TARGET_ALL, + export_use=mgr_cons.EXPORT_USE_CLONE, + include_in_export=mgr_cons.INCLUDE_EXPORT_PASSWORD_HASHES) + def get_pxe_port_macs_bios(self, ethernet_interfaces_mac): """Get a list of pxe port MAC addresses for BIOS. diff --git a/sushy_oem_idrac/resources/manager/mappings.py b/sushy_oem_idrac/resources/manager/mappings.py index 1eb8072..336571b 100644 --- a/sushy_oem_idrac/resources/manager/mappings.py +++ b/sushy_oem_idrac/resources/manager/mappings.py @@ -41,3 +41,23 @@ IMPORT_SHUTDOWN_VALUE_MAP = { IMPORT_SHUTDOWN_VALUE_MAP_REV =\ utils.revert_dictionary(IMPORT_SHUTDOWN_VALUE_MAP) + +EXPORT_USE_VALUE_MAP = { + 'Default': mgr_cons.EXPORT_USE_DEFAULT, + 'Clone': mgr_cons.EXPORT_USE_CLONE, + 'Replace': mgr_cons.EXPORT_USE_REPLACE +} + +EXPORT_USE_VALUE_MAP_REV = utils.revert_dictionary(EXPORT_USE_VALUE_MAP) + +INCLUDE_EXPORT_VALUE_MAP = { + 'Default': mgr_cons.INCLUDE_EXPORT_DEFAULT, + 'IncludeReadOnly': mgr_cons.INCLUDE_EXPORT_READ_ONLY, + 'IncludePasswordHashValues': + mgr_cons.INCLUDE_EXPORT_PASSWORD_HASHES, + 'IncludeReadOnly,IncludePasswordHashValues': + mgr_cons.INCLUDE_EXPORT_READ_ONLY_PASSWORD_HASHES +} + +INCLUDE_EXPORT_VALUE_MAP_REV =\ + utils.revert_dictionary(INCLUDE_EXPORT_VALUE_MAP) diff --git a/sushy_oem_idrac/tests/unit/test_manager.py b/sushy_oem_idrac/tests/unit/test_manager.py index 8ddfa4a..b44e5ed 100644 --- a/sushy_oem_idrac/tests/unit/test_manager.py +++ b/sushy_oem_idrac/tests/unit/test_manager.py @@ -100,7 +100,26 @@ class ManagerTestCase(BaseTestCase): '/redfish/v1/Managers/iDRAC.Embedded.1/Actions/Oem/EID_674_Manager' '.ExportSystemConfiguration', data={'ShareParameters': {'Target': 'ALL'}, - 'ExportFormat': 'JSON'}) + 'ExportFormat': 'JSON', + 'ExportUse': 'Default', + 'IncludeInExport': 'Default'}) + + @mock.patch('sushy.resources.oem.common._global_extn_mgrs_by_resource', {}) + def test__export_system_configuration_nondefault(self): + oem = self.manager.get_oem_extension('Dell') + oem._export_system_configuration( + target=mgr_cons.EXPORT_TARGET_RAID, + export_use=mgr_cons.EXPORT_USE_CLONE, + include_in_export=mgr_cons.INCLUDE_EXPORT_READ_ONLY) + + self.conn.post.assert_called_once_with( + '/redfish/v1/Managers/iDRAC.Embedded.1/Actions/Oem/EID_674_Manager' + '.ExportSystemConfiguration', data={'ShareParameters': + {'Target': 'RAID'}, + 'ExportFormat': 'JSON', + 'ExportUse': 'Clone', + 'IncludeInExport': + 'IncludeReadOnly'}) @mock.patch('sushy.resources.oem.common._global_extn_mgrs_by_resource', {}) def test__export_system_configuration_invalid_target(self): @@ -109,6 +128,87 @@ class ManagerTestCase(BaseTestCase): self.assertRaises(sushy.exceptions.InvalidParameterValueError, oem._export_system_configuration, target) + def test__export_system_configuration_invalid_export_use(self): + oem = self.manager.get_oem_extension('Dell') + self.assertRaises(sushy.exceptions.InvalidParameterValueError, + oem._export_system_configuration, "RAID", + export_use="ABC") + + def test__export_system_configuration_invalid_include_in_export(self): + oem = self.manager.get_oem_extension('Dell') + self.assertRaises(sushy.exceptions.InvalidParameterValueError, + oem._export_system_configuration, "RAID", + include_in_export="ABC") + + @mock.patch('sushy.resources.oem.common._global_extn_mgrs_by_resource', {}) + def test_get_allowed_export_use_values(self): + oem = self.manager.get_oem_extension('Dell') + expected_values = {mgr_cons.EXPORT_USE_DEFAULT, + mgr_cons.EXPORT_USE_CLONE, + mgr_cons.EXPORT_USE_REPLACE} + allowed_values = oem.get_allowed_export_use_values() + self.assertIsInstance(allowed_values, set) + self.assertEqual(expected_values, allowed_values) + + @mock.patch.object(oem_manager, 'LOG', autospec=True) + def test_get_allowed_export_use_values_missing(self, mock_log): + oem = self.manager.get_oem_extension('Dell') + export_action = ('OemManager.v1_0_0' + '#OemManager.ExportSystemConfiguration') + oem.json['Actions']['Oem'][export_action].pop( + 'ExportUse@Redfish.AllowableValues') + oem.refresh() + expected_values = {mgr_cons.EXPORT_USE_DEFAULT, + mgr_cons.EXPORT_USE_CLONE, + mgr_cons.EXPORT_USE_REPLACE} + allowed_values = oem.get_allowed_export_use_values() + self.assertIsInstance(allowed_values, set) + self.assertEqual(expected_values, allowed_values) + mock_log.warning.assert_called_once() + + @mock.patch('sushy.resources.oem.common._global_extn_mgrs_by_resource', {}) + def test_get_allowed_include_in_export_values(self): + oem = self.manager.get_oem_extension('Dell') + expected_values = {mgr_cons.INCLUDE_EXPORT_DEFAULT, + mgr_cons.INCLUDE_EXPORT_READ_ONLY, + mgr_cons.INCLUDE_EXPORT_PASSWORD_HASHES, + mgr_cons.INCLUDE_EXPORT_READ_ONLY_PASSWORD_HASHES} + allowed_values = oem.get_allowed_include_in_export_values() + self.assertIsInstance(allowed_values, set) + self.assertEqual(expected_values, allowed_values) + + @mock.patch.object(oem_manager, 'LOG', autospec=True) + def test_get_allowed_include_in_export_values_missing(self, mock_log): + oem = self.manager.get_oem_extension('Dell') + export_action = ('OemManager.v1_0_0' + '#OemManager.ExportSystemConfiguration') + oem.json['Actions']['Oem'][export_action].pop( + 'IncludeInExport@Redfish.AllowableValues') + oem.refresh() + expected_values = {mgr_cons.INCLUDE_EXPORT_DEFAULT, + mgr_cons.INCLUDE_EXPORT_READ_ONLY, + mgr_cons.INCLUDE_EXPORT_PASSWORD_HASHES, + mgr_cons.INCLUDE_EXPORT_READ_ONLY_PASSWORD_HASHES} + allowed_values = oem.get_allowed_include_in_export_values() + self.assertIsInstance(allowed_values, set) + self.assertEqual(expected_values, allowed_values) + mock_log.warning.assert_called_once() + + @mock.patch('sushy.resources.oem.common._global_extn_mgrs_by_resource', {}) + def test_export_system_configuration(self): + oem = self.manager.get_oem_extension('Dell') + oem._export_system_configuration = mock.Mock() + mock_response = mock.Mock() + oem._export_system_configuration.return_value = mock_response + + response = oem.export_system_configuration() + + self.assertEqual(mock_response, response) + oem._export_system_configuration.assert_called_once_with( + mgr_cons.EXPORT_TARGET_ALL, + export_use=mgr_cons.EXPORT_USE_CLONE, + include_in_export=mgr_cons.INCLUDE_EXPORT_PASSWORD_HASHES) + @mock.patch('sushy.resources.oem.common._global_extn_mgrs_by_resource', {}) def test_get_pxe_port_macs_bios(self): oem = self.manager.get_oem_extension('Dell')