diff --git a/sushy_oem_idrac/resources/manager/manager.py b/sushy_oem_idrac/resources/manager/manager.py index 6cf6dde..707abae 100644 --- a/sushy_oem_idrac/resources/manager/manager.py +++ b/sushy_oem_idrac/resources/manager/manager.py @@ -12,6 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. +import json import logging import subprocess import time @@ -42,6 +43,12 @@ _SYSTEM_CONFIG_TAG = "SystemConfiguration" # Response Code Constant _RESPONSE_OK_CODE = 200 +# Structure {'Component FQDD': (tuple of beginning of Attribute keys)} +_DESTRUCTIVE_CONF_KEYS = { + 'iDRAC.Embedded.1': + ('IPv4Static', 'IPv6Static', 'IPv4.1#Enable', 'IPv4.1#DHCPEnable', + 'IPv6.1#Enable', 'IPv6.1#AutoConfig')} + class SharedParameters(base.CompositeField): allowed_target_values = base.Field('Target@Redfish.AllowableValues') @@ -411,23 +418,44 @@ VFDD\ LOG.error('Dell OEM export system configuration failed : %s', exc) raise - def export_system_configuration(self): + def export_system_configuration(self, include_destructive_fields=True): """Export system configuration. Exports ALL targets for cloning and includes password hashes and read-only attributes. + :param include_destructive_fields: Whether includes settings such as + iDRAC static IP address that could lead to losing access to iDRAC + if importing this configuration into another system. Default to + True for backward compability. False recommended if unsure. :returns: Response object containing configuration details. :raises: InvalidParameterValueError on invalid target. :raises: ExtensionError on failure to perform requested operation """ include_in_export = mgr_cons.INCLUDE_EXPORT_READ_ONLY_PASSWORD_HASHES - return self._export_system_configuration( + + response = self._export_system_configuration( mgr_cons.EXPORT_TARGET_ALL, export_use=mgr_cons.EXPORT_USE_CLONE, include_in_export=include_in_export) + if (response.status_code == _RESPONSE_OK_CODE + and not include_destructive_fields): + conf = response.json() + if _SYSTEM_CONFIG_TAG in conf.keys(): + for fqdd, values in _DESTRUCTIVE_CONF_KEYS.items(): + for comp in conf[_SYSTEM_CONFIG_TAG]['Components']: + if comp['FQDD'] == fqdd: + attributes_copy = comp['Attributes'].copy() + for child in comp['Attributes']: + if child.get('Name').startswith(values): + attributes_copy.remove(child) + comp['Attributes'] = attributes_copy + response._content = json.dumps(conf).encode() + + return response + 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/tests/unit/json_samples/export_configuration_idrac.json b/sushy_oem_idrac/tests/unit/json_samples/export_configuration_idrac.json new file mode 100644 index 0000000..f1ae997 --- /dev/null +++ b/sushy_oem_idrac/tests/unit/json_samples/export_configuration_idrac.json @@ -0,0 +1,174 @@ +{ "SystemConfiguration": { + "Comments": [ + { "Comment": "Export type is Clone,RO,JSON,IncludeHash,Selective" }, + { "Comment": "Exported configuration may contain commented attributes. Attributes may be commented due to dependency, destructive nature, preserving server identity or for security reasons." } + ], + "Model": "PowerEdge R640", + "ServiceTag": "ABC1234", + "TimeStamp": "Thu Nov 4 07:21:54 2021", + "Components": [ + { "FQDD": "iDRAC.Embedded.1", + "Attributes": [ + { "Name": "Info.1#Product", + "Value": "Integrated Dell Remote Access Controller", + "Set On Import": "False", + "Comment": "Always Read Only" }, + { "Name": "Info.1#Description", + "Value": "This system component provides a complete set of remote management functions for Dell PowerEdge Servers", + "Set On Import": "False", + "Comment": "Always Read Only" }, + { "Name": "Info.1#Version", + "Value": "5.00.10.20", + "Set On Import": "False", + "Comment": "Always Read Only" }, + { "Name": "Info.1#Build", + "Value": "01", + "Set On Import": "False", + "Comment": "Always Read Only" }, + { "Name": "Info.1#Name", + "Value": "iDRAC", + "Set On Import": "False", + "Comment": "Always Read Only" }, + { "Name": "Info.1#Type", + "Value": "14G Monolithic", + "Set On Import": "False", + "Comment": "Always Read Only" }, + { "Name": "Info.1#ServerGen", + "Value": "14G", + "Set On Import": "False", + "Comment": "Always Read Only" }, + { "Name": "IPv4.1#Enable", + "Value": "Enabled", + "Set On Import": "True", + "Comment": "Read and Write" }, + { "Name": "IPv4.1#DHCPEnable", + "Value": "Disabled", + "Set On Import": "True", + "Comment": "Read and Write" }, + { "Name": "IPv6.1#Enable", + "Value": "Disabled", + "Set On Import": "True", + "Comment": "Read and Write" }, + { "Name": "IPv6.1#AutoConfig", + "Value": "Enabled", + "Set On Import": "True", + "Comment": "Read and Write" }, + { "Name": "IPv6.1#LinkLocalAddress", + "Value": "2001:db8:3333:4444:5555:6666:7777:8888", + "Set On Import": "False", + "Comment": "Always Read Only" }, + { "Name": "IPv6.1#Address2", + "Value": "::", + "Set On Import": "False", + "Comment": "Always Read Only" }, + { "Name": "IPv6.1#Address3", + "Value": "::", + "Set On Import": "False", + "Comment": "Always Read Only" }, + { "Name": "IPv6.1#Address4", + "Value": "::", + "Set On Import": "False", + "Comment": "Always Read Only" }, + { "Name": "IPv6.1#Address5", + "Value": "::", + "Set On Import": "False", + "Comment": "Always Read Only" }, + { "Name": "IPv6.1#Address6", + "Value": "::", + "Set On Import": "False", + "Comment": "Always Read Only" }, + { "Name": "IPv6.1#Address7", + "Value": "::", + "Set On Import": "False", + "Comment": "Always Read Only" }, + { "Name": "IPv6.1#Address8", + "Value": "::", + "Set On Import": "False", + "Comment": "Always Read Only" }, + { "Name": "IPv6.1#Address9", + "Value": "::", + "Set On Import": "False", + "Comment": "Always Read Only" }, + { "Name": "IPv6.1#Address10", + "Value": "::", + "Set On Import": "False", + "Comment": "Always Read Only" }, + { "Name": "IPv6.1#Address11", + "Value": "::", + "Set On Import": "False", + "Comment": "Always Read Only" }, + { "Name": "IPv6.1#Address12", + "Value": "::", + "Set On Import": "False", + "Comment": "Always Read Only" }, + { "Name": "IPv6.1#Address13", + "Value": "::", + "Set On Import": "False", + "Comment": "Always Read Only" }, + { "Name": "IPv6.1#Address14", + "Value": "::", + "Set On Import": "False", + "Comment": "Always Read Only" }, + { "Name": "IPv6.1#Address15", + "Value": "::", + "Set On Import": "False", + "Comment": "Always Read Only" }, + { "Name": "IPv6.1#AddressState", + "Value": "Active", + "Set On Import": "False", + "Comment": "Always Read Only" }, + { "Name": "IPv6.1#DUID", + "Value": "00:01:00:01:25:67:39:1b:f4:02:70:db:6c:88", + "Set On Import": "False", + "Comment": "Always Read Only" }, + { "Name": "IPv4Static.1#Address", + "Value": "192.168.88.13", + "Set On Import": "True", + "Comment": "Read and Write" }, + { "Name": "IPv4Static.1#Netmask", + "Value": "255.255.255.0", + "Set On Import": "True", + "Comment": "Read and Write" }, + { "Name": "IPv4Static.1#Gateway", + "Value": "192.168.88.1", + "Set On Import": "True", + "Comment": "Read and Write" }, + { "Name": "IPv4Static.1#DNS1", + "Value": "0.0.0.0", + "Set On Import": "True", + "Comment": "Read and Write" }, + { "Name": "IPv4Static.1#DNS2", + "Value": "0.0.0.0", + "Set On Import": "True", + "Comment": "Read and Write" }, + { "Name": "IPv4Static.1#DNSFromDHCP", + "Value": "Disabled", + "Set On Import": "True", + "Comment": "Read and Write" }, + { "Name": "IPv6Static.1#Address1", + "Value": "::", + "Set On Import": "True", + "Comment": "Read and Write" }, + { "Name": "IPv6Static.1#Gateway", + "Value": "::", + "Set On Import": "True", + "Comment": "Read and Write" }, + { "Name": "IPv6Static.1#PrefixLength", + "Value": "64", + "Set On Import": "True", + "Comment": "Read and Write" }, + { "Name": "IPv6Static.1#DNS1", + "Value": "::", + "Set On Import": "True", + "Comment": "Read and Write" }, + { "Name": "IPv6Static.1#DNS2", + "Value": "::", + "Set On Import": "True", + "Comment": "Read and Write" }, + { "Name": "IPv6Static.1#DNSFromDHCP6", + "Value": "Disabled", + "Set On Import": "True", + "Comment": "Read and Write" } + ]} + ]} + } diff --git a/sushy_oem_idrac/tests/unit/resources/manager/test_manager.py b/sushy_oem_idrac/tests/unit/resources/manager/test_manager.py index 911028c..2c05dda 100644 --- a/sushy_oem_idrac/tests/unit/resources/manager/test_manager.py +++ b/sushy_oem_idrac/tests/unit/resources/manager/test_manager.py @@ -295,6 +295,29 @@ class ManagerTestCase(BaseTestCase): export_use=mgr_cons.EXPORT_USE_CLONE, include_in_export=include_in_export) + @mock.patch('sushy.resources.oem.common._global_extn_mgrs_by_resource', {}) + def test_export_system_configuration_destructive_fields(self): + oem = self.manager.get_oem_extension('Dell') + oem._export_system_configuration = mock.Mock() + with open('sushy_oem_idrac/tests/unit/json_samples/' + 'export_configuration_idrac.json') as f: + mock_response = oem._export_system_configuration.return_value + mock_response.json.return_value = json.load(f) + mock_response.status_code = 200 + + response = oem.export_system_configuration( + include_destructive_fields=False) + + response_json = json.loads(response._content) + # From 40 items in test data 16 should be removed + self.assertEqual(24, len(response_json['SystemConfiguration'] + ['Components'][0]['Attributes'])) + include_in_export = mgr_cons.INCLUDE_EXPORT_READ_ONLY_PASSWORD_HASHES + oem._export_system_configuration.assert_called_once_with( + mgr_cons.EXPORT_TARGET_ALL, + export_use=mgr_cons.EXPORT_USE_CLONE, + include_in_export=include_in_export) + @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')