From 7f242eb75550ad7a63d366508f4c58a8cfa94bb7 Mon Sep 17 00:00:00 2001 From: liuxh Date: Thu, 18 Mar 2021 17:12:45 +0800 Subject: [PATCH 1/2] =?UTF-8?q?vplex=E4=BB=A3=E7=A0=81=E5=90=88=E5=B9=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- delfin/drivers/dell_emc/vplex/__init__.py | 0 .../drivers/dell_emc/vplex/alert_handler.py | 65 ++++ delfin/drivers/dell_emc/vplex/consts.py | 17 + delfin/drivers/dell_emc/vplex/rest_handler.py | 126 +++++++ delfin/drivers/dell_emc/vplex/vplex_stor.py | 334 ++++++++++++++++++ .../unit/drivers/dell_emc/vplex/__init__.py | 0 .../drivers/dell_emc/vplex/test_emc_vplex.py | 0 7 files changed, 542 insertions(+) create mode 100644 delfin/drivers/dell_emc/vplex/__init__.py create mode 100644 delfin/drivers/dell_emc/vplex/alert_handler.py create mode 100644 delfin/drivers/dell_emc/vplex/consts.py create mode 100644 delfin/drivers/dell_emc/vplex/rest_handler.py create mode 100644 delfin/drivers/dell_emc/vplex/vplex_stor.py create mode 100644 delfin/tests/unit/drivers/dell_emc/vplex/__init__.py create mode 100644 delfin/tests/unit/drivers/dell_emc/vplex/test_emc_vplex.py diff --git a/delfin/drivers/dell_emc/vplex/__init__.py b/delfin/drivers/dell_emc/vplex/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/delfin/drivers/dell_emc/vplex/alert_handler.py b/delfin/drivers/dell_emc/vplex/alert_handler.py new file mode 100644 index 000000000..4215e6b05 --- /dev/null +++ b/delfin/drivers/dell_emc/vplex/alert_handler.py @@ -0,0 +1,65 @@ +# Copyright 2021 The SODA Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http:#www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. +import hashlib +import time + +from oslo_log import log + +from delfin import exception +from delfin.common import constants +from delfin.i18n import _ + +LOG = log.getLogger(__name__) + + +class AlertHandler(object): + OID_SEVERITY = '1.3.6.1.6.3.1.1.4.1.0' + OID_COMPONENT = '1.3.6.1.4.1.1139.21.1.3.0' + OID_SYMPTOMTEXT = '1.3.6.1.4.1.1139.21.1.5.0' + + TRAP_LEVEL_MAP = {'1.3.6.1.4.1.1139.21.0.1': constants.Severity.CRITICAL, + '1.3.6.1.4.1.1139.21.0.2': constants.Severity.MAJOR, + '1.3.6.1.4.1.1139.21.0.3': constants.Severity.WARNING, + '1.3.6.1.4.1.1139.21.0.4': + constants.Severity.INFORMATIONAL + } + + SECONDS_TO_MS = 1000 + + @staticmethod + def parse_alert(context, alert): + try: + description = alert.get(AlertHandler.OID_SYMPTOMTEXT) + alert_model = dict() + alert_model['alert_id'] = alert.get(AlertHandler.OID_COMPONENT) + alert_model['alert_name'] = description + alert_model['severity'] = AlertHandler.TRAP_LEVEL_MAP.get( + alert.get(AlertHandler.OID_SEVERITY), + 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['description'] = description + alert_model['resource_type'] = constants.DEFAULT_RESOURCE_TYPE + alert_model['location'] = '' + alert_model['match_key'] = hashlib.md5(description.encode()). \ + hexdigest() + + return alert_model + except Exception as e: + LOG.error(e) + msg = (_("Failed to build alert model as some attributes missing " + "in alert message.")) + raise exception.InvalidResults(msg) diff --git a/delfin/drivers/dell_emc/vplex/consts.py b/delfin/drivers/dell_emc/vplex/consts.py new file mode 100644 index 000000000..78866c0ea --- /dev/null +++ b/delfin/drivers/dell_emc/vplex/consts.py @@ -0,0 +1,17 @@ +# Copyright 2021 The SODA Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http:#www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. + +SOCKET_TIMEOUT = 10 +BASE_CONTEXT = '/vplex' +REST_AUTH_URL = '/vplex/clusters' diff --git a/delfin/drivers/dell_emc/vplex/rest_handler.py b/delfin/drivers/dell_emc/vplex/rest_handler.py new file mode 100644 index 000000000..d810177a4 --- /dev/null +++ b/delfin/drivers/dell_emc/vplex/rest_handler.py @@ -0,0 +1,126 @@ +# Copyright 2021 The SODA Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http:#www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. +import six +from oslo_log import log as logging + +from delfin import cryptor +from delfin import exception +from delfin.drivers.dell_emc.vplex import consts +from delfin.drivers.utils.rest_client import RestClient + +LOG = logging.getLogger(__name__) + + +class RestHandler(RestClient): + + def __init__(self, **kwargs): + super(RestHandler, self).__init__(**kwargs) + + def login(self): + try: + data = {} + self.init_http_head() + self.session.headers.update({ + "username": self.rest_username, + "password": cryptor.decode(self.rest_password)}) + res = self.do_call(consts.REST_AUTH_URL, data, 'GET') + if res.status_code != 200: + LOG.error("Login error. URL: %(url)s\n" + "Reason: %(reason)s.", + {"url": consts.REST_AUTH_URL, "reason": res.text}) + if 'User authentication failed' in res.text: + raise exception.InvalidUsernameOrPassword() + else: + raise exception.BadResponse(res.text) + except Exception as e: + LOG.error("Login error: %s", six.text_type(e)) + raise e + + def get_rest_info(self, url, data=None, method='GET'): + """Return dict result of the url response.""" + result_json = None + res = self.do_call(url, data, method) + if res.status_code == 200: + result_json = res.json().get('response') + return result_json + + def get_virtual_volume_by_name_resp(self, cluster_name, + virtual_volume_name): + url = '%s/clusters/%s/virtual-volumes/%s' % \ + (consts.BASE_CONTEXT, cluster_name, virtual_volume_name) + response = self.get_rest_info(url) + return response + + def get_virtual_volume_resp(self, cluster_name): + url = '%s/clusters/%s/virtual-volumes' % ( + consts.BASE_CONTEXT, cluster_name) + response = self.get_rest_info(url) + return response + + def get_cluster_resp(self): + uri = '%s/clusters' % consts.BASE_CONTEXT + response = self.get_rest_info(uri) + return response + + def get_devcie_resp(self, cluster_name): + url = '%s/clusters/%s/devices' % (consts.BASE_CONTEXT, cluster_name) + response = self.get_rest_info(url) + return response + + def get_device_by_name_resp(self, cluster_name, device_name): + url = '%s/clusters/%s/devices/%s' % ( + consts.BASE_CONTEXT, cluster_name, device_name) + response = self.get_rest_info(url) + return response + + def get_health_check_resp(self): + url = '%s/health-check' % consts.BASE_CONTEXT + data = {"args": "-l"} + response = self.get_rest_info(url, data, method='POST') + return response + + def get_cluster_by_name_resp(self, cluster_name): + url = '%s/clusters/%s' % (consts.BASE_CONTEXT, cluster_name) + response = self.get_rest_info(url) + return response + + def get_storage_volume_summary_resp(self, cluster_name): + url = '%s/storage-volume+summary' % consts.BASE_CONTEXT + args = '--clusters %s' % cluster_name + data = {"args": args} + response = self.get_rest_info(url, data, method='POST') + return response + + def get_device_summary_resp(self, cluster_name): + url = '%s/local-device+summary' % consts.BASE_CONTEXT + args = '--clusters %s' % cluster_name + data = {"args": args} + response = self.get_rest_info(url, data, method='POST') + return response + + def get_virtual_volume_summary_resp(self, cluster_name): + url = '%s/virtual-volume+summary' % consts.BASE_CONTEXT + args = '--clusters %s' % cluster_name + data = {"args": args} + response = self.get_rest_info(url, data, method='POST') + return response + + def logout(self): + try: + if self.session: + self.session.close() + except Exception as e: + err_msg = "Logout error: %s" % (six.text_type(e)) + LOG.error(err_msg) + raise e diff --git a/delfin/drivers/dell_emc/vplex/vplex_stor.py b/delfin/drivers/dell_emc/vplex/vplex_stor.py new file mode 100644 index 000000000..cec2bf37f --- /dev/null +++ b/delfin/drivers/dell_emc/vplex/vplex_stor.py @@ -0,0 +1,334 @@ +# Copyright 2021 The SODA Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http:#www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. +import re + +import six +from delfin import exception +from oslo_log import log +from oslo_utils import units + +from delfin.common import constants +from delfin.drivers import driver +from delfin.drivers.dell_emc.vplex import alert_handler +from delfin.drivers.dell_emc.vplex import rest_handler + +LOG = log.getLogger(__name__) + + +class VplexStorageDriver(driver.StorageDriver): + """DELL EMC VPLEX storage driver implement the DELL EMC Storage driver""" + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.rest_handler = rest_handler.RestHandler(**kwargs) + self.rest_handler.login() + + def reset_connection(self, context, **kwargs): + self.rest_handler.logout() + self.rest_handler.verify = kwargs.get('verify', False) + self.rest_handler.login() + + def get_storage(self, context): + health_check = self.rest_handler.get_health_check_resp() + all_cluster = self.rest_handler.get_cluster_resp() + cluster_name_list = VplexStorageDriver.get_resource_names(all_cluster) + if cluster_name_list: + health_map = {} + custom_data = health_check.get("custom-data") + VplexStorageDriver.handle_detail(custom_data, + health_map, split=':') + for cluster_name in cluster_name_list: + response = self.rest_handler.get_cluster_by_name_resp( + cluster_name) + attr_map = VplexStorageDriver.get_attribute_map(response) + operate_status = attr_map.get('operational-status') + health_status = attr_map.get('health-state') + status = VplexStorageDriver.analyse_status(operate_status, + health_status) + try: + raw_capacity = self.get_cluster_raw_capacity(cluster_name) + total_capacity = self.get_cluster_total_capacity( + cluster_name) + used_capacity = self.get_cluster_used_capacity( + cluster_name) + except Exception: + error_msg = "Failed to get capacity from VPLEX!" + raise exception.StorageBackendException(error_msg) + free_capacity = total_capacity - used_capacity + if free_capacity < 0: + free_capacity = 0 + cluster = { + 'name': cluster_name, + 'vendor': 'DELL EMC', + 'description': 'EMC VPlex Storage', + 'status': status, + 'serial_number': attr_map.get('top-level-assembly'), + 'firmware_version': health_map.get("Product Version"), + 'model': 'EMC VPLEX ' + health_map.get("Product Type"), + 'location': '', + 'raw_capacity': int(raw_capacity), + 'total_capacity': int(total_capacity), + 'used_capacity': int(used_capacity), + 'free_capacity': int(free_capacity), + 'subscribed_capacity': 0 + } + break + return cluster + + def list_storage_pools(self, context): + device_list = [] + all_cluster = self.rest_handler.get_cluster_resp() + cluster_name_list = VplexStorageDriver.get_resource_names(all_cluster) + for cluster_name in cluster_name_list: + response_device = self.rest_handler.get_devcie_resp(cluster_name) + map_device_childer = VplexStorageDriver.get_children_map( + response_device) + for name, resource_type in map_device_childer.items(): + response_dn = self.rest_handler.get_device_by_name_resp( + cluster_name, name) + map_dn_attribute = VplexStorageDriver.get_attribute_map( + response_dn) + virtual_volume = map_dn_attribute.get("virtual-volume") + total_capacity_str = map_dn_attribute.get("capacity") + total_capacity = VplexStorageDriver.analyse_capacity( + total_capacity_str) + operate_status = map_dn_attribute.get('operational-status') + health_status = map_dn_attribute.get('health-state') + used_capacity = 0 + free_capacity = 0 + if virtual_volume: + used_capacity = total_capacity + else: + free_capacity = total_capacity + + device = { + 'name': name, + 'storage_id': self.storage_id, + 'native_storage_pool_id': map_dn_attribute.get( + "system-id"), + 'description': 'EMC VPlex Pool', + 'status': self.analyse_status(operate_status, + health_status), + 'storage_type': constants.StorageType.BLOCK, + 'total_capacity': int(total_capacity), + 'used_capacity': int(used_capacity), + 'free_capacity': int(free_capacity) + } + device_list.append(device) + return device_list + + def list_volumes(self, context): + vv_list = [] + all_cluster = self.rest_handler.get_cluster_resp() + cluster_name_list = VplexStorageDriver.get_resource_names(all_cluster) + for cluster_name in cluster_name_list: + resposne_vv = self.rest_handler.get_virtual_volume_resp( + cluster_name) + map_vv_children = VplexStorageDriver.get_children_map(resposne_vv) + for name, resource_type in map_vv_children.items(): + response_vvn = self.rest_handler. \ + get_virtual_volume_by_name_resp(cluster_name, name) + map_vvn_attribute = VplexStorageDriver.get_attribute_map( + response_vvn) + thin_enabled = map_vvn_attribute.get("thin-enabled") + operate_status = map_vvn_attribute.get('operational-status') + health_status = map_vvn_attribute.get('health-state') + vv_type = self.analyse_vv_type(thin_enabled) + total_capacity = VplexStorageDriver.analyse_capacity( + map_vvn_attribute.get("capacity")) + vpd_id = map_vvn_attribute.get("vpd-id") + cells = vpd_id.split(":") + wwn = '' + if len(cells) > 1: + wwn = cells[1] + used_capacity = 0 + if vv_type == constants.VolumeType.THICK: + used_capacity = total_capacity + vv = { + 'name': name, + 'storage_id': self.storage_id, + 'description': 'EMC VPlex volume', + 'status': self.analyse_status(operate_status, + health_status), + 'native_volume_id': vpd_id, + 'native_storage_pool_id': map_vvn_attribute.get( + 'supporting-device'), + 'type': vv_type, + 'total_capacity': int(total_capacity), + 'used_capacity': int(used_capacity), + 'free_capacity': 0, + 'wwn': wwn + } + vv_list.append(vv) + return vv_list + + def add_trap_config(self, context, trap_config): + pass + + def remove_trap_config(self, context, trap_config): + pass + + @staticmethod + def parse_alert(context, alert): + return alert_handler.AlertHandler().parse_alert(context, alert) + + def list_alerts(self, context, query_para=None): + pass + + def clear_alert(self, context, alert): + pass + + @staticmethod + def get_access_url(): + return 'https://{ip}' + + @staticmethod + def get_attribute_map(response): + attr_map = {} + if response: + contexts = response.get("context") + for context in contexts: + attributes = context.get("attributes") + for attribute in attributes: + key = attribute.get("name") + value = attribute.get("value") + attr_map[key] = value + return attr_map + + @staticmethod + def analyse_capacity(capacity_str): + capacity = 0 + if capacity_str.strip(): + capacity = re.findall("\\d+", capacity_str)[0] + return capacity + + @staticmethod + def analyse_status(operational_status, health_status): + status = constants.StorageStatus.ABNORMAL + status_normal = ["ok"] + status_offline = ["unknown", "isolated", "not-running", + "non-recoverable-error"] + if operational_status and health_status in status_normal: + status = constants.StorageStatus.NORMAL + elif operational_status and health_status in status_offline: + status = constants.StorageStatus.OFFLINE + return status + + @staticmethod + def analyse_vv_type(thin_enabled): + rs_type = constants.VolumeType.THICK + if thin_enabled == "enabled": + rs_type = constants.VolumeType.THIN + return rs_type + + @staticmethod + def get_children_map(response): + child_map = {} + if response: + contexts = response.get("context") + for context in contexts: + childrens = context.get("children") + for children in childrens: + name = children.get("name") + type = children.get("type") + child_map[name] = type + return child_map + + @staticmethod + def get_resource_names(response): + resource_name_list = [] + if response: + contexts = response.get('context') + for context in contexts: + childer_clusters = context.get("children") + for childer_cluster in childer_clusters: + cluster_name = childer_cluster.get("name") + resource_name_list.append(cluster_name) + return resource_name_list + + @staticmethod + def handle_detail(detail_info, detail_map, split): + detail_arr = detail_info.split('\n') + for detail in detail_arr: + if detail is not None and detail != '': + strinfo = detail.split(split, 1) + key = strinfo[0] + value = '' + if len(strinfo) > 1: + value = strinfo[1] + detail_map[key] = value + + def get_cluster_raw_capacity(self, cluster_name): + resposne_summary = self.rest_handler. \ + get_storage_volume_summary_resp(cluster_name) + try: + custom_data = resposne_summary.get("custom-data") + find_capacity = re.findall( + r"Capacity\s+total\s+(([0-9]*(\.[0-9]{1,3}))|([0-9]+))", + custom_data) + find_capacity_str = find_capacity[-1][0] + find_capacity_float = float(find_capacity_str) + capacity = int(find_capacity_float * units.Ti) + except Exception as e: + LOG.error("Storage raw capacity, cluster %s analyse error %s" % + cluster_name, six.text_type(e)) + raise e + return capacity + + def get_cluster_total_capacity(self, cluster_name): + resposne_summary = self.rest_handler.get_device_summary_resp( + cluster_name) + try: + custom_data = resposne_summary.get("custom-data") + find_capacity = re.findall( + r'total.*?(([0-9]*(\.[0-9]{1,3}))|([0-9]+))', + custom_data) + find_capacity_str = find_capacity[-1][0] + find_capacity_float = float(find_capacity_str) + capacity = int(find_capacity_float * units.Ti) + except Exception as e: + LOG.error("Storage total capacity, cluster %s analyse error %s" % + cluster_name, six.text_type(e)) + raise e + return capacity + + def get_cluster_used_capacity(self, cluster_name): + resposne_summary = self.rest_handler. \ + get_virtual_volume_summary_resp(cluster_name) + try: + custom_data = resposne_summary.get("custom-data") + find_capacity = re.findall( + r"capacity\s+is\s+(([0-9]*(\.[0-9]{1,3}))|([0-9]+))", + custom_data) + find_capacity_str = find_capacity[-1][0] + find_capacity_float = float(find_capacity_str) + capacity = int(find_capacity_float * units.Ti) + except Exception as e: + LOG.error("Storage used capacity, cluster %s analyse error %s" % + cluster_name, six.text_type(e)) + raise e + return capacity + + +@staticmethod +def handle_detail_list(detail_info, detail_map, split): + detail_arr = detail_info.split('\n') + for detail in detail_arr: + if detail is not None and detail != '': + strinfo = detail.split(split, 1) + key = strinfo[0] + value = '' + if len(strinfo) > 1: + value = strinfo[1] + detail_map[key] = value diff --git a/delfin/tests/unit/drivers/dell_emc/vplex/__init__.py b/delfin/tests/unit/drivers/dell_emc/vplex/__init__.py new file mode 100644 index 000000000..e69de29bb 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 new file mode 100644 index 000000000..e69de29bb From 66ca90130f9368aa43332a9a8a5c48eb76a63540 Mon Sep 17 00:00:00 2001 From: liuxh Date: Thu, 18 Mar 2021 17:13:34 +0800 Subject: [PATCH 2/2] =?UTF-8?q?vplex=E4=BB=A3=E7=A0=81=E5=90=88=E5=B9=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- delfin/drivers/dell_emc/vplex/vplex_stor.py | 12 + .../drivers/dell_emc/vplex/test_emc_vplex.py | 261 ++++++++++++++++++ 2 files changed, 273 insertions(+) diff --git a/delfin/drivers/dell_emc/vplex/vplex_stor.py b/delfin/drivers/dell_emc/vplex/vplex_stor.py index cec2bf37f..cf6aa7b35 100644 --- a/delfin/drivers/dell_emc/vplex/vplex_stor.py +++ b/delfin/drivers/dell_emc/vplex/vplex_stor.py @@ -320,6 +320,18 @@ def get_cluster_used_capacity(self, cluster_name): raise e return capacity + def list_controllers(self, context): + """List all storage controllers from storage system.""" + pass + + def list_ports(self, context): + """List all ports from storage system.""" + pass + + def list_disks(self, context): + """List all disks from storage system.""" + pass + @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 e69de29bb..b68624435 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 @@ -0,0 +1,261 @@ +# Copyright 2021 The SODA Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http:#www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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 unittest import TestCase, mock + +from delfin import context +from delfin.drivers.dell_emc.vplex.rest_handler import RestHandler +from delfin.drivers.dell_emc.vplex.vplex_stor import VplexStorageDriver + +ACCESS_INFO = { + "storage_id": "12345", + "vendor": "dell_emc", + "model": "vplex", + "rest": { + "host": "8.44.162.250", + "port": 443, + "username": "service", + "password": "Abcdef@123" + } +} +TRAP_INFO = { + "1.3.6.1.2.1.1.3.0": "0", + '1.3.6.1.6.3.1.1.4.1.0': '1.3.6.1.4.1.1139.21.0', + '1.3.6.1.4.1.1139.21.1.5.0': 'this is test', + '1.3.6.1.4.1.1139.21.1.3.0': '123321' +} +trap_result = { + 'alert_id': '123321', + 'alert_name': 'this is test', + 'severity': 'Informational', + 'category': 'Fault', + 'type': 'EquipmentAlarm', + 'occur_time': 1614067724000, + 'description': 'this is test', + 'resource_type': 'Storage', + 'location': '', + 'match_key': '8c6d115258631625b625486f81b09532' +} +GET_ALL_CLUSTER = { + "context": [{ + "children": [{ + "name": "cluster-1", + "type": "cluster" + } + ] + } + ] +} +GET_ALL_LUNS = { + "context": [ + { + "children": [ + { + "name": "device_VPLEX_LUN0_1_vol", + "type": "virtual-volume" + } + ] + } + ] +} +GET_LUN = { + "context": [ + { + "attributes": [ + { + "name": "capacity", + "value": "644245094400B" + }, + { + "name": "health-state", + "value": "ok" + }, + { + "name": "operational-status", + "value": "ok" + }, + { + "name": "supporting-device", + "value": "device__VPLEX_LUN0_1" + }, + { + "name": "thin-enabled", + "value": "unavailable" + }, + { + "name": "vpd-id", + "value": "VPD83T3:60000000000000000000000000000000" + } + ] + } + ] +} +volume_result = [{ + 'name': 'device_VPLEX_LUN0_1_vol', + 'storage_id': '12345', + 'description': 'EMC VPlex volume', + 'status': 'normal', + 'native_volume_id': 'VPD83T3:60000000000000000000000000000000', + 'native_storage_pool_id': 'device__VPLEX_LUN0_1', + 'type': 'thick', + 'total_capacity': 644245094400, + 'used_capacity': 644245094400, + 'free_capacity': 0, + 'wwn': '60000000000000000000000000000000' +} +] +GET_ALL_POOLS = { + "context": [ + { + "children": [ + { + "name": "Device_KLM_test01", + "type": "local-device" + } + ] + } + ] +} +GET_POOL = { + "context": [ + { + "attributes": [ + { + "name": "capacity", + "value": "732212254720B" + }, + { + "name": "health-state", + "value": "ok" + }, + { + "name": "operational-status", + "value": "ok" + }, + { + "name": "system-id", + "value": "Device_KLM_test01" + }, + { + "name": "virtual-volume", + "value": "Volume_CLARiiON0041_KLM_test01" + } + ] + } + ] +} +pool_result = [ + { + 'name': 'Device_KLM_test01', + 'storage_id': '12345', + 'native_storage_pool_id': 'Device_KLM_test01', + 'description': 'EMC VPlex Pool', + 'status': 'normal', + 'storage_type': 'block', + 'total_capacity': 732212254720, + 'used_capacity': 732212254720, + 'free_capacity': 0 + } +] +GET_HEALH_CHECK = { + "context": None, + "message": "health-check -l", + "exception": None, + "custom-data": "Product Version: 6.1.0.01.00.13\n" + "Product Type: Local\n" +} +GET_CLUSTER = { + "context": [ + { + "type": "cluster", + "parent": "/clusters", + "attributes": [ + { + "name": "health-state", + "value": "major-failure" + }, + { + "name": "operational-status", + "value": "degraded" + }, + { + "name": "top-level-assembly", + "value": "FNM00000000000" + } + ], + } + ] +} +storage_result = { + 'name': 'cluster-1', + 'vendor': 'DELL EMC', + 'description': 'EMC VPlex Storage', + 'status': 'abnormal', + 'serial_number': 'FNM00000000000', + 'firmware_version': ' 6.1.0.01.00.13', + 'model': 'EMC VPLEX Local', + 'location': '', + 'raw_capacity': 12754334882201, + 'total_capacity': 11654823254425, + 'used_capacity': 8983009998929, + 'free_capacity': 2671813255496, + 'subscribed_capacity': 0 +} +GET_ALL_STORAGE_VOLUME_SUMMARY = { + "custom-data": "Capacity total 11.6T\n\n" +} +GET_ALL_POOLS_SUMMARY = { + "custom-data": "total capacity 1.88T total capacity " + "8.68T total capacity 10.6T\n\n" +} +GET_ALL_LUNS_SUMMARY = { + "custom-data": "Total virtual-volume capacity is 8.17T." +} + + +class TestVplexStorDriver(TestCase): + RestHandler.login = mock.Mock(return_value=None) + + def test_parse_alert(self): + trap = VplexStorageDriver(**ACCESS_INFO).parse_alert(context, + TRAP_INFO) + trap_result['occur_time'] = trap['occur_time'] + self.assertDictEqual(trap, trap_result) + + @mock.patch.object(RestHandler, 'get_cluster_resp') + @mock.patch.object(RestHandler, 'get_virtual_volume_resp') + @mock.patch.object(RestHandler, 'get_virtual_volume_by_name_resp') + def test_list_volumes(self, mock_name, mock_volume, mock_cluster): + mock_cluster.return_value = GET_ALL_CLUSTER + mock_volume.return_value = GET_ALL_LUNS + mock_name.return_value = GET_LUN + volume = VplexStorageDriver(**ACCESS_INFO).list_volumes(context) + self.assertDictEqual(volume[0], volume_result[0]) + + @mock.patch.object(RestHandler, 'get_cluster_resp') + @mock.patch.object(RestHandler, 'get_devcie_resp') + @mock.patch.object(RestHandler, 'get_device_by_name_resp') + def test_list_storage_pools(self, mock_name, mock_device, mock_cluster): + mock_cluster.return_value = GET_ALL_CLUSTER + mock_device.return_value = GET_ALL_POOLS + mock_name.return_value = GET_POOL + pool = VplexStorageDriver(**ACCESS_INFO).list_storage_pools(context) + self.assertDictEqual(pool[0], pool_result[0]) + + def test_get_storage(self): + RestHandler.get_rest_info = mock.Mock( + side_effect=[GET_HEALH_CHECK, GET_ALL_CLUSTER, GET_CLUSTER, + GET_ALL_STORAGE_VOLUME_SUMMARY, GET_ALL_POOLS_SUMMARY, + GET_ALL_LUNS_SUMMARY]) + storage = VplexStorageDriver(**ACCESS_INFO).get_storage(context) + self.assertDictEqual(storage, storage_result)