From 3aeb9af8d943efab360a0c51b7267ac260b4e937 Mon Sep 17 00:00:00 2001 From: tanjy Date: Wed, 5 Jan 2022 14:32:08 +0800 Subject: [PATCH] add ds8000 resources info --- delfin/drivers/ibm/ds8k/__init__.py | 0 delfin/drivers/ibm/ds8k/alert_handler.py | 67 +++ delfin/drivers/ibm/ds8k/ds8k.py | 261 ++++++++++++ delfin/drivers/ibm/ds8k/rest_handler.py | 109 +++++ .../unit/drivers/ibm/ibm_ds8k/__init__.py | 0 .../drivers/ibm/ibm_ds8k/test_ibm_ds8k.py | 403 ++++++++++++++++++ setup.py | 1 + 7 files changed, 841 insertions(+) create mode 100644 delfin/drivers/ibm/ds8k/__init__.py create mode 100644 delfin/drivers/ibm/ds8k/alert_handler.py create mode 100644 delfin/drivers/ibm/ds8k/ds8k.py create mode 100644 delfin/drivers/ibm/ds8k/rest_handler.py create mode 100644 delfin/tests/unit/drivers/ibm/ibm_ds8k/__init__.py create mode 100644 delfin/tests/unit/drivers/ibm/ibm_ds8k/test_ibm_ds8k.py diff --git a/delfin/drivers/ibm/ds8k/__init__.py b/delfin/drivers/ibm/ds8k/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/delfin/drivers/ibm/ds8k/alert_handler.py b/delfin/drivers/ibm/ds8k/alert_handler.py new file mode 100644 index 000000000..de4c89521 --- /dev/null +++ b/delfin/drivers/ibm/ds8k/alert_handler.py @@ -0,0 +1,67 @@ +# 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 time + +import six +from oslo_log import log + +from delfin import exception +from delfin.common import alert_util +from delfin.common import constants + +LOG = log.getLogger(__name__) + + +class AlertHandler(object): + + TIME_PATTERN = "%Y-%m-%dT%H:%M:%S%z" + + ALERT_LEVEL_MAP = {'error': constants.Severity.CRITICAL, + 'warning': constants.Severity.WARNING, + 'info': constants.Severity.INFORMATIONAL + } + SECONDS_TO_MS = 1000 + + def parse_queried_alerts(self, alert_model_list, alert_list, query_para): + alerts = alert_list.get('data', {}).get('events') + if alerts: + for alert in alerts: + try: + occur_time = int(time.mktime(time.strptime( + alert.get('time'), + self.TIME_PATTERN))) * AlertHandler.SECONDS_TO_MS + if not alert_util.is_alert_in_time_range( + query_para, occur_time): + continue + + alert_model = {} + alert_model['alert_id'] = alert.get('type') + alert_model['alert_name'] = alert.get('description') + alert_model['severity'] = self.ALERT_LEVEL_MAP.get( + alert.get('severity'), + constants.Severity.INFORMATIONAL) + alert_model['description'] = alert.get('description') + alert_model['category'] = constants.Category.FAULT + alert_model['type'] = constants.EventType.EQUIPMENT_ALARM + alert_model['sequence_number'] = alert.get('id') + alert_model['occur_time'] = occur_time + alert_model['resource_type'] = \ + constants.DEFAULT_RESOURCE_TYPE + alert_model_list.append(alert_model) + except Exception as e: + LOG.error(e) + err_msg = "Failed to build alert model as some" \ + " attributes missing in queried alerts: %s"\ + % (six.text_type(e)) + raise exception.InvalidResults(err_msg) diff --git a/delfin/drivers/ibm/ds8k/ds8k.py b/delfin/drivers/ibm/ds8k/ds8k.py new file mode 100644 index 000000000..41b25fe09 --- /dev/null +++ b/delfin/drivers/ibm/ds8k/ds8k.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. +import six +from oslo_log import log +from oslo_utils import units + +from delfin import exception +from delfin.common import constants +from delfin.drivers import driver +from delfin.drivers.ibm.ds8k import rest_handler, alert_handler + +LOG = log.getLogger(__name__) + + +class DS8KDriver(driver.StorageDriver): + + PORT_TYPE_MAP = {'FC-AL': constants.PortType.FC, + 'SCSI-FCP': constants.PortType.FC, + 'FICON': constants.PortType.FICON + } + PORT_STATUS_MAP = { + 'online': constants.PortHealthStatus.NORMAL, + 'offline': constants.PortHealthStatus.ABNORMAL, + 'fenced': constants.PortHealthStatus.UNKNOWN, + 'quiescing': constants.PortHealthStatus.UNKNOWN + } + + 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 close_connection(self): + self.rest_handler.logout() + + def get_storage(self, context): + try: + result = None + system_info = self.rest_handler.get_rest_info('/api/v1/systems') + if system_info: + system_data = system_info.get('data', {}).get('systems', []) + if system_data: + for system in system_data: + name = system.get('name') + model = system.get('MTM') + serial_number = system.get('sn') + version = system.get('release') + status = constants.StorageStatus.NORMAL + if system.get('state') != 'online': + status = constants.StorageStatus.ABNORMAL + total = 0 + free = 0 + used = 0 + raw = 0 + if system.get('cap') != '' and \ + system.get('cap') is not None: + total = int(system.get('cap')) + if system.get('capraw') != '' and \ + system.get('capraw') is not None: + raw = int(system.get('capraw')) + if system.get('capalloc') != '' and \ + system.get('capalloc') is not None: + used = int(system.get('capalloc')) + if system.get('capavail') != '' and \ + system.get('capavail') is not None: + free = int(system.get('capavail')) + result = { + 'name': name, + 'vendor': 'IBM', + 'model': model, + 'status': status, + 'serial_number': serial_number, + 'firmware_version': version, + 'location': '', + 'total_capacity': total, + 'raw_capacity': raw, + 'used_capacity': used, + 'free_capacity': free + } + break + else: + raise exception.StorageBackendException( + "ds8k storage system info is None") + else: + raise exception.StorageBackendException( + "ds8k storage system info is None") + return result + except Exception as err: + err_msg = "Failed to get storage attributes from ds8k: %s" % \ + (six.text_type(err)) + raise exception.InvalidResults(err_msg) + + def list_storage_pools(self, context): + pool_info = self.rest_handler.get_rest_info('/api/v1/pools') + pool_list = [] + status = constants.StoragePoolStatus.NORMAL + if pool_info is not None: + pool_data = pool_info.get('data', {}).get('pools', []) + for pool in pool_data: + if pool.get('stgtype') == 'fb': + pool_type = constants.StorageType.BLOCK + else: + pool_type = constants.StorageType.FILE + if (int(pool.get('capalloc')) / int(pool.get('cap'))) * 100 > \ + int(pool.get('threshold')): + status = constants.StoragePoolStatus.ABNORMAL + pool_name = '%s_%s' % (pool.get('name'), pool.get('node')) + pool_result = { + 'name': pool_name, + 'storage_id': self.storage_id, + 'native_storage_pool_id': str(pool.get('id')), + 'status': status, + 'storage_type': pool_type, + 'total_capacity': int(pool.get('cap')), + 'used_capacity': int(pool.get('capalloc')), + 'free_capacity': int(pool.get('capavail')) + } + pool_list.append(pool_result) + return pool_list + + def list_volumes(self, context): + volume_list = [] + pool_list = self.rest_handler.get_rest_info('/api/v1/pools') + if pool_list is not None: + pool_data = pool_list.get('data', {}).get('pools', []) + for pool in pool_data: + url = '/api/v1/pools/%s/volumes' % pool.get('id') + volumes = self.rest_handler.get_rest_info(url) + if volumes is not None: + vol_entries = volumes.get('data', {}).get('volumes', []) + for volume in vol_entries: + total = volume.get('cap') + used = volume.get('capalloc') + vol_type = constants.VolumeType.THICK if \ + volume.get('stgtype') == 'fb' else \ + constants.VolumeType.THIN + status = constants.StorageStatus.NORMAL if \ + volume.get('state') == 'normal' else\ + constants.StorageStatus.ABNORMAL + vol_name = '%s_%s' % (volume.get('name'), + volume.get('id')) + vol = { + 'name': vol_name, + 'storage_id': self.storage_id, + 'description': '', + 'status': status, + 'native_volume_id': str(volume.get('id')), + 'native_storage_pool_id': + volume.get('pool').get('id'), + 'wwn': '', + 'type': vol_type, + 'total_capacity': int(total), + 'used_capacity': int(used), + 'free_capacity': int(total) - int(used) + } + volume_list.append(vol) + return volume_list + + def list_alerts(self, context, query_para=None): + alert_model_list = [] + alert_list = self.rest_handler.get_rest_info( + '/api/v1/events?severity=warning,error') + alert_handler.AlertHandler() \ + .parse_queried_alerts(alert_model_list, alert_list, query_para) + return alert_model_list + + @staticmethod + def division_port_wwn(original_wwn): + result_wwn = None + if not original_wwn: + return result_wwn + is_first = True + for i in range(0, len(original_wwn), 2): + if is_first is True: + result_wwn = '%s' % (original_wwn[i:i + 2]) + is_first = False + else: + result_wwn = '%s:%s' % (result_wwn, original_wwn[i:i + 2]) + return result_wwn + + def list_ports(self, context): + port_list = [] + port_info = self.rest_handler.get_rest_info('/api/v1/ioports') + if port_info: + port_data = port_info.get('data', {}).get('ioports', []) + for port in port_data: + status = DS8KDriver.PORT_STATUS_MAP.get( + port.get('state'), constants.PortHealthStatus.UNKNOWN) + speed = None + connection_status = constants.PortConnectionStatus.CONNECTED\ + if status == constants.PortHealthStatus.NORMAL \ + else constants.PortConnectionStatus.DISCONNECTED + if port.get('speed'): + speed = int(port.get('speed').split(' ')[0]) * units.G + port_result = { + 'name': port.get('loc'), + 'storage_id': self.storage_id, + 'native_port_id': port.get('id'), + 'location': port.get('loc'), + 'connection_status': connection_status, + 'health_status': status, + 'type': DS8KDriver.PORT_TYPE_MAP.get( + port.get('protocol'), constants.PortType.OTHER), + 'logical_type': '', + 'speed': speed, + 'max_speed': speed, + 'wwn': DS8KDriver.division_port_wwn(port.get('wwpn')) + } + port_list.append(port_result) + return port_list + + def list_controllers(self, context): + controller_list = [] + controller_info = self.rest_handler.get_rest_info('/api/v1/nodes') + if controller_info: + contrl_data = controller_info.get('data', {}).get('nodes', []) + for contrl in contrl_data: + status = constants.ControllerStatus.NORMAL if \ + contrl.get('state') == 'online' else \ + constants.ControllerStatus.UNKNOWN + controller_result = { + 'name': contrl.get('id'), + 'storage_id': self.storage_id, + 'native_controller_id': contrl.get('id'), + 'status': status + } + controller_list.append(controller_result) + return controller_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): + pass + + def clear_alert(self, context, alert): + pass + + @staticmethod + def get_access_url(): + return 'https://{ip}:{port}' diff --git a/delfin/drivers/ibm/ds8k/rest_handler.py b/delfin/drivers/ibm/ds8k/rest_handler.py new file mode 100644 index 000000000..591880c84 --- /dev/null +++ b/delfin/drivers/ibm/ds8k/rest_handler.py @@ -0,0 +1,109 @@ +# Copyright 2021 The SODA Authors. +# All Rights Reserved. +# +# 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 threading + +import six +from oslo_log import log as logging + +from delfin import cryptor +from delfin import exception +from delfin.drivers.utils.rest_client import RestClient + +LOG = logging.getLogger(__name__) + + +class RestHandler(RestClient): + REST_TOKEN_URL = '/api/v1/tokens' + + def __init__(self, **kwargs): + self.session_lock = threading.Lock() + super(RestHandler, self).__init__(**kwargs) + + def call_with_token(self, url, data, method): + auth_key = None + if self.session: + auth_key = self.session.headers.get('X-Auth-Token', None) + if auth_key: + self.session.headers['X-Auth-Token'] \ + = cryptor.decode(auth_key) + res = self.do_call(url, data, method) + if auth_key: + self.session.headers['X-Auth-Token'] = auth_key + return res + + def call(self, url, data=None, method=None): + try: + res = self.call_with_token(url, data, method) + if res.status_code == 401: + LOG.error("Failed to get token,status_code:%s,error_mesg:%s" % + (res.status_code, res.text)) + self.login() + res = self.call_with_token(url, data, method) + elif res.status_code == 503: + raise exception.InvalidResults(res.text) + return res + except Exception as e: + LOG.error("Method:%s,url:%s failed: %s" % (method, url, + six.text_type(e))) + raise e + + def get_rest_info(self, url, data=None, method='GET'): + result_json = None + res = self.call(url, data, method) + if res.status_code == 200: + result_json = res.json() + return result_json + + def login(self): + try: + data = { + 'request': { + 'params': { + "username": self.rest_username, + "password": cryptor.decode(self.rest_password) + } + } + } + with self.session_lock: + if self.session is None: + self.init_http_head() + res = self.call_with_token( + RestHandler.REST_TOKEN_URL, data, 'POST') + if res.status_code == 200: + result = res.json() + self.session.headers['X-Auth-Token'] = \ + cryptor.encode(result.get('token').get('token')) + else: + LOG.error("Login error. URL: %(url)s,Reason: %(reason)s.", + {"url": RestHandler.REST_TOKEN_URL, + "reason": res.text}) + if 'Authentication has failed' in res.text: + raise exception.InvalidUsernameOrPassword() + else: + raise exception.StorageBackendException(res.text) + except Exception as e: + LOG.error("Login error: %s", six.text_type(e)) + raise e + finally: + data = None + + 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/tests/unit/drivers/ibm/ibm_ds8k/__init__.py b/delfin/tests/unit/drivers/ibm/ibm_ds8k/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/delfin/tests/unit/drivers/ibm/ibm_ds8k/test_ibm_ds8k.py b/delfin/tests/unit/drivers/ibm/ibm_ds8k/test_ibm_ds8k.py new file mode 100644 index 000000000..e3dcd7936 --- /dev/null +++ b/delfin/tests/unit/drivers/ibm/ibm_ds8k/test_ibm_ds8k.py @@ -0,0 +1,403 @@ +# 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 sys +from unittest import TestCase, mock + +sys.modules['delfin.cryptor'] = mock.Mock() + +from delfin import context +from delfin.drivers.ibm.ds8k.rest_handler import RestHandler +from delfin.drivers.ibm.ds8k.ds8k import DS8KDriver + + +ACCESS_INFO = { + "storage_id": "12345", + "rest": { + "host": "110.143.132.231", + "port": "8443", + "username": "username", + "password": "cGFzc3dvcmQ=" + }, + "ssh": { + "host": "110.143.132.231", + "port": "22", + "username": "username", + "password": "password", + "host_key": "weqewrerwerwerwe" + }, + "vendor": "IBM", + "model": "DS8000", + "extra_attributes": { + "array_id": "00112233" + } +} +GET_STORAGE = { + "data": { + "systems": [ + { + "id": "2107-75BXG71", + "name": "TDCUOB_DS8870", + "state": "online", + "release": "7.5.1", + "bundle": "87.51.103.5120", + "MTM": "2423-961", + "sn": "75BXG71", + "wwnn": "5005076304FFD7EF", + "cap": "1655709892608", + "capalloc": "1073741824000", + "capavail": "581968068608", + "capraw": "2516582400000" + } + ] + } +} +GET_ALL_POOLS = { + "data": { + "pools": [ + { + "id": "P0", + "link": { + "rel": "self", + "href": "https:/192.168.1.170:8452/api/v1/pools/P0" + }, + "name": "test_pool", + "node": "0", + "stgtype": "fb", + "cap": "1655709892608", + "capalloc": "1073741824000", + "capavail": "581968068608", + "overprovisioned": "0.6", + "easytier": "managed", + "tieralloc": [ + { + "tier": "ENT", + "cap": "1655709892608", + "allocated": "1073741824000", + "assigned": "0" + } + ], + "threshold": "15", + "real_capacity_allocated_on_ese": "0", + "virtual_capacity_allocated_on_ese": "0", + "eserep": {}, + "tserep": {}, + "volumes": { + "link": { + "rel": "self" + } + } + } + ] + } +} +GET_ALL_LUNS = { + "data": { + "volumes": + [ + { + "link": { + "rel": "self", + "href": "https://{hmc}:443/api/v1/volumes/0000" + }, + "id": "0000", + "name": "mytest", + "state": "normal", + "cap": "322122547200", + "stgtype": "fb", + "VOLSER": "", + "lss": { + "id": "00", + "link": { + "rel": "self", + "href": + "https://{hmc}:443/api/lss/00" + } + }, + "allocmethod": "legacy", + "tp": "none", + "capalloc": "134217728", + "MTM": "2107-900", + "datatype": "FB 512", + "tieralloc": + [ + { + "tier": "ENT", + "allocated": "34502" + } + ], + "pool": { + "id": "P2", + "link": { + "rel": "self", + "href": + "https://{hmc}:443/api/v1/pools/P2" + } + } + } + ] + } +} +GET_ALL_LUNS_NULL = { + "data": { + "volumes": + [] + } +} +GET_ALL_ALERTS = { + "data": { + "events": + [ + { + "id": "SEfe", + "type": "HostPortStateChanged", + "severity": "error", + "time": "2014-04-20T13:00:23-0700", + "resource_id": "1152922127280127616", + "formatted_parameter": + ["10000090FA383E80", "Logged Off", + "Logged In", "NISCSIHostPortID: ""IBM.2107-75BXG71/12"], + "description": "Host port 10000090FA383E80 state logged in." + } + ] + } +} +GET_ALL_PORTS = { + "data": { + "ioports": + [ + { + "id": "I0000", + "link": { + "rel": "self", + "href": "https:/192.168.1.170:8452/api/v1/ioports/I0000" + }, + "state": "online", + "protocol": "FC-AL", + "wwpn": "50050763040017EF", + "type": "Fibre Channel-SW", + "speed": "8 Gb/s", + "loc": "U1400.1B1.RJ55380-P1-C1-T0" + }, + { + "id": "I0005", + "link": { + "rel": "self", + "href": "https:/192.168.1.170:8452/api/v1/ioports/I0005" + }, + "state": "online", + "protocol": "SCSI-FCP", + "wwpn": "50050763044057EF", + "type": "Fibre Channel-SW", + "speed": "8 Gb/s", + "loc": "U1400.1B1.RJ55380-P1-C1-T5" + } + ] + } +} +GET_ALL_CONTROLLERS = { + 'data': { + 'nodes': [ + { + 'id': '00', + 'state': 'online' + }, { + 'id': '01', + 'state': 'online' + } + ] + } +} +TOKEN_RESULT = { + "server": { + "status": "ok", + "code": "200", + "message": "Operation done successfully." + }, + "token": { + "token": "ddb1743a", + "expired_time": "2014-08-25T03:28:15-0700", + "max_idle_interval": "1800000" + } +} +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.103.1.18.2.0', + '1.3.6.1.4.1.1139.103.1.18.1.1': 'eeeeeeeee', + '1.3.6.1.4.1.1139.103.1.18.1.3': 'ddddddd', + '1.3.6.1.4.1.1139.103.1.18.1.4': 'this is test', + '1.3.6.1.4.1.1139.103.1.18.1.5': '2020/11/20 14:10:10', + '1.3.6.1.4.1.1139.103.1.18.1.2': 'test' +} +storage_result = { + 'name': 'TDCUOB_DS8870', + 'vendor': 'IBM', + 'model': '2423-961', + 'status': 'normal', + 'serial_number': '75BXG71', + 'firmware_version': '7.5.1', + 'location': '', + 'total_capacity': 1655709892608, + 'raw_capacity': 2516582400000, + 'used_capacity': 1073741824000, + 'free_capacity': 581968068608 +} +pool_result = [ + { + 'name': 'test_pool_0', + 'storage_id': '12345', + 'native_storage_pool_id': 'P0', + 'status': 'abnormal', + 'storage_type': 'block', + 'total_capacity': 1655709892608, + 'used_capacity': 1073741824000, + 'free_capacity': 581968068608 + } +] +volume_result = [ + { + 'name': 'mytest_0000', + 'storage_id': '12345', + 'description': '', + 'status': 'normal', + 'native_volume_id': '0000', + 'native_storage_pool_id': 'P2', + 'wwn': '', + 'type': 'thick', + 'total_capacity': 322122547200, + 'used_capacity': 134217728, + 'free_capacity': 321988329472 + } +] +alert_result = [ + { + 'alert_id': 'HostPortStateChanged', + 'alert_name': 'Host port 10000090FA383E80 state logged in.', + 'severity': 'Critical', + 'description': 'Host port 10000090FA383E80 state logged in.', + 'category': 'Fault', + 'type': 'EquipmentAlarm', + 'sequence_number': 'SEfe', + 'occur_time': 1397970023000, + 'resource_type': 'Storage' + } +] +port_result = [ + { + 'name': 'U1400.1B1.RJ55380-P1-C1-T0', + 'storage_id': '12345', + 'native_port_id': 'I0000', + 'location': 'U1400.1B1.RJ55380-P1-C1-T0', + 'connection_status': 'connected', + 'health_status': 'normal', + 'type': 'fc', + 'logical_type': '', + 'speed': 8000000000, + 'max_speed': 8000000000, + 'wwn': '50:05:07:63:04:00:17:EF' + }, { + 'name': 'U1400.1B1.RJ55380-P1-C1-T5', + 'storage_id': '12345', + 'native_port_id': 'I0005', + 'location': 'U1400.1B1.RJ55380-P1-C1-T5', + 'connection_status': 'connected', + 'health_status': 'normal', + 'type': 'fc', + 'logical_type': '', + 'speed': 8000000000, + 'max_speed': 8000000000, + 'wwn': '50:05:07:63:04:40:57:EF' + } +] +contrl_result = [ + { + 'name': '00', + 'storage_id': '12345', + 'native_controller_id': '00', + 'status': 'normal' + }, { + 'name': '01', + 'storage_id': '12345', + 'native_controller_id': '01', + 'status': 'normal' + } +] + +trap_result = { + 'alert_id': 'ddddddd', + 'alert_name': 'test', + 'severity': 'Critical', + 'category': 'Fault', + 'type': 'EquipmentAlarm', + 'occur_time': 1605852610000, + 'description': 'this is test', + 'resource_type': 'Storage', + 'location': 'eeeeeeeee' +} + + +class TestDS8KDriver(TestCase): + + @mock.patch.object(RestHandler, 'get_rest_info') + def test_get_storage(self, mock_storage): + RestHandler.login = mock.Mock(return_value=None) + mock_storage.return_value = GET_STORAGE + storage = DS8KDriver(**ACCESS_INFO).get_storage(context) + self.assertDictEqual(storage, storage_result) + + @mock.patch.object(RestHandler, 'get_rest_info') + def test_list_storage_pools(self, mock_pool): + RestHandler.login = mock.Mock(return_value=None) + mock_pool.return_value = GET_ALL_POOLS + pool = DS8KDriver(**ACCESS_INFO).list_storage_pools(context) + self.assertEqual(pool, pool_result) + + def test_list_volumes(self): + RestHandler.login = mock.Mock(return_value=None) + RestHandler.get_rest_info = mock.Mock( + side_effect=[GET_ALL_POOLS, GET_ALL_LUNS]) + vol = DS8KDriver(**ACCESS_INFO).list_volumes(context) + self.assertEqual(vol, volume_result) + + @mock.patch.object(RestHandler, 'get_rest_info') + def test_list_alerts(self, mock_alert): + RestHandler.login = mock.Mock(return_value=None) + mock_alert.return_value = GET_ALL_ALERTS + alert = DS8KDriver(**ACCESS_INFO).list_alerts(context) + alert[0]['occur_time'] = alert_result[0]['occur_time'] + self.assertEqual(alert, alert_result) + + @mock.patch.object(RestHandler, 'call_with_token') + def test_call_and_login(self, mock_token): + with self.assertRaises(Exception) as exc: + mock_token.return_value = mock.MagicMock( + status_code=401, text='Authentication has failed') + DS8KDriver(**ACCESS_INFO).rest_handler.login() + self.assertEqual('Invalid username or password.', str(exc.exception)) + RestHandler.login = mock.Mock(return_value=None) + mock_token.return_value = mock.MagicMock(status_code=401) + DS8KDriver(**ACCESS_INFO).rest_handler.call('') + + @mock.patch.object(RestHandler, 'get_rest_info') + def test_list_ports(self, mock_port): + RestHandler.login = mock.Mock(return_value=None) + mock_port.return_value = GET_ALL_PORTS + port = DS8KDriver(**ACCESS_INFO).list_ports(context) + self.assertEqual(port, port_result) + + @mock.patch.object(RestHandler, 'get_rest_info') + def test_list_list_controllers(self, mock_contrl): + RestHandler.login = mock.Mock(return_value=None) + mock_contrl.return_value = GET_ALL_CONTROLLERS + controller = DS8KDriver(**ACCESS_INFO).list_controllers(context) + self.assertEqual(controller, contrl_result) diff --git a/setup.py b/setup.py index 224f498f8..630ca5258 100644 --- a/setup.py +++ b/setup.py @@ -48,6 +48,7 @@ 'hpe msa = delfin.drivers.hpe.hpe_msa.hpe_msastor:HpeMsaStorDriver', 'huawei oceanstor = delfin.drivers.huawei.oceanstor.oceanstor:OceanStorDriver', 'ibm storwize_svc = delfin.drivers.ibm.storwize_svc.storwize_svc:StorwizeSVCDriver', + 'ibm ds8k = delfin.drivers.ibm.ds8k.ds8k:DS8KDriver', 'netapp cmode = delfin.drivers.netapp.dataontap.cluster_mode:NetAppCmodeDriver', 'hitachi hnas = delfin.drivers.hitachi.hnas.hds_nas:HitachiHNasDriver' ]