diff --git a/delfin/drivers/dell_emc/vplex/alert_handler.py b/delfin/drivers/dell_emc/vplex/alert_handler.py index 4215e6b05..b19cebe63 100644 --- a/delfin/drivers/dell_emc/vplex/alert_handler.py +++ b/delfin/drivers/dell_emc/vplex/alert_handler.py @@ -12,11 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. import hashlib -import time from oslo_log import log -from delfin import exception +from delfin import exception, utils from delfin.common import constants from delfin.i18n import _ @@ -49,8 +48,7 @@ def parse_alert(context, alert): constants.Severity.INFORMATIONAL) alert_model['category'] = constants.Category.FAULT alert_model['type'] = constants.EventType.EQUIPMENT_ALARM - occur_time = int(time.time()) * AlertHandler.SECONDS_TO_MS - alert_model['occur_time'] = occur_time + alert_model['occur_time'] = utils.utcnow_ms() alert_model['description'] = description alert_model['resource_type'] = constants.DEFAULT_RESOURCE_TYPE alert_model['location'] = '' diff --git a/delfin/drivers/dell_emc/vplex/consts.py b/delfin/drivers/dell_emc/vplex/consts.py index 78866c0ea..f22edf481 100644 --- a/delfin/drivers/dell_emc/vplex/consts.py +++ b/delfin/drivers/dell_emc/vplex/consts.py @@ -11,7 +11,52 @@ # 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. +from delfin.common import constants SOCKET_TIMEOUT = 10 BASE_CONTEXT = '/vplex' REST_AUTH_URL = '/vplex/clusters' + +PORT_TYPE_MAP = { + 'fc': constants.PortType.FC, + 'iscsi': constants.PortType.ISCSI, + 'ficon': constants.PortType.FICON, + 'fcoe': constants.PortType.FCOE, + 'eth': constants.PortType.ETH, + 'sas': constants.PortType.SAS, + 'ib': constants.PortType.IB, + 'other': constants.PortType.OTHER, +} +PORT_LOGICAL_TYPE_MAP = { + 'front-end': constants.PortLogicalType.FRONTEND, + 'back-end': constants.PortLogicalType.BACKEND, + 'service': constants.PortLogicalType.SERVICE, + 'management': constants.PortLogicalType.MANAGEMENT, + 'internal': constants.PortLogicalType.INTERNAL, + 'maintenance': constants.PortLogicalType.MAINTENANCE, + 'inter-director-communication': constants.PortLogicalType.INTERCONNECT, + 'other': constants.PortLogicalType.OTHER, + 'local-com': constants.PortLogicalType.INTERCLUSTER, + 'wan-com': constants.PortLogicalType.CLUSTER_MGMT +} +PORT_CONNECT_STATUS_MAP = { + 'up': constants.PortConnectionStatus.CONNECTED, + 'down': constants.PortConnectionStatus.DISCONNECTED, + 'no-link': constants.PortConnectionStatus.UNKNOWN, + 'ok': constants.PortConnectionStatus.CONNECTED, + 'pending': constants.PortConnectionStatus.CONNECTED, + 'suspended': constants.PortConnectionStatus.DISCONNECTED, + 'hardware error': constants.PortConnectionStatus.UNKNOWN +} +PORT_HEALTH_STATUS_MAP = { + 'ok': constants.PortHealthStatus.NORMAL, + 'error': constants.PortHealthStatus.ABNORMAL, + 'stopped': constants.PortHealthStatus.UNKNOWN +} +CONTROLLER_STATUS_MAP = { + "ok": constants.ControllerStatus.NORMAL, + "busy": constants.ControllerStatus.NORMAL, + "no contact": constants.ControllerStatus.OFFLINE, + "lost communication": constants.ControllerStatus.OFFLINE, + "unknown": constants.ControllerStatus.UNKNOWN +} diff --git a/delfin/drivers/dell_emc/vplex/rest_handler.py b/delfin/drivers/dell_emc/vplex/rest_handler.py index d810177a4..58fcb7d25 100644 --- a/delfin/drivers/dell_emc/vplex/rest_handler.py +++ b/delfin/drivers/dell_emc/vplex/rest_handler.py @@ -42,7 +42,8 @@ def login(self): if 'User authentication failed' in res.text: raise exception.InvalidUsernameOrPassword() else: - raise exception.BadResponse(res.text) + raise exception.StorageBackendException( + six.text_type(res.text)) except Exception as e: LOG.error("Login error: %s", six.text_type(e)) raise e @@ -124,3 +125,25 @@ def logout(self): err_msg = "Logout error: %s" % (six.text_type(e)) LOG.error(err_msg) raise e + + def get_engine_director_resp(self): + url = '%s/engines/*/directors/*' % consts.BASE_CONTEXT + response = self.get_rest_info(url) + return response + + def get_version_verbose(self): + url = '%s/version' % consts.BASE_CONTEXT + args = '-a --verbose' + data = {"args": args} + response = self.get_rest_info(url, data, method='POST') + return response + + def get_cluster_export_port_resp(self): + url = '%s/clusters/*/exports/ports/*' % consts.BASE_CONTEXT + response = self.get_rest_info(url) + return response + + def get_engine_director_hardware_port_resp(self): + url = '%s/engines/*/directors/*/hardware/ports/*' % consts.BASE_CONTEXT + response = self.get_rest_info(url) + return response diff --git a/delfin/drivers/dell_emc/vplex/vplex_stor.py b/delfin/drivers/dell_emc/vplex/vplex_stor.py index a6cdd9ffa..15a718af6 100644 --- a/delfin/drivers/dell_emc/vplex/vplex_stor.py +++ b/delfin/drivers/dell_emc/vplex/vplex_stor.py @@ -22,6 +22,7 @@ from delfin.drivers import driver from delfin.drivers.dell_emc.vplex import alert_handler from delfin.drivers.dell_emc.vplex import rest_handler +from delfin.drivers.dell_emc.vplex import consts LOG = log.getLogger(__name__) @@ -80,8 +81,7 @@ def get_storage(self, context): 'raw_capacity': int(raw_capacity), 'total_capacity': int(total_capacity), 'used_capacity': int(used_capacity), - 'free_capacity': int(free_capacity), - 'subscribed_capacity': 0 + 'free_capacity': int(free_capacity) } break return cluster @@ -324,16 +324,229 @@ def get_cluster_used_capacity(self, cluster_name): def list_controllers(self, context): """List all storage controllers from storage system.""" - pass + ct_list = [] + director_version_map = {} + version_resp = self.rest_handler.get_version_verbose() + all_director = self.rest_handler.get_engine_director_resp() + ct_context_list = VplexStorageDriver.get_context_list(all_director) + VplexStorageDriver.analyse_director_version(version_resp, + director_version_map) + for ct_context in ct_context_list: + ct_attr_map = ct_context.get("attributes") + communication_status = ct_attr_map.get('communication-status') + name = ct_attr_map.get('name') + ct = { + 'native_controller_id': ct_attr_map.get('director-id'), + 'name': name, + 'status': VplexStorageDriver.analyse_director_status( + communication_status), + 'location': '', + 'storage_id': self.storage_id, + 'soft_version': self.get_value_from_nest_map( + director_version_map, name, "Director Software"), + 'cpu_info': '', + 'memory_size': '' + } + ct_list.append(ct) + return ct_list def list_ports(self, context): """List all ports from storage system.""" - pass + port_list = [] + hardware_port_map = {} + hardware_port_resp = self.rest_handler. \ + get_engine_director_hardware_port_resp() + export_port_resp = self.rest_handler.get_cluster_export_port_resp() + VplexStorageDriver.analyse_hardware_port(hardware_port_resp, + hardware_port_map) + port_context_list = VplexStorageDriver. \ + get_context_list(export_port_resp) + for port_context in port_context_list: + port_attr = port_context.get('attributes') + port_name = port_attr.get('name') + export_status = port_attr.get('export-status') + speed, max_speed, protocols, role, port_status, \ + operational_status = self.get_hardware_port_info( + hardware_port_map, port_name, 'attributes') + connection_status = VplexStorageDriver.analyse_port_connect_status( + export_status) + port = { + 'native_port_id': port_attr.get('name'), + 'name': port_attr.get('name'), + 'type': VplexStorageDriver.analyse_port_type(protocols), + 'logical_type': VplexStorageDriver.analyse_port_logical_type( + role), + 'connection_status': connection_status, + 'health_status': VplexStorageDriver.analyse_port_health_status( + operational_status), + 'location': '', + 'storage_id': self.storage_id, + 'native_parent_id': port_attr.get('director-id'), + 'speed': VplexStorageDriver.analyse_speed(speed), + 'max_speed': VplexStorageDriver.analyse_speed(max_speed), + 'wwn': port_attr.get('port-wwn'), + 'mac_address': '', + 'ipv4': '', + 'ipv4_mask': '', + 'ipv6': '', + 'ipv6_mask': '' + } + port_list.append(port) + return port_list def list_disks(self, context): """List all disks from storage system.""" pass + @staticmethod + def get_context_list(response): + context_list = [] + if response: + contexts = response.get("context") + for context in contexts: + ct_type = context.get("type") + parent = context.get("parent") + attributes = context.get("attributes") + context_map = {} + attr_map = {} + for attribute in attributes: + key = attribute.get("name") + value = attribute.get("value") + attr_map[key] = value + context_map["type"] = ct_type + context_map["parent"] = parent + context_map["attributes"] = attr_map + context_list.append(context_map) + return context_list + + @staticmethod + def analyse_director_version(version_resp, director_version_map): + custom_data = version_resp.get('custom-data') + detail_arr = custom_data.split('\n') + director_name = '' + version_name = '' + for detail in detail_arr: + if detail is not None and detail != '': + if "For director" in detail: + match_obj = re.search( + r'For director.+?directors/(.*?):', detail) + if match_obj: + director_name = match_obj.group(1) + continue + if director_name: + if "What:" in detail: + match_obj = re.search(r'What:\s+(.+?)$', detail) + if match_obj: + version_name = match_obj.group(1) + continue + if version_name: + match_obj = re.search(r'Version:\s+(.+?)$', detail) + if match_obj: + version_value = match_obj.group(1) + if director_version_map.get(director_name): + director_version_map.get(director_name)[ + version_name] = version_value + else: + version_map = {} + version_map[version_name] = version_value + director_version_map[ + director_name] = version_map + + @staticmethod + def analyse_director_status(status): + return consts.CONTROLLER_STATUS_MAP. \ + get(status, constants.ControllerStatus.UNKNOWN) + + def get_director_specified_version(self, version_map, director_name, + specified_name): + version_value = '' + if version_map: + director_map = version_map.get(director_name) + if director_map: + version_value = director_map.get(specified_name) + return version_value + + def get_value_from_nest_map(self, nest_map, first_key, second_key): + final_value = '' + if nest_map: + second_map = nest_map.get(first_key) + if second_map: + final_value = second_map.get(second_key) + return final_value + + def get_hardware_port_info(self, nest_map, first_key, second_key): + speed = '' + max_speed = '' + protocols = [] + role = '' + port_status = '' + operational_status = '' + if nest_map: + second_map = nest_map.get(first_key) + if second_map: + third_map = second_map.get(second_key) + if third_map: + speed = third_map.get('current-speed') + max_speed = third_map.get('max-speed') + protocols = third_map.get('protocols') + role = third_map.get('role') + port_status = third_map.get('port-status') + operational_status = third_map.get('operational-status') + return (speed, max_speed, protocols, role, port_status, + operational_status) + + @staticmethod + def analyse_hardware_port(resp, hardware_port_map): + port_list = VplexStorageDriver.get_context_list(resp) + if port_list: + for port in port_list: + port_attr = port.get("attributes") + if port_attr: + port_name = port_attr.get("target-port") + hardware_port_map[port_name] = port + + @staticmethod + def analyse_port_type(protocols): + port_type = constants.PortType.OTHER + if protocols: + for protocol in protocols: + port_type_value = consts.PORT_TYPE_MAP.get(protocol) + if port_type_value: + port_type = port_type_value + break + return port_type + + @staticmethod + def analyse_port_logical_type(role): + return consts.PORT_LOGICAL_TYPE_MAP. \ + get(role, constants.PortLogicalType.OTHER) + + @staticmethod + def analyse_port_connect_status(status): + return consts.PORT_CONNECT_STATUS_MAP. \ + get(status, constants.PortConnectionStatus.UNKNOWN) + + @staticmethod + def analyse_port_health_status(status): + return consts.PORT_HEALTH_STATUS_MAP. \ + get(status, constants.PortHealthStatus.UNKNOWN) + + @staticmethod + def analyse_speed(speed_value): + speed = None + if speed_value: + match_obj = re.search(r'([1-9]\d*\.?\d*)|(0\.\d*[1-9])', + speed_value) + if match_obj: + speed = int(match_obj.group(0)) + if 'Gbit' in speed_value: + speed = speed * units.G + elif 'Mbit' in speed_value: + speed = speed * units.M + elif 'Kbit' in speed_value: + speed = speed * units.k + return speed + @staticmethod def handle_detail_list(detail_info, detail_map, split): diff --git a/delfin/tests/unit/drivers/dell_emc/vplex/test_emc_vplex.py b/delfin/tests/unit/drivers/dell_emc/vplex/test_emc_vplex.py index 1a4c3d7f2..4cd6f9e42 100644 --- a/delfin/tests/unit/drivers/dell_emc/vplex/test_emc_vplex.py +++ b/delfin/tests/unit/drivers/dell_emc/vplex/test_emc_vplex.py @@ -208,8 +208,7 @@ 'raw_capacity': 12754334882201, 'total_capacity': 11654823254425, 'used_capacity': 8983009998929, - 'free_capacity': 2671813255496, - 'subscribed_capacity': 0 + 'free_capacity': 2671813255496 } GET_ALL_STORAGE_VOLUME_SUMMARY = { "custom-data": "Capacity total 11.6T\n\n" @@ -221,6 +220,181 @@ GET_ALL_LUNS_SUMMARY = { "custom-data": "Total virtual-volume capacity is 8.17T." } +GET_ALL_ENGINE_DIRECTOR = { + "context": [ + { + "type": "director", + "parent": "/engines/engine-1-1/directors", + "attributes": [ + { + "name": "director-id", + "value": "0x00000000472029e9" + }, + { + "name": "communication-status", + "value": "ok" + }, + { + "name": "name", + "value": "director-1-1-A" + } + ] + } + ] +} +controllers_result = [ + { + 'native_controller_id': '0x00000000472029e9', + 'name': 'director-1-1-A', + 'status': 'normal', + 'location': '', + 'storage_id': '12345', + 'soft_version': '161.1.0.78.0', + 'cpu_info': '', + 'memory_size': '' + } +] + +GET_VERSION_VERBOSE = { + "context": None, + "message": "getsysinfo", + "exception": None, + "custom-data": "What: Mgmt Server Software\nVersion: 161.1.0.78\n " + "For director /engines/engine-1-1/directors/director-1-1-A:" + "\n " + "What: O/S\n " + "Version: 161.1.0.11 (SLES11)\n\n " + "What: NSFW\n " + "Version: 161.1.0.78.0\n\n " + "What: ZPEM\n " + "Version: 161.1.0.78.0-0\n " + "What: Director Software\n " + "Version: 161.1.0.78.0\n\n " + "What: SSD Model: P30056-0000000000000 000000000\n " + "Version: 0005\n" +} + +GET_ALL_CLUSTER_EXPORT_PORT = { + "context": [ + { + "type": "fc-target-port", + "parent": "/clusters/cluster-1/exports/ports", + "attributes": [ + { + "name": "director-id", + "value": "0x00000000472029e9" + }, + { + "name": "enabled", + "value": "true" + }, + { + "name": "export-status", + "value": "ok" + }, + { + "name": "name", + "value": "P00000000472029E9-A0-FC00" + }, + { + "name": "node-wwn", + "value": "0x50001440472029e9" + }, + { + "name": "port-id", + "value": None + }, + { + "name": "port-wwn", + "value": "0x500014428029e900" + } + ] + } + ] +} + +GET_ALL_ENGINE_DIRECTOR_HARDWARE_PORT = { + "context": [ + { + "type": "fc-port", + "parent": "/engines/engine-1-1/directors/director-1-1-A/" + "hardware/ports", + "attributes": [ + { + "name": "address", + "value": "0x500014428029e900" + }, + { + "name": "current-speed", + "value": "8Gbits/s" + }, + { + "name": "enabled", + "value": "true" + }, + { + "name": "max-speed", + "value": "8Gbits/s" + }, + { + "name": "name", + "value": "A0-FC00" + }, + { + "name": "node-wwn", + "value": "0x50001440472029e9" + }, + { + "name": "operational-status", + "value": "ok" + }, + { + "name": "port-status", + "value": "up" + }, + { + "name": "port-wwn", + "value": "0x500014428029e900" + }, + { + "name": "protocols", + "value": [ + "fc" + ] + }, + { + "name": "role", + "value": "front-end" + }, + { + "name": "target-port", + "value": "P00000000472029E9-A0-FC00" + } + ] + } + ] +} +ports_result = [ + { + 'native_port_id': 'P00000000472029E9-A0-FC00', + 'name': 'P00000000472029E9-A0-FC00', + 'type': 'fc', + 'logical_type': 'frontend', + 'connection_status': 'connected', + 'health_status': 'normal', + 'location': '', + 'storage_id': '12345', + 'native_parent_id': '0x00000000472029e9', + 'speed': 8000000000, + 'max_speed': 8000000000, + 'wwn': '0x500014428029e900', + 'mac_address': '', + 'ipv4': '', + 'ipv4_mask': '', + 'ipv6': '', + 'ipv6_mask': '' + } +] class TestVplexStorDriver(TestCase): @@ -265,3 +439,20 @@ def test_list_alerts(self): VplexStorageDriver(**ACCESS_INFO).list_alerts(context) self.assertEqual('list_alerts is not supported in model VPLEX', str(exc.exception)) + + @mock.patch.object(RestHandler, 'get_version_verbose') + @mock.patch.object(RestHandler, 'get_engine_director_resp') + def test_list_controller(self, mock_controller, mocke_version): + mocke_version.return_value = GET_VERSION_VERBOSE + mock_controller.return_value = GET_ALL_ENGINE_DIRECTOR + controllers = VplexStorageDriver(**ACCESS_INFO). \ + list_controllers(context) + self.assertDictEqual(controllers[0], controllers_result[0]) + + @mock.patch.object(RestHandler, 'get_cluster_export_port_resp') + @mock.patch.object(RestHandler, 'get_engine_director_hardware_port_resp') + def test_list_port(self, mock_hardware_port, mock_export_port): + mock_hardware_port.return_value = GET_ALL_ENGINE_DIRECTOR_HARDWARE_PORT + mock_export_port.return_value = GET_ALL_CLUSTER_EXPORT_PORT + ports = VplexStorageDriver(**ACCESS_INFO).list_ports(context) + self.assertDictEqual(ports[0], ports_result[0])