diff --git a/ChangeLog b/ChangeLog index fa9f9eed..5cf8bc15 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,27 @@ PyU4V Change Log ================ +Version 9.2.0.3 - released 7/12/20 +================================== +- Masking View performance support has been added to performance.py + - PyU4V.performance.PerformanceFunctions.get_masking_view_keys + - PyU4V.performance.PerformanceFunctions.get_masking_view_stats +- Director list, Port list, IP interface list, and get IP interface details + have been added to system.py + - PyU4V.system.SystemFunctions.get_director_list + - PyU4V.system.SystemFunctions.get_director_port_list + - PyU4V.system.SystemFunctions.get_ip_interface_list + - PyU4V.system.SystemFunctions.get_ip_interface +- Array storage level details are now available via provisioning.py + - PyU4V.provisioning.ProvisioningFunctions.get_array +- Array level WLP capabilities are now accessible in workload_planner.py + - PyU4V.workload_planner.WLPFunctions.get_capabilities +- array_id is now an optional parameter for the following functions: + - PyU4V.replication.ReplicationFunctions.get_array_replication_capabilities + - PyU4V.migration.MigrationFunctions.get_migration_info + - PyU4V.migration.MigrationFunctions.get_array_migration_capabilities + + Version 9.2.0.2 - released 14/10/20 =================================== - When generating threshold CSV files from diff --git a/PyU4V/__init__.py b/PyU4V/__init__.py index 37eb4b96..225557e3 100644 --- a/PyU4V/__init__.py +++ b/PyU4V/__init__.py @@ -23,7 +23,7 @@ from .univmax_conn import U4VConn # noqa: F401 __title__ = 'pyu4v' -__version__ = '9.2.0.2' +__version__ = '9.2.0.3' __author__ = 'Dell EMC or its subsidiaries' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2020 Dell EMC Inc' diff --git a/PyU4V/migration.py b/PyU4V/migration.py index 226b5b72..baa890f1 100644 --- a/PyU4V/migration.py +++ b/PyU4V/migration.py @@ -41,14 +41,15 @@ def __init__(self, array_id, rest_client): self.modify_resource = self.common.modify_resource self.delete_resource = self.common.delete_resource - def get_migration_info(self): + def get_migration_info(self, array_id=None): """Return migration information for an array. :returns: migration info -- dict """ + array_id = array_id if array_id else self.array_id return self.get_resource( category=MIGRATION, - resource_level=SYMMETRIX, resource_level_id=self.array_id) + resource_level=SYMMETRIX, resource_level_id=array_id) def create_migration_environment(self, target_array_id): """Create a new migration environment between two arrays. @@ -77,11 +78,12 @@ def delete_migration_environment(self, target_array_id): resource_level=SYMMETRIX, resource_level_id=self.array_id, resource_type=ENVIRONMENT, resource_type_id=target_array_id) - def get_array_migration_capabilities(self): + def get_array_migration_capabilities(self, array_id=None): """Check what migration facilities are available. :returns: array capabilities -- dict """ + array_id = array_id if array_id else self.array_id capabilities = self.get_resource( category=MIGRATION, resource_level=CAPABILITIES, resource_type=SYMMETRIX) @@ -90,7 +92,7 @@ def get_array_migration_capabilities(self): 'storageArrayCapability', list()) if capabilities else list()) array_capabilities = dict() for symm in symm_list: - if symm['arrayId'] == self.array_id: + if symm['arrayId'] == array_id: array_capabilities = symm break return array_capabilities diff --git a/PyU4V/performance.py b/PyU4V/performance.py index 9bf68bf4..bc9b5f7d 100644 --- a/PyU4V/performance.py +++ b/PyU4V/performance.py @@ -1809,11 +1809,44 @@ def get_iscsi_target_stats( data_format=data_format, request_body=request_body, start_time=start_time, end_time=end_time, recency=recency) + def get_masking_view_keys(self, array_id=None): + """List masking views for the given array. + + :param array_id: array id -- str + :returns: masking view info with first and last available dates -- list + """ + array_id = self.array_id if not array_id else array_id + key_list = self.get_performance_key_list(category=pc.MV, + array_id=array_id) + return key_list.get(pc.MV_INFO, list()) if key_list else list() + + def get_masking_view_stats( + self, masking_view_id, metrics, array_id=None, + data_format=pc.AVERAGE, start_time=None, end_time=None, + recency=None): + """List time range performance data for given masking view. + + :param masking_view_id: masking view id -- str + :param metrics: performance metrics to retrieve -- str or list + :param array_id: array id -- str + :param data_format: response data format 'Average' or 'Maximum' -- str + :param start_time: timestamp in milliseconds since epoch -- str + :param end_time: timestamp in milliseconds since epoch -- str + :param recency: check recency of timestamp in minutes -- int + :returns: performance metrics -- dict + """ + array_id = self.array_id if not array_id else array_id + request_body = {pc.MV_ID: masking_view_id} + return self.get_performance_stats( + array_id=array_id, category=pc.MV, metrics=metrics, + data_format=data_format, request_body=request_body, + start_time=start_time, end_time=end_time, recency=recency) + def get_port_group_keys(self, array_id=None): """List port group for the given array. - :param array_id: array_id: array id -- str - :returns: port group info with first and last available + :param array_id: array id -- str + :returns: port group info with first and last available dates -- list """ array_id = self.array_id if not array_id else array_id key_list = self.get_performance_key_list(category=pc.PG, diff --git a/PyU4V/provisioning.py b/PyU4V/provisioning.py index 36079592..c7994d0b 100644 --- a/PyU4V/provisioning.py +++ b/PyU4V/provisioning.py @@ -61,6 +61,18 @@ def __init__(self, array_id, rest_client): self.modify_resource = self.common.modify_resource self.delete_resource = self.common.delete_resource + def get_array(self, array_id=None): + """Query for details of an array from SLOPROVISIONING endpoint. + + :param array_id: array serial number -- str + :returns: array details -- dict + """ + array_id = array_id if array_id else self.array_id + response = self.get_resource( + category=SLOPROVISIONING, resource_level=SYMMETRIX, + resource_level_id=array_id) + return response if response else dict() + def get_director(self, director): """Query for details of a director for a symmetrix. diff --git a/PyU4V/replication.py b/PyU4V/replication.py index 811ad91c..81a4972e 100644 --- a/PyU4V/replication.py +++ b/PyU4V/replication.py @@ -75,11 +75,12 @@ def get_replication_info(self): category=REPLICATION, resource_level=SYMMETRIX, resource_level_id=self.array_id) - def get_array_replication_capabilities(self): + def get_array_replication_capabilities(self, array_id=None): """Check what replication facilities are available. :returns: replication capability details -- dict """ + array_id = array_id if array_id else self.array_id capabilities = self.get_resource( category=REPLICATION, resource_level=CAPABILITIES, resource_type=SYMMETRIX) @@ -87,7 +88,7 @@ def get_array_replication_capabilities(self): 'symmetrixCapability', list()) if capabilities else list() array_capabilities = dict() for symm in symm_list: - if symm['symmetrixId'] == self.array_id: + if symm['symmetrixId'] == array_id: array_capabilities = symm break return array_capabilities diff --git a/PyU4V/system.py b/PyU4V/system.py index 89f3644d..28820bf9 100644 --- a/PyU4V/system.py +++ b/PyU4V/system.py @@ -14,6 +14,7 @@ """system.py.""" import logging +import re import time from datetime import datetime @@ -74,6 +75,11 @@ SUCCESS = constants.SUCCESS AUDIT_RECORD_TIME = constants.AUDIT_RECORD_TIME STR_TIME_FORMAT = constants.STR_TIME_FORMAT +DIRECTOR = constants.DIRECTOR +DIRECTOR_ID = constants.DIRECTOR_ID +PORT = constants.PORT +IP_INTERFACE = constants.IP_INTERFACE +IP_INTERFACE_ID = constants.IP_INTERFACE_ID class SystemFunctions(object): @@ -835,3 +841,86 @@ def download_audit_log_record(self, array_id=None, return_binary=False, LOG.info('The audit log download request was successful.') return return_dict + + def get_director_list(self, array_id=None, iscsi_only=False): + """Get a list of directors for a given array. + + :param array_id: array serial number -- str + :param iscsi_only: return only iSCSI directors -- bool + :returns: iSCSI directors -- list + """ + array_id = array_id if array_id else self.array_id + dir_list = self.common.get_resource( + category=SYSTEM, + resource_level=SYMMETRIX, resource_level_id=array_id, + resource_type=DIRECTOR) + response_dir_list = list() + for director in dir_list.get(DIRECTOR_ID, list()): + if iscsi_only and re.match(r'^SE-\d[A-Z]$', director): + response_dir_list.append(director) + elif not iscsi_only: + response_dir_list.append(director) + + return response_dir_list + + def get_director_port_list(self, director_id, array_id=None, + iscsi_target=None): + """Get a list of director ports for a specified director. + + :param director_id: director id -- str + :param array_id: array id -- str + :param iscsi_target: if the port is an iSCSI target, applicable to + front-end SE directors only, default to not set + -- bool + :returns: director ports -- list + """ + array_id = array_id if array_id else self.array_id + + filters = dict() + if isinstance(iscsi_target, bool): + filters['iscsi_target'] = iscsi_target + + port_list = self.common.get_resource( + category=SYSTEM, + resource_level=SYMMETRIX, resource_level_id=array_id, + resource_type=DIRECTOR, resource_type_id=director_id, + resource=PORT, params=filters) + return port_list.get( + 'symmetrixPortKey', list()) if port_list else list() + + def get_ip_interface_list(self, director_id, port_id, array_id=None): + """Get a list of IP interfaces for a given array. + + :param director_id: director id -- str + :param port_id: port id -- str + :param array_id: array id -- str + :returns: IP interfaces -- list + """ + array_id = array_id if array_id else self.array_id + ip_list = self.common.get_resource( + category=SYSTEM, + resource_level=SYMMETRIX, resource_level_id=array_id, + resource_type=DIRECTOR, resource_type_id=director_id, + resource=PORT, resource_id=port_id, object_type=IP_INTERFACE) + + return ip_list.get(IP_INTERFACE_ID, list()) if ip_list else list() + + def get_ip_interface(self, director_id, port_id, interface_id, + array_id=None): + """Get IP interface details + + :param director_id: director id -- str + :param port_id: port id -- str + :param interface_id: interface id -- str + :param array_id: array id -- str + :returns: IP interface details -- dict + """ + array_id = array_id if array_id else self.array_id + interface = self.common.get_resource( + category=SYSTEM, + resource_level=SYMMETRIX, resource_level_id=array_id, + resource_type=DIRECTOR, resource_type_id=director_id, + resource=PORT, resource_id=port_id, + object_type=IP_INTERFACE, object_type_id=interface_id) + + return interface if interface else dict() diff --git a/PyU4V/tests/ci_tests/test_pyu4v_ci_migration.py b/PyU4V/tests/ci_tests/test_pyu4v_ci_migration.py index e0cadf29..96fda1fc 100644 --- a/PyU4V/tests/ci_tests/test_pyu4v_ci_migration.py +++ b/PyU4V/tests/ci_tests/test_pyu4v_ci_migration.py @@ -28,7 +28,8 @@ def setUp(self): def test_get_migration_info(self): """Test get_migration_info.""" - migration_info = self.migration.get_migration_info() + migration_info = self.migration.get_migration_info( + array_id=self.conn.array_id) self.assertEqual(4, len(migration_info.keys())) self.assertEqual(self.conn.array_id, migration_info.get('arrayId')) self.assertTrue(migration_info.get('local')) @@ -37,7 +38,8 @@ def test_get_migration_info(self): def test_get_array_migration_capabilities(self): """Test get_array_migration_capabilities.""" - array_capabilities = self.migration.get_array_migration_capabilities() + array_capabilities = self.migration.get_array_migration_capabilities( + array_id=self.conn.array_id) self.assertTrue(array_capabilities) self.assertEqual(6, len(array_capabilities.keys())) self.assertEqual(self.conn.array_id, array_capabilities.get('arrayId')) diff --git a/PyU4V/tests/ci_tests/test_pyu4v_ci_performance.py b/PyU4V/tests/ci_tests/test_pyu4v_ci_performance.py index 2a3a506a..ddca1f99 100644 --- a/PyU4V/tests/ci_tests/test_pyu4v_ci_performance.py +++ b/PyU4V/tests/ci_tests/test_pyu4v_ci_performance.py @@ -768,6 +768,15 @@ def test_iscsi_target_performance_function(self): self.run_performance_test_asserts(category, id_tag, key_func, metrics_func) + def test_masking_view_performance_function(self): + """Test Masking View performance function.""" + category = pc.MV + id_tag = pc.MV_ID + key_func = self.perf.get_masking_view_keys + metrics_func = self.perf.get_masking_view_stats + self.run_performance_test_asserts(category, id_tag, key_func, + metrics_func) + def test_port_group_performance_function(self): """Test port group performance function.""" category = pc.PG diff --git a/PyU4V/tests/ci_tests/test_pyu4v_ci_provisioning.py b/PyU4V/tests/ci_tests/test_pyu4v_ci_provisioning.py index 6f712367..43eb2933 100644 --- a/PyU4V/tests/ci_tests/test_pyu4v_ci_provisioning.py +++ b/PyU4V/tests/ci_tests/test_pyu4v_ci_provisioning.py @@ -31,6 +31,14 @@ def setUp(self): super(CITestProvisioning, self).setUp() self.provisioning = self.conn.provisioning + def test_get_array(self): + """Test get_array.""" + response = self.provisioning.get_array(array_id=self.conn.array_id) + self.assertTrue(response) + self.assertIsInstance(response, dict) + self.assertEqual(response.get('symmetrixId'), self.conn.array_id) + self.assertTrue(response.get('local')) + def test_get_director(self): """Test get_director.""" availability = 'availability' diff --git a/PyU4V/tests/ci_tests/test_pyu4v_ci_replication.py b/PyU4V/tests/ci_tests/test_pyu4v_ci_replication.py index a52bc94c..8643a1d7 100644 --- a/PyU4V/tests/ci_tests/test_pyu4v_ci_replication.py +++ b/PyU4V/tests/ci_tests/test_pyu4v_ci_replication.py @@ -30,7 +30,8 @@ def setUp(self): def test_get_array_replication_capabilities(self): """Test get_array_replication_capabilities.""" - rep_info = self.conn.replication.get_array_replication_capabilities() + rep_info = self.conn.replication.get_array_replication_capabilities( + array_id=self.conn.array_id) self.assertEqual(9, len(rep_info.keys())) assert 'symmetrixId' in rep_info assert 'snapVxCapable' in rep_info @@ -449,7 +450,7 @@ def test_get_rdf_group(self): self.assertIn('remoteSymmetrix', rdfg_details) def test_get_rdf_group_list(self): - """Test get_migration_info.""" + """Test get_rdf_group_list.""" rdf_number = self.replication.get_rdf_group_list() self.assertIsInstance(rdf_number, list) diff --git a/PyU4V/tests/ci_tests/test_pyu4v_ci_system.py b/PyU4V/tests/ci_tests/test_pyu4v_ci_system.py index a1c048a1..5396b6b9 100644 --- a/PyU4V/tests/ci_tests/test_pyu4v_ci_system.py +++ b/PyU4V/tests/ci_tests/test_pyu4v_ci_system.py @@ -502,3 +502,93 @@ def test_download_audit_log_record_return_binary(self): self.assertTrue(response.get(SUCCESS)) self.assertIn(BINARY_DATA, response.keys()) self.assertIsInstance(response.get(BINARY_DATA), bytes) + + def test_get_director_list(self): + """Test get_director_list.""" + response = self.system.get_director_list() + self.assertTrue(response) + self.assertIsInstance(response, list) + + def test_get_director_list_iscsi_only(self): + """Test get_director_list iscsi_only set as True.""" + response = self.system.get_director_list(iscsi_only=True) + self.assertTrue(response) + self.assertIsInstance(response, list) + for pmax_dir in response: + self.assertIn('SE', pmax_dir) + + def test_get_director_port_list(self): + """Test get_director_port_list.""" + director_list = self.system.get_director_list() + director_id = random.choice(director_list) + response = self.system.get_director_port_list(director_id=director_id) + self.assertTrue(response) + self.assertIsInstance(response, list) + + def test_get_director_port_list_iscsi_target_set(self): + """Test get_director_port_list with iscsi_only set as True.""" + iscsi_dir_list = self.system.get_director_list(iscsi_only=True) + if not iscsi_dir_list: + self.skipTest('No iSCSI Directors available in CI environment.') + director_id = random.choice(iscsi_dir_list) + + all_ports = self.system.get_director_port_list( + director_id=director_id) + self.assertTrue(all_ports) + self.assertIsInstance(all_ports, list) + + tgt_ports = self.system.get_director_port_list( + director_id=director_id, iscsi_target=True) + self.assertTrue(tgt_ports) + self.assertIsInstance(tgt_ports, list) + + not_tgt_ports = self.system.get_director_port_list( + director_id=director_id, iscsi_target=False) + self.assertTrue(not_tgt_ports) + self.assertIsInstance(not_tgt_ports, list) + + self.assertEqual(len(all_ports), + (len(tgt_ports) + len(not_tgt_ports))) + + def test_get_ip_interface_list(self): + """Test get_ip_interface_list.""" + iscsi_dir_list = self.system.get_director_list(iscsi_only=True) + if not iscsi_dir_list: + self.skipTest('No iSCSI Directors available in CI environment.') + director_id = random.choice(iscsi_dir_list) + + iscsi_port_list = self.system.get_director_port_list( + director_id=director_id, iscsi_target=False) + if not iscsi_port_list: + self.skipTest('No IP interface ports available in CI environment.') + port = random.choice(iscsi_port_list) + port_id = port.get('portId') + + ip_interface_list = self.system.get_ip_interface_list( + director_id=director_id, port_id=port_id) + self.assertTrue(ip_interface_list) + self.assertIsInstance(ip_interface_list, list) + + def test_get_ip_interface(self): + """Test get_ip_interface.""" + iscsi_dir_list = self.system.get_director_list(iscsi_only=True) + if not iscsi_dir_list: + self.skipTest('No iSCSI Directors available in CI environment.') + director_id = random.choice(iscsi_dir_list) + + iscsi_port_list = self.system.get_director_port_list( + director_id=director_id, iscsi_target=False) + if not iscsi_port_list: + self.skipTest('No IP interface ports available in CI environment.') + port = random.choice(iscsi_port_list) + port_id = port.get('portId') + + ip_interface_list = self.system.get_ip_interface_list( + director_id=director_id, port_id=port_id) + ip_interface = random.choice(ip_interface_list) + + ip_interface_details = self.system.get_ip_interface( + director_id=director_id, port_id=port_id, + interface_id=ip_interface) + self.assertTrue(ip_interface_details) + self.assertIsInstance(ip_interface_details, dict) diff --git a/PyU4V/tests/ci_tests/test_pyu4v_ci_wlp.py b/PyU4V/tests/ci_tests/test_pyu4v_ci_wlp.py index 99e1129c..57760d2c 100644 --- a/PyU4V/tests/ci_tests/test_pyu4v_ci_wlp.py +++ b/PyU4V/tests/ci_tests/test_pyu4v_ci_wlp.py @@ -70,3 +70,17 @@ def test_get_headroom_info_workload_exception(self): exception.ResourceNotFoundException, self.conn.wlp.get_headroom, self.conn.array_id, srp='SRP_1', slo='Diamond', workload='OLTP') + + def test_get_capabilities_no_array_set(self): + """Test get_capabilities, no array set all local arrays returned.""" + response = self.conn.wlp.get_capabilities() + self.assertTrue(response) + self.assertIsInstance(response, list) + + def test_get_capabilities_array_set(self): + """Test get_capabilities, array set only one array returned.""" + response = self.conn.wlp.get_capabilities(array_id=self.conn.array_id) + self.assertTrue(response) + self.assertIsInstance(response, list) + self.assertEqual(1, len(response)) + self.assertEqual(self.conn.array_id, response[0].get('symmetrixId')) diff --git a/PyU4V/tests/unit_tests/pyu4v_common_data.py b/PyU4V/tests/unit_tests/pyu4v_common_data.py index 4f2584ee..f9d63c49 100644 --- a/PyU4V/tests/unit_tests/pyu4v_common_data.py +++ b/PyU4V/tests/unit_tests/pyu4v_common_data.py @@ -320,6 +320,39 @@ class CommonData(object): storagegroup_name_1, storagegroup_name_2]} + array_slo_details = { + 'default_fba_srp': srp, + 'symmetrixId': array, + 'system_capacity': { + 'usable_total_tb': 61.12, 'subscribed_total_tb': 149.99, + 'subscribed_allocated_tb': 3.73, 'snapshot_total_tb': 1492.07, + 'subscribed_usable_capacity_percent': 246.0, + 'usable_used_tb': 6.24, 'snapshot_modified_tb': 0.0}, + 'physicalCapacity': { + 'used_capacity_gb': 76290.38, 'total_capacity_gb': 76290.38}, + 'host_visible_device_count': 6848, + 'system_efficiency': { + 'overall_efficiency_ratio_to_one': 445.1, + 'virtual_provisioning_savings_ratio_to_one': 40.6, + 'data_reduction_enabled_percent': 0.0, + 'pattern_detection_savings_tb': 0.0, + 'drr_on_reducible_only_to_one': 0.0, + 'snapshot_savings_ratio_to_one': 1.14096688E7, + 'deduplication_and_compression_savings_tb': 0.0, + 'unreducible_data_tb': 0.0, 'reducible_data_tb': 0.0}, + 'sloCompliance': { + 'slo_marginal': 0, 'no_slo': 157, 'slo_stable': 251, + 'slo_critical': 9}, + 'meta_data_usage': { + 'backend_meta_data_used_percent': 47.0, + 'replication_cache_used_percent': 0, + 'system_meta_data_used_percent': 34.0, + 'frontend_meta_data_used_percent': 13.0}, + 'model': 'PowerMax_2000', + 'ucode': '5978.669.669', + 'device_count': 7168, + 'local': True} + # replication capabilities = {'symmetrixCapability': [{'rdfCapable': True, 'snapVxCapable': True, @@ -531,11 +564,33 @@ class CommonData(object): 'minor_acknowledged_count': 0, 'normal_acknowledged_count': 0}, 'symmAlertSummary': list()} + + ip_interface_address = '192.168.0.3' + ip_interface_address_network = '192.168.0.3-9' + ip_interface_list = {'ipInterfaceId': [ip_interface_address_network]} + ip_interface_details = { + 'ip_interface_id': ip_interface_address_network, 'network_id': 9, + 'ip_prefix_length': 24, 'vlan_id': 0, 'mtu': 1500, + 'iscsi_target_director': director_id2, 'iscsi_target_port': 0, + 'ip_address': ip_interface_address} + # wlp wlp_info = {'symmetrixId': array, 'lastProcessed': 1569417000000, 'nextUpdate': 795} headroom_array = {'gbHeadroom': [{'srpId': 'SRP_TEST', 'emulation': 'FBA', 'capacity': 58555.9}]} + wlp_capabilities = {'symmetrixCapability': [ + {'symmetrixId': array, 'workloadDetailCapable': True, + 'componentUtilizationCapable': True, 'characterizationCapable': True, + 'headroomGbCapable': True, 'suitabilityTestCapable': True, + 'provisioningTemplateCapable': True, 'slComplianceCapable': True, + 'headroomIopsCapable': True}, + {'symmetrixId': remote_array, 'workloadDetailCapable': True, + 'componentUtilizationCapable': True, + 'characterizationCapable': True, + 'headroomGbCapable': True, 'suitabilityTestCapable': True, + 'provisioningTemplateCapable': True, 'slComplianceCapable': True, + 'headroomIopsCapable': True}]} # iterator iterator_page = {'result': [{'volumeId': '00002'}], 'from': 2, 'to': 2} diff --git a/PyU4V/tests/unit_tests/pyu4v_fakes.py b/PyU4V/tests/unit_tests/pyu4v_fakes.py index 4c83c5f0..05c67eb1 100644 --- a/PyU4V/tests/unit_tests/pyu4v_fakes.py +++ b/PyU4V/tests/unit_tests/pyu4v_fakes.py @@ -143,7 +143,11 @@ def _get_request(self, url, params): else: return_object = self.data.slo_list else: - return_object = self.data.symm_list + url_components = url.split('/') + if 'symmetrix' == url_components[-2]: + return_object = self.data.array_slo_details + else: + return_object = self.data.symm_list elif 'replication' in url: return_object = self._replication(url) @@ -190,7 +194,13 @@ def _get_request(self, url, params): return_object = self.data.vol_with_pages elif 'wlp' in url: - return_object = self.data.wlp_info + uri_components = url.split('/') + if 'symmetrix' == uri_components[-2]: + return_object = self.data.wlp_info + if 'headroom' == uri_components[-1]: + return_object = self.data.headroom_array + if 'capabilities' == uri_components[-2]: + return_object = self.data.wlp_capabilities elif 'version' in url: return_object = self.data.server_version @@ -418,6 +428,11 @@ def _system_port(self, url): in url): return_object = port + if 'ipinterface' in uri_components[-1]: + return_object = self.data.ip_interface_list + elif 'ipinterface' in uri_components[-2]: + return_object = self.data.ip_interface_details + return return_object def _system_alert_summary(self): @@ -521,6 +536,8 @@ def _performance_call(self, url): return_object = self.p_data.ip_interface_keys elif performance_section == pc.ISCSI_TGT: return_object = self.p_data.iscsi_target_keys + elif performance_section == pc.MV: + return_object = self.p_data.masking_view_keys elif performance_section == pc.PG: return_object = self.p_data.port_group_keys elif performance_section == pc.RDFA: diff --git a/PyU4V/tests/unit_tests/pyu4v_performance_data.py b/PyU4V/tests/unit_tests/pyu4v_performance_data.py index b039d6c0..227e5a57 100644 --- a/PyU4V/tests/unit_tests/pyu4v_performance_data.py +++ b/PyU4V/tests/unit_tests/pyu4v_performance_data.py @@ -204,6 +204,11 @@ class PerformanceData(object): 'firstAvailableDate': first_date, 'lastAvailableDate': last_date}]} + masking_view_id = 'test-mv' + masking_view_keys = {'maskingViewInfo': [{'maskingViewId': iscsi_target_id, + 'firstAvailableDate': first_date, + 'lastAvailableDate': last_date}]} + port_group_id = 'test-pg' port_group_keys = {'portGroupInfo': [{'portGroupId': port_group_id, 'firstAvailableDate': first_date, diff --git a/PyU4V/tests/unit_tests/test_pyu4v_migration.py b/PyU4V/tests/unit_tests/test_pyu4v_migration.py index 0f9f9b64..2d4a1127 100644 --- a/PyU4V/tests/unit_tests/test_pyu4v_migration.py +++ b/PyU4V/tests/unit_tests/test_pyu4v_migration.py @@ -49,7 +49,8 @@ def tearDown(self): def test_get_migration_info(self): """Test get_migration_info.""" - migration_info = self.migration.get_migration_info() + migration_info = self.migration.get_migration_info( + array_id=self.data.array) self.assertEqual(self.data.migration_info, migration_info) def test_create_migration_environment(self): @@ -75,7 +76,8 @@ def test_delete_migration_environment(self): def test_get_array_migration_capabilities(self): """Test get_array_migration_capabilities.""" - capabilities = self.migration.get_array_migration_capabilities() + capabilities = self.migration.get_array_migration_capabilities( + array_id=self.data.array) capabilities_ref = ( self.data.migration_capabilities['storageArrayCapability'][0]) self.assertEqual(capabilities_ref, capabilities) diff --git a/PyU4V/tests/unit_tests/test_pyu4v_performance.py b/PyU4V/tests/unit_tests/test_pyu4v_performance.py index 581933a7..2030a2ca 100644 --- a/PyU4V/tests/unit_tests/test_pyu4v_performance.py +++ b/PyU4V/tests/unit_tests/test_pyu4v_performance.py @@ -1275,6 +1275,21 @@ def test_get_iscsi_target_stats(self): self.assertEqual(response.get('reporting_level'), self.common.convert_to_snake_case(pc.ISCSI_TGT)) + def test_get_masking_view_keys(self): + """Test get_masking_view_keys.""" + response = self.perf.get_masking_view_keys() + self.assertIsInstance(response, list) + self.assertTrue(response[0].get(pc.MV_ID)) + + def test_get_masking_view_stats(self): + """Test get_masking_view_stats.""" + response = self.perf.get_masking_view_stats( + masking_view_id=self.p_data.masking_view_id, + metrics=pc.KPI, start_time=self.time_now, end_time=self.time_now) + self.assertIsInstance(response, dict) + self.assertEqual(response.get('reporting_level'), + self.common.convert_to_snake_case(pc.MV)) + def test_get_port_group_keys(self): """Test get_port_group_keys.""" response = self.perf.get_port_group_keys() diff --git a/PyU4V/tests/unit_tests/test_pyu4v_provisioning.py b/PyU4V/tests/unit_tests/test_pyu4v_provisioning.py index a18fd132..abb35b10 100644 --- a/PyU4V/tests/unit_tests/test_pyu4v_provisioning.py +++ b/PyU4V/tests/unit_tests/test_pyu4v_provisioning.py @@ -52,6 +52,12 @@ def tearDown(self): pf.FakeConfigFile.delete_fake_config_file( self.conf_file, self.conf_dir) + def test_get_array(self): + """Test get_array.""" + array_id = self.data.array + array_details = self.provisioning.get_array(array_id=array_id) + self.assertEqual(self.data.array_slo_details, array_details) + def test_get_director(self): """Test get_director.""" dir_details = self.provisioning.get_director(self.data.director_id1) diff --git a/PyU4V/tests/unit_tests/test_pyu4v_replication.py b/PyU4V/tests/unit_tests/test_pyu4v_replication.py index 06dc7589..8db92b21 100644 --- a/PyU4V/tests/unit_tests/test_pyu4v_replication.py +++ b/PyU4V/tests/unit_tests/test_pyu4v_replication.py @@ -56,7 +56,8 @@ def test_get_replication_info(self): def test_check_snap_capabilities(self): """Test get_array_replication_capabilities.""" - capabilities = self.replication.get_array_replication_capabilities() + capabilities = self.replication.get_array_replication_capabilities( + array_id=self.data.array) self.assertEqual( self.data.capabilities['symmetrixCapability'][1], capabilities) diff --git a/PyU4V/tests/unit_tests/test_pyu4v_system.py b/PyU4V/tests/unit_tests/test_pyu4v_system.py index 17e1a2fb..85ba6c0c 100644 --- a/PyU4V/tests/unit_tests/test_pyu4v_system.py +++ b/PyU4V/tests/unit_tests/test_pyu4v_system.py @@ -476,3 +476,55 @@ def test_download_audit_log_record_write_file_no_name(self, mck_write): mck_write.assert_called_once() self.assertTrue(response[SUCCESS]) self.assertIn('/test/test.pdf', str(response[AUDIT_RECORD_PATH])) + + def test_get_director_list(self): + """Test get_director_list.""" + array_id = self.data.array + dir_list = self.system.get_director_list(array_id=array_id) + self.assertTrue(dir_list) + self.assertIsInstance(dir_list, list) + self.assertEqual([self.data.director_id1, self.data.director_id2], + dir_list) + + def test_get_iscsi_director_list(self): + """Test get_director_list iscsi_only set to True.""" + array_id = self.data.array + iscsi_dir_list = self.system.get_director_list( + array_id=array_id, iscsi_only=True) + self.assertTrue(iscsi_dir_list) + self.assertIsInstance(iscsi_dir_list, list) + self.assertEqual([self.data.director_id2], iscsi_dir_list) + + def test_get_director_port_list(self): + """Test get_director_port_list.""" + director_id = self.data.director_id1 + dir_port_list = self.system.get_director_port_list( + director_id=director_id, iscsi_target=False) + self.assertTrue(dir_port_list) + self.assertIsInstance(dir_port_list, list) + self.assertEqual(self.data.port_key_list.get('symmetrixPortKey'), + dir_port_list) + + def test_get_ip_interface_list(self): + """Test get_ip_interface_list""" + director_id = self.data.director_id2 + port_id = 0 + ip_int_list = self.system.get_ip_interface_list( + director_id=director_id, port_id=port_id) + self.assertTrue(ip_int_list) + self.assertIsInstance(ip_int_list, list) + self.assertEqual(self.data.ip_interface_list.get('ipInterfaceId'), + ip_int_list) + + def test_get_ip_interface(self): + """Test get_ip_interface.""" + director_id = self.data.director_id2 + port_id = 0 + interface_id = self.data.ip_interface_address_network + ip_int_info = self.system.get_ip_interface( + director_id=director_id, port_id=port_id, + interface_id=interface_id) + + self.assertTrue(ip_int_info) + self.assertIsInstance(ip_int_info, dict) + self.assertEqual(self.data.ip_interface_details, ip_int_info) diff --git a/PyU4V/tests/unit_tests/test_pyu4v_wlp.py b/PyU4V/tests/unit_tests/test_pyu4v_wlp.py index de6f6b04..d7254256 100644 --- a/PyU4V/tests/unit_tests/test_pyu4v_wlp.py +++ b/PyU4V/tests/unit_tests/test_pyu4v_wlp.py @@ -87,3 +87,18 @@ def test_get_headroom_fail(self): self.data.workload) self.assertFalse(headroom) self.assertIsInstance(headroom, list) + + def test_get_capabilities_with_array_id(self): + """Test get_capabilities with array_id specified.""" + array_id = self.data.array + wlp_cap = self.wlp.get_capabilities(array_id=array_id) + self.assertTrue(wlp_cap) + self.assertIsInstance(wlp_cap, list) + self.assertEqual(1, len(wlp_cap)) + + def test_get_capabilities_without_array_id(self): + """Test get_capabilities with no array_id specified.""" + wlp_cap = self.wlp.get_capabilities() + self.assertTrue(wlp_cap) + self.assertIsInstance(wlp_cap, list) + self.assertEqual(2, len(wlp_cap)) diff --git a/PyU4V/utils/constants.py b/PyU4V/utils/constants.py index 11fe84a9..64b94bad 100644 --- a/PyU4V/utils/constants.py +++ b/PyU4V/utils/constants.py @@ -39,7 +39,7 @@ APP_MPART = 'multipart/form-data' # Unisphere REST URI constants -PYU4V_VERSION = '9.2.0.2' +PYU4V_VERSION = '9.2.0.3' UNISPHERE_VERSION = '92' VERSION = 'version' ITERATOR = 'Iterator' @@ -141,6 +141,8 @@ SYMMETRIX_PORT_KEY = 'symmetrixPortKey' NUM_OF_PORTS = 'num_of_ports' NUM_OF_CORES = 'num_of_cores' +IP_INTERFACE = 'ipinterface' +IP_INTERFACE_ID = 'ipInterfaceId' # Host constants HOST_ID = 'hostId' diff --git a/PyU4V/utils/performance_constants.py b/PyU4V/utils/performance_constants.py index aca0c44b..d7d73b52 100644 --- a/PyU4V/utils/performance_constants.py +++ b/PyU4V/utils/performance_constants.py @@ -141,6 +141,9 @@ ISCSI_TGT_ID_KEY = 'iscsiTargetId' ISCSI_TGT_ID_METRICS = 'iSCSITargetId' ISCSI_TGT_INFO = 'iSCSITargetInfo' +MV = 'MaskingView' +MV_ID = 'maskingViewId' +MV_INFO = 'maskingViewInfo' PG = 'PortGroup' PG_ID = 'portGroupId' PG_INFO = 'portGroupInfo' diff --git a/PyU4V/workload_planner.py b/PyU4V/workload_planner.py index 056398b3..1cb99ea1 100644 --- a/PyU4V/workload_planner.py +++ b/PyU4V/workload_planner.py @@ -73,3 +73,23 @@ def get_headroom(self, array_id, workload=None, srp=None, slo=None): resource_level=SYMMETRIX, resource_level_id=array_id, resource_type=HEADROOM, params=params) return response.get(GB_HEADROOM, list()) if response else list() + + def get_capabilities(self, array_id=None): + """Generate WLP capability list for each WLP authorized array. + + :param array_id: array id -- str + :returns: array WLP capabilities -- list + """ + return_response = list() + response = self.common.get_resource( + category=WLP, resource_level='capabilities', + resource_type=SYMMETRIX) + if array_id: + for wlp_info in response.get('symmetrixCapability'): + if wlp_info.get('symmetrixId') == array_id: + return_response = [wlp_info] + break + else: + return_response = response.get('symmetrixCapability', list()) + + return return_response diff --git a/README.rst b/README.rst index 2b2cc0a7..259fc27b 100644 --- a/README.rst +++ b/README.rst @@ -26,7 +26,7 @@ PyU4V Version 9.2 +-------------------------------+----------------------------+ | **Author** | Dell EMC | +-------------------------------+----------------------------+ -| **PyU4V Version** | 9.2.0.2 | +| **PyU4V Version** | 9.2.0.3 | +-------------------------------+----------------------------+ | **Minimum Unisphere Version** | 9.2.0.1 | +-------------------------------+----------------------------+ @@ -80,7 +80,7 @@ specifying ``PyU4V`` as the install package for ``pip``:: $ pip install PyU4V # Install a specific version - $ pip install PyU4V==9.2.0.2 + $ pip install PyU4V==9.2.0.3 Copy the sample ``PyU4V.conf`` provided with PyU4V to either your working directory or within a directory named ``.PyU4V`` in your current users home diff --git a/docs/source/conf.py b/docs/source/conf.py index 1b744539..f712728d 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -26,7 +26,7 @@ # The short X.Y version. version = u'9.2' # The full version, including alpha/beta/rc tags -release = '9.2.0.2' +release = '9.2.0.3' # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: diff --git a/docs/source/index.rst b/docs/source/index.rst index 30de5e99..a72e7c9c 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -35,7 +35,7 @@ Supported PyU4V Versions ------------------------ +-------------------------------+----------------------------------------+ -| **PyU4V Version** | 9.2.0.2 | +| **PyU4V Version** | 9.2.0.3 | +-------------------------------+----------------------------------------+ | **Minimum Unisphere Version** | 9.2.0.1 | +-------------------------------+----------------------------------------+ diff --git a/docs/source/installation.rst b/docs/source/installation.rst index e3cecc00..d074cf6b 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -5,7 +5,7 @@ Requirements ------------ +-------------------------------+----------------------------------------+ -| **PyU4V Version** | 9.2.0.2 | +| **PyU4V Version** | 9.2.0.3 | +-------------------------------+----------------------------------------+ | **Minimum Unisphere Version** | 9.2.0.1 | +-------------------------------+----------------------------------------+ @@ -66,7 +66,7 @@ specifying ``PyU4V`` as the install package for ``pip``: $ pip install PyU4V # Install a specific version - $ pip install PyU4V==9.2.0.2 + $ pip install PyU4V==9.2.0.3 .. URL LINKS diff --git a/setup.py b/setup.py index af1bef3d..2d50f13b 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ setuptools.setup( name='PyU4V', - version='9.2.0.2', + version='9.2.0.3', url='https://github.com/dell/PyU4V/', author='Dell Inc. or its subsidiaries', author_email='Michael.Mcaleer@dell.com',