diff --git a/delfin/api/v1/port_groups.py b/delfin/api/v1/port_groups.py index 052e6580f..da8119bf8 100644 --- a/delfin/api/v1/port_groups.py +++ b/delfin/api/v1/port_groups.py @@ -42,6 +42,22 @@ def show(self, req, id): port_groups = db.port_groups_get_all( ctxt, marker, limit, sort_keys, sort_dirs, query_params, offset) + + # Get Port Group to Port relation from DB + for port_group in port_groups: + params = { + "native_port_group_id": + port_group['native_port_group_id'] + } + ports = db.port_grp_port_rels_get_all( + ctxt, filters=params) + + native_port_id_list = [] + for port in ports: + native_port_id_list.append(port['native_port_id']) + + port_group['ports'] = native_port_id_list + return port_group_view.build_port_groups(port_groups) diff --git a/delfin/api/v1/storage_host_groups.py b/delfin/api/v1/storage_host_groups.py index a15669022..65171a6ae 100644 --- a/delfin/api/v1/storage_host_groups.py +++ b/delfin/api/v1/storage_host_groups.py @@ -42,6 +42,23 @@ def show(self, req, id): storage_host_groups = db.storage_host_groups_get_all( ctxt, marker, limit, sort_keys, sort_dirs, query_params, offset) + + # Get Storage Host Group to Host relation from DB + for host_group in storage_host_groups: + params = { + "native_storage_host_group_id": + host_group['native_storage_host_group_id'] + } + hosts = db.storage_host_grp_host_rels_get_all( + ctxt, filters=params) + + native_storage_host_id_list = [] + for host in hosts: + native_storage_host_id_list.append( + host['native_storage_host_id']) + + host_group['storage_hosts'] = native_storage_host_id_list + return storage_host_group_view\ .build_storage_host_groups(storage_host_groups) diff --git a/delfin/api/v1/volume_groups.py b/delfin/api/v1/volume_groups.py index c6a6cf35c..d97f328dd 100644 --- a/delfin/api/v1/volume_groups.py +++ b/delfin/api/v1/volume_groups.py @@ -42,6 +42,22 @@ def show(self, req, id): volume_groups = db.volume_groups_get_all( ctxt, marker, limit, sort_keys, sort_dirs, query_params, offset) + + # Get Volume Group to Volume relation from DB + for volume_group in volume_groups: + params = { + "native_volume_group_id": + volume_group['native_volume_group_id'] + } + volumes = db.vol_grp_vol_rels_get_all( + ctxt, filters=params) + + native_volume_id_list = [] + for volume in volumes: + native_volume_id_list.append(volume['native_volume_id']) + + volume_group['volumes'] = native_volume_id_list + return volume_group_view.build_volume_groups(volume_groups) diff --git a/delfin/common/constants.py b/delfin/common/constants.py index 1f7e6f146..9665d8e6d 100644 --- a/delfin/common/constants.py +++ b/delfin/common/constants.py @@ -238,6 +238,39 @@ class ShareProtocol(object): ALL = (CIFS, NFS, FTP, HDFS) +class HostStatus(object): + NORMAL = 'normal' + OFFLINE = 'offline' + ABNORMAL = 'abnormal' + + ALL = (NORMAL, OFFLINE, ABNORMAL) + + +class HostOSTypes(object): + LINUX = 'Linux' + WINDOWS = 'Windows' + SOLARIS = 'Solaris' + HP_UX = 'HP-UX' + AIX = 'AIX' + XEN_SERVER = 'XenServer' + VMWARE_ESX = 'VMware ESX' + LINUX_VIS = 'LINUX_VIS' + WINDOWS_SERVER_2012 = 'Windows Server 2012' + ORACLE_VM = 'Oracle VM' + OPEN_VMS = 'Open VMS' + + ALL = (LINUX, WINDOWS, SOLARIS, HP_UX, AIX, XEN_SERVER, VMWARE_ESX, + LINUX_VIS, WINDOWS_SERVER_2012, ORACLE_VM, OPEN_VMS) + + +class InitiatorStatus(object): + ONLINE = 'online' + OFFLINE = 'offline' + UNKNOWN = 'unknown' + + ALL = (ONLINE, OFFLINE, UNKNOWN) + + # Enumerations for alert severity class Severity(object): FATAL = 'Fatal' diff --git a/delfin/db/sqlalchemy/api.py b/delfin/db/sqlalchemy/api.py index 755fa65c0..224b1adb3 100644 --- a/delfin/db/sqlalchemy/api.py +++ b/delfin/db/sqlalchemy/api.py @@ -2822,7 +2822,7 @@ def vol_grp_vol_rels_get_all(context, marker=None, limit=None, with session.begin(): # Generate the query query = _generate_paginate_query(context, session, models. - VolGrpVolRelation, + VolGrpVolRel, marker, limit, sort_keys, sort_dirs, filters, offset) # No volume grp volume relation would match, return empty list diff --git a/delfin/drivers/fake_storage/__init__.py b/delfin/drivers/fake_storage/__init__.py index 1a8fe60b0..7bbef40d9 100644 --- a/delfin/drivers/fake_storage/__init__.py +++ b/delfin/drivers/fake_storage/__init__.py @@ -76,12 +76,13 @@ # Min and max are currently set to 1 to make sure at least one relation can be # built in fake driver for host mapping elements -MIN_STORAGE_HOST_INITIATORS, MAX_STORAGE_HOST_INITIATORS = 1, 1 -MIN_STORAGE_HOSTS, MAX_STORAGE_HOSTS = 1, 1 -MIN_STORAGE_HOST_GROUPS, MAX_STORAGE_HOST_GROUPS = 1, 1 -MIN_VOLUME_GROUPS, MAX_VOLUME_GROUPS = 1, 1 -MIN_PORT_GROUPS, MAX_PORT_GROUPS = 1, 1 -MIN_MASKING_VIEWS, MAX_MASKING_VIEWS = 1, 1 +MIN_STORAGE_HOST_INITIATORS, MAX_STORAGE_HOST_INITIATORS = 1, 3 +MIN_STORAGE_HOSTS, MAX_STORAGE_HOSTS = 1, 5 +MIN_STORAGE_HOST_GROUPS, MAX_STORAGE_HOST_GROUPS = 1, 5 +MIN_VOLUME_GROUPS, MAX_VOLUME_GROUPS = 1, 5 +MIN_PORT_GROUPS, MAX_PORT_GROUPS = 1, 5 +MAX_GROUP_RESOURCES_SIZE = 5 +MIN_MASKING_VIEWS, MAX_MASKING_VIEWS = 1, 5 def get_range_val(range_str, t): @@ -124,6 +125,10 @@ def __init__(self, **kwargs): MIN_VOLUME, MAX_VOLUME = get_range_val( CONF.fake_driver.fake_volume_range, int) PAGE_LIMIT = int(CONF.fake_driver.fake_page_query_limit) + self.rd_volumes_count = random.randint(MIN_VOLUME, MAX_VOLUME) + self.rd_ports_count = random.randint(MIN_PORTS, MAX_PORTS) + self.rd_storage_hosts_count = random.randint(MIN_STORAGE_HOSTS, + MAX_STORAGE_HOSTS) def _get_random_capacity(self): total = random.randint(1000, 2000) @@ -190,7 +195,7 @@ def list_storage_pools(self, ctx): def list_volumes(self, ctx): # Get a random number as the volume count. - rd_volumes_count = random.randint(MIN_VOLUME, MAX_VOLUME) + rd_volumes_count = self.rd_volumes_count LOG.info("###########fake_volumes number for %s: %d" % ( self.storage_id, rd_volumes_count)) loops = math.ceil(rd_volumes_count / PAGE_LIMIT) @@ -228,7 +233,7 @@ def list_controllers(self, ctx): return ctrl_list def list_ports(self, ctx): - rd_ports_count = random.randint(MIN_PORTS, MAX_PORTS) + rd_ports_count = self.rd_ports_count LOG.info("###########fake_ports for %s: %d" % (self.storage_id, rd_ports_count)) port_list = [] @@ -871,8 +876,7 @@ def list_storage_host_initiators(self, ctx): return storage_host_initiators_list def list_storage_hosts(self, ctx): - rd_storage_hosts_count = random.randint(MIN_STORAGE_HOSTS, - MAX_STORAGE_HOSTS) + rd_storage_hosts_count = self.rd_storage_hosts_count LOG.info("###########fake_storage_hosts for %s: %d" % (self.storage_id, rd_storage_hosts_count)) storage_host_list = [] @@ -890,33 +894,72 @@ def list_storage_hosts(self, ctx): return storage_host_list def list_storage_host_groups(self, ctx): - rd_storage_host_groups_count = random.randint(MIN_STORAGE_HOST_GROUPS, - MAX_STORAGE_HOST_GROUPS) + rd_storage_host_groups_count = random.randint( + MIN_STORAGE_HOST_GROUPS, MAX_STORAGE_HOST_GROUPS) LOG.info("###########fake_storage_host_groups for %s: %d" % (self.storage_id, rd_storage_host_groups_count)) storage_host_grp_list = [] for idx in range(rd_storage_host_groups_count): + # Create hosts in hosts group + host_name_list = [] + storage_hosts_count = self.rd_storage_hosts_count - 1 + if storage_hosts_count > 0: + for i in range(MAX_GROUP_RESOURCES_SIZE): + host_name = "storage_host_" + str( + random.randint(0, storage_hosts_count)) + if host_name not in host_name_list: + host_name_list.append(host_name) + + # Create comma separated list + storage_hosts = None + for host in host_name_list: + if storage_hosts: + storage_hosts = storage_hosts + "," + host + else: + storage_hosts = host + f = { "name": "storage_host_group_" + str(idx), "description": "storage_host_group_" + str(idx), "storage_id": self.storage_id, "native_storage_host_group_id": "storage_host_group_" + str(idx), + "storage_hosts": storage_hosts } storage_host_grp_list.append(f) return storage_host_grp_list def list_port_groups(self, ctx): - rd_port_groups_count = random.randint(MIN_PORT_GROUPS, MAX_PORT_GROUPS) + rd_port_groups_count = random.randint(MIN_PORT_GROUPS, + MAX_PORT_GROUPS) LOG.info("###########fake_port_groups for %s: %d" % (self.storage_id, rd_port_groups_count)) port_grp_list = [] for idx in range(rd_port_groups_count): + # Create ports in ports group + port_name_list = [] + ports_count = self.rd_ports_count - 1 + if ports_count > 0: + for i in range(MAX_GROUP_RESOURCES_SIZE): + port_name = "port_" + str( + random.randint(0, ports_count)) + if port_name not in port_name_list: + port_name_list.append(port_name) + + # Create comma separated list + ports = None + for port in port_name_list: + if ports: + ports = ports + "," + port + else: + ports = port + f = { "name": "port_group_" + str(idx), "description": "port_group_" + str(idx), "storage_id": self.storage_id, "native_port_group_id": "port_group_" + str(idx), + "ports": ports } port_grp_list.append(f) @@ -929,11 +972,30 @@ def list_volume_groups(self, ctx): % (self.storage_id, rd_volume_groups_count)) volume_grp_list = [] for idx in range(rd_volume_groups_count): + # Create volumes in volumes group + volume_name_list = [] + volumes_count = self.rd_volumes_count - 1 + if volumes_count > 0: + for i in range(MAX_GROUP_RESOURCES_SIZE): + volume_name = "volume_" + str( + random.randint(0, volumes_count)) + if volume_name not in volume_name_list: + volume_name_list.append(volume_name) + + # Create comma separated list + volumes = None + for volume in volume_name_list: + if volumes: + volumes = volumes + "," + volume + else: + volumes = volume + f = { "name": "volume_group_" + str(idx), "description": "volume_group_" + str(idx), "storage_id": self.storage_id, "native_volume_group_id": "volume_group_" + str(idx), + "volumes": volumes } volume_grp_list.append(f) return volume_grp_list diff --git a/delfin/drivers/huawei/oceanstor/consts.py b/delfin/drivers/huawei/oceanstor/consts.py index 362eb421c..a0439a1e4 100644 --- a/delfin/drivers/huawei/oceanstor/consts.py +++ b/delfin/drivers/huawei/oceanstor/consts.py @@ -160,6 +160,32 @@ THICK_LUNTYPE = '0' THIN_LUNTYPE = '1' +HOST_OS = [ + constants.HostOSTypes.LINUX, + constants.HostOSTypes.WINDOWS, + constants.HostOSTypes.SOLARIS, + constants.HostOSTypes.HP_UX, + constants.HostOSTypes.AIX, + constants.HostOSTypes.XEN_SERVER, + constants.HostOSTypes.VMWARE_ESX, + constants.HostOSTypes.LINUX_VIS, + constants.HostOSTypes.WINDOWS_SERVER_2012, + constants.HostOSTypes.ORACLE_VM, + constants.HostOSTypes.OPEN_VMS, +] + +HOST_RUNNINGSTATUS_NORMAL = '1' +INITIATOR_RUNNINGSTATUS_UNKNOWN = '0' +INITIATOR_RUNNINGSTATUS_ONLINE = '27' +INITIATOR_RUNNINGSTATUS_OFFLINE = '28' +ISCSI_INITIATOR_TYPE = 222 +FC_INITIATOR_TYPE = 223 +IB_INITIATOR_TYPE = 16499 +ISCSI_INITIATOR_DESCRIPTION = 'iSCSI Initiator' +FC_INITIATOR_DESCRIPTION = 'FC Initiator' +IB_INITIATOR_DESCRIPTION = 'IB Initiator' +UNKNOWN_INITIATOR_DESCRIPTION = 'Unknown Initiator' + OCEANSTOR_METRICS = { 'iops': '22', 'readIops': '25', diff --git a/delfin/drivers/huawei/oceanstor/oceanstor.py b/delfin/drivers/huawei/oceanstor/oceanstor.py index 85ca041a1..0c4ee97fc 100644 --- a/delfin/drivers/huawei/oceanstor/oceanstor.py +++ b/delfin/drivers/huawei/oceanstor/oceanstor.py @@ -12,14 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -import six - from oslo_config import cfg from oslo_log import log from delfin.common import constants from delfin.drivers.huawei.oceanstor import rest_client, consts, alert_handler from delfin.drivers import driver -from delfin import exception LOG = log.getLogger(__name__) CONF = cfg.CONF @@ -125,11 +122,9 @@ def list_storage_pools(self, context): return pool_list - except Exception as err: - LOG.error( - "Failed to get pool metrics from OceanStor: {}".format(err)) - raise exception.StorageBackendException( - 'Failed to get pool metrics from OceanStor') + except Exception: + LOG.error("Failed to get pool metrics from OceanStor") + raise def _get_orig_pool_id(self, pools, volume): for pool in pools: @@ -187,11 +182,9 @@ def list_volumes(self, context): return volume_list - except Exception as err: - LOG.error( - "Failed to get list volumes from OceanStor: {}".format(err)) - raise exception.StorageBackendException( - 'Failed to get list volumes from OceanStor') + except Exception: + LOG.error("Failed to get list volumes from OceanStor") + raise def list_controllers(self, context): try: @@ -220,17 +213,9 @@ def list_controllers(self, context): return controller_list - except exception.DelfinException as err: - err_msg = "Failed to get controller metrics from OceanStor: %s" %\ - (six.text_type(err)) - LOG.error(err_msg) - raise err - - except Exception as err: - err_msg = "Failed to get controller metrics from OceanStor: %s" %\ - (six.text_type(err)) - LOG.error(err_msg) - raise exception.InvalidResults(err_msg) + except Exception: + LOG.error("Failed to get controller metrics from OceanStor") + raise def list_ports(self, context): try: @@ -299,17 +284,9 @@ def list_ports(self, context): return port_list - except exception.DelfinException as err: - err_msg = "Failed to get port metrics from OceanStor: %s" %\ - (six.text_type(err)) - LOG.error(err_msg) - raise err - - except Exception as err: - err_msg = "Failed to get port metrics from OceanStor: %s" %\ - (six.text_type(err)) - LOG.error(err_msg) - raise exception.InvalidResults(err_msg) + except Exception: + LOG.error("Failed to get port metrics from OceanStor") + raise def list_disks(self, context): try: @@ -355,17 +332,9 @@ def list_disks(self, context): return disk_list - except exception.DelfinException as err: - err_msg = "Failed to get disk metrics from OceanStor: %s" %\ - (six.text_type(err)) - LOG.error(err_msg) - raise err - - except Exception as err: - err_msg = "Failed to get disk metrics from OceanStor: %s" %\ - (six.text_type(err)) - LOG.error(err_msg) - raise exception.InvalidResults(err_msg) + except Exception: + LOG.error("Failed to get disk metrics from OceanStor") + raise def _list_quotas(self, quotas, fs_id, qt_id): q_type = { @@ -428,17 +397,9 @@ def list_quotas(self, context): return quotas_list - except exception.DelfinException as err: - err_msg = "Failed to get quotas from OceanStor: %s" %\ - (six.text_type(err)) - LOG.error(err_msg) - raise err - - except Exception as err: - err_msg = "Failed to get quotas from OceanStor: %s" %\ - (six.text_type(err)) - LOG.error(err_msg) - raise exception.InvalidResults(err_msg) + except Exception: + LOG.error("Failed to get quotas from OceanStor") + raise def list_filesystems(self, context): try: @@ -495,17 +456,9 @@ def list_filesystems(self, context): return fs_list - except exception.DelfinException as err: - err_msg = "Failed to get filesystems from OceanStor: %s" %\ - (six.text_type(err)) - LOG.error(err_msg) - raise err - - except Exception as err: - err_msg = "Failed to get filesystems from OceanStor: %s" %\ - (six.text_type(err)) - LOG.error(err_msg) - raise exception.InvalidResults(err_msg) + except Exception: + LOG.error("Failed to get filesystems from OceanStor") + raise def list_qtrees(self, context): try: @@ -535,17 +488,9 @@ def list_qtrees(self, context): return qt_list - except exception.DelfinException as err: - err_msg = "Failed to get qtrees from OceanStor: %s" %\ - (six.text_type(err)) - LOG.error(err_msg) - raise err - - except Exception as err: - err_msg = "Failed to get qtrees from OceanStor: %s" %\ - (six.text_type(err)) - LOG.error(err_msg) - raise exception.InvalidResults(err_msg) + except Exception: + LOG.error("Failed to get qtrees from OceanStor") + raise def list_shares(self, context): try: @@ -575,17 +520,9 @@ def list_shares(self, context): return s_list - except exception.DelfinException as err: - err_msg = "Failed to get shares from OceanStor: %s" %\ - (six.text_type(err)) - LOG.error(err_msg) - raise err - - except Exception as err: - err_msg = "Failed to get shares from OceanStor: %s" %\ - (six.text_type(err)) - LOG.error(err_msg) - raise exception.InvalidResults(err_msg) + except Exception: + LOG.error("Failed to get shares from OceanStor") + raise def add_trap_config(self, context, trap_config): pass @@ -616,17 +553,9 @@ def collect_perf_metrics(self, context, storage_id, self.client.configure_metrics_collection() self.init_perf_config = False - except exception.DelfinException as err: - err_msg = "Failed to configure collection in OceanStor: %s" %\ - (six.text_type(err)) - LOG.error(err_msg) - raise err - - except Exception as err: - err_msg = "Failed to configure collection in OceanStor: %s" %\ - (six.text_type(err)) - LOG.error(err_msg) - raise exception.InvalidResults(err_msg) + except Exception: + LOG.error("Failed to configure collection in OceanStor") + raise metrics = [] try: @@ -665,17 +594,9 @@ def collect_perf_metrics(self, context, storage_id, resource_metrics.get(constants.ResourceType.DISK)) metrics.extend(disk_metrics) - except exception.DelfinException as err: - err_msg = "Failed to collect metrics from OceanStor: %s" %\ - (six.text_type(err)) - LOG.error(err_msg) - raise err - - except Exception as err: - err_msg = "Failed to collect metrics from OceanStor: %s" %\ - (six.text_type(err)) - LOG.error(err_msg) - raise exception.InvalidResults(err_msg) + except Exception: + LOG.error("Failed to collect metrics from OceanStor") + raise return metrics @@ -692,3 +613,235 @@ def get_capabilities(context): constants.ResourceType.DISK: consts.DISK_CAP } } + + def list_storage_host_initiators(self, ctx): + try: + # Get list of OceanStor initiators details + initiators = self.client.get_all_initiators() + initiator_list = [] + switcher = { + consts.INITIATOR_RUNNINGSTATUS_ONLINE: + constants.InitiatorStatus.ONLINE, + consts.INITIATOR_RUNNINGSTATUS_OFFLINE: + constants.InitiatorStatus.OFFLINE, + consts.INITIATOR_RUNNINGSTATUS_UNKNOWN: + constants.InitiatorStatus.UNKNOWN, + } + type_switch = { + consts.ISCSI_INITIATOR_TYPE: + consts.ISCSI_INITIATOR_DESCRIPTION, + consts.FC_INITIATOR_TYPE: + consts.FC_INITIATOR_DESCRIPTION, + consts.IB_INITIATOR_TYPE: + consts.IB_INITIATOR_DESCRIPTION, + } + for initiator in initiators: + status = switcher.get(initiator['RUNNINGSTATUS'], + constants.InitiatorStatus.UNKNOWN) + description = type_switch.get( + initiator['TYPE'], consts.UNKNOWN_INITIATOR_DESCRIPTION) + + initiator_item = { + "name": initiator.get('NAME'), + "description": description, + "alias": initiator['ID'], + "storage_id": self.storage_id, + "native_storage_host_initiator_id": initiator['ID'], + "wwn": initiator['ID'], + "status": status, + "native_storage_host_id": initiator.get('PARENTID'), + } + initiator_list.append(initiator_item) + + return initiator_list + + except Exception: + LOG.error("Failed to get initiators from OceanStor") + raise + + def list_storage_hosts(self, ctx): + try: + # Get list of OceanStor host details + hosts = self.client.get_all_hosts() + host_list = [] + for host in hosts: + os_type = '' + host_os = int(host['OPERATIONSYSTEM']) + if host_os < len(consts.HOST_OS): + os_type = consts.HOST_OS[host_os] + status = constants.HostStatus.NORMAL + if host['RUNNINGSTATUS'] != consts.HOST_RUNNINGSTATUS_NORMAL: + status = constants.HostStatus.ABNORMAL + + h = { + "name": host['NAME'], + "description": host['DESCRIPTION'], + "storage_id": self.storage_id, + "native_storage_host_id": host['ID'], + "os_type": os_type, + "status": status, + "ip_address": host['IP'] + } + host_list.append(h) + + return host_list + + except Exception: + LOG.error("Failed to get host metrics from OceanStor") + raise + + def list_storage_host_groups(self, ctx): + try: + # Get list of OceanStor host_groups details + host_groups = self.client.get_all_host_groups() + host_group_list = [] + for host_group in host_groups: + hosts = self.client.get_all_associate_hosts( + host_group['TYPE'], host_group['ID']) + hosts_str = None + for host in hosts: + if hosts_str: + hosts_str = "{0},{1}".format(hosts_str, host['ID']) + else: + hosts_str = "{0}".format(host['ID']) + + host_g = { + "name": host_group['NAME'], + "description": host_group['DESCRIPTION'], + "storage_id": self.storage_id, + "native_storage_host_group_id": host_group['ID'], + "storage_hosts": hosts_str + } + host_group_list.append(host_g) + + return host_group_list + + except Exception: + LOG.error("Failed to get host_groups from OceanStor") + raise + + def list_port_groups(self, ctx): + try: + # Get list of OceanStor port_groups details + port_groups = self.client.get_all_port_groups() + port_group_list = [] + for port_group in port_groups: + ports = self.client.get_all_associate_ports( + port_group['TYPE'], port_group['ID']) + ports_str = None + for port in ports: + if ports_str: + ports_str = "{0},{1}".format(ports_str, port['ID']) + else: + ports_str = "{0}".format(port['ID']) + + port_g = { + "name": port_group['NAME'], + "description": port_group['DESCRIPTION'], + "storage_id": self.storage_id, + "native_port_group_id": port_group['ID'], + "ports": ports_str + } + port_group_list.append(port_g) + + return port_group_list + + except Exception: + LOG.error("Failed to get port_groups from OceanStor") + raise + + def list_volume_groups(self, ctx): + try: + # Get list of OceanStor vol_groups details + vol_groups = self.client.get_all_volume_groups() + vol_group_list = [] + for vol_group in vol_groups: + volumes = self.client.get_all_associate_volumes( + vol_group['TYPE'], vol_group['ID']) + volumes_str = None + for volume in volumes: + if volumes_str: + volumes_str = "{0},{1}".format(volumes_str, + volume['ID']) + else: + volumes_str = "{0}".format(volume['ID']) + + vol_g = { + "name": vol_group['NAME'], + "description": vol_group['DESCRIPTION'], + "storage_id": self.storage_id, + "native_volume_group_id": vol_group['ID'], + "volumes": volumes_str + } + vol_group_list.append(vol_g) + + return vol_group_list + + except Exception: + LOG.error("Failed to get vol_groups from OceanStor") + raise + + def list_masking_views(self, ctx): + try: + # Get list of OceanStor masking view details + views = self.client.get_all_mapping_views() + + view_dict = {} + for view in views: + v = { + "name": view['NAME'], + "description": view['DESCRIPTION'], + "storage_id": self.storage_id, + "native_masking_view_id": view['ID'], + } + view_dict[view['ID']] = v + + view_keys = view_dict.keys() + + host_groups = self.client.get_all_host_groups() + for host_group in host_groups: + hg_views = self.client.get_all_associate_mapping_views( + host_group['TYPE'], host_group['ID']) + for hg_view in hg_views: + v_id = hg_view['ID'] + if v_id in view_keys: + view_dict[v_id]['native_storage_host_group_id'] =\ + host_group['ID'] + else: + msg = "Missing mapping view for host group id {0}".\ + format(host_group['ID']) + LOG.info(msg) + + volume_groups = self.client.get_all_volume_groups() + for volume_group in volume_groups: + vg_views = self.client.get_all_associate_mapping_views( + volume_group['TYPE'], volume_group['ID']) + for vg_view in vg_views: + v_id = vg_view['ID'] + if v_id in view_keys: + view_dict[v_id]['native_volume_group_id'] =\ + volume_group['ID'] + else: + msg = "Missing mapping view for volume group id {0}".\ + format(volume_group['ID']) + LOG.info(msg) + + port_groups = self.client.get_all_port_groups() + for port_group in port_groups: + pg_views = self.client.get_all_associate_mapping_views( + port_group['TYPE'], port_group['ID']) + for pg_view in pg_views: + v_id = pg_view['ID'] + if v_id in view_keys: + view_dict[v_id]['native_port_group_id'] =\ + port_group['ID'] + else: + msg = "Missing mapping view for port group id {0}".\ + format(port_group['ID']) + LOG.info(msg) + + return list(view_dict.values()) + + except Exception: + LOG.error("Failed to get view metrics from OceanStor") + raise diff --git a/delfin/drivers/huawei/oceanstor/rest_client.py b/delfin/drivers/huawei/oceanstor/rest_client.py index 8172422fe..17314e50d 100644 --- a/delfin/drivers/huawei/oceanstor/rest_client.py +++ b/delfin/drivers/huawei/oceanstor/rest_client.py @@ -368,6 +368,68 @@ def get_all_shares(self): return cifs + nfs + ftps + def get_all_mapping_views(self): + url = "/mappingview" + view = self.paginated_call(url, None, "GET", log_filter_flag=True) + return view + + def get_all_associate_resources(self, url, obj_type, obj_id): + params = "ASSOCIATEOBJTYPE={0}&ASSOCIATEOBJID={1}&".format(obj_type, + obj_id) + return self.paginated_call(url, None, "GET", + params=params, log_filter_flag=True) + + def get_all_associate_mapping_views(self, obj_type, obj_id): + url = "/mappingview/associate" + return self.get_all_associate_resources(url, obj_type, obj_id) + + def get_all_associate_hosts(self, obj_type, obj_id): + url = "/host/associate" + return self.get_all_associate_resources(url, obj_type, obj_id) + + def get_all_associate_volumes(self, obj_type, obj_id): + url = "/lun/associate" + return self.get_all_associate_resources(url, obj_type, obj_id) + + def get_all_associate_ports(self, obj_type, obj_id): + eth_ports = self.get_all_associate_resources( + "/eth_port/associate", obj_type, obj_id) + fc_ports = self.get_all_associate_resources( + "/fc_port/associate", obj_type, obj_id) + fcoe_ports = self.get_all_associate_resources( + "/fcoe_port/associate", obj_type, obj_id) + + return eth_ports + fc_ports + fcoe_ports + + def get_all_hosts(self): + url = "/host" + host = self.paginated_call(url, None, "GET", log_filter_flag=True) + return host + + def get_all_initiators(self): + url = "/fc_initiator" + fc_i = self.paginated_call(url, None, "GET", log_filter_flag=True) + url = "/iscsi_initiator" + iscsi_i = self.paginated_call(url, None, "GET", log_filter_flag=True) + url = "/ib_initiator" + ib_i = self.paginated_call(url, None, "GET", log_filter_flag=True) + return fc_i + iscsi_i + ib_i + + def get_all_host_groups(self): + url = "/hostgroup" + hostg = self.paginated_call(url, None, "GET", log_filter_flag=True) + return hostg + + def get_all_port_groups(self): + url = "/portgroup" + portg = self.paginated_call(url, None, "GET", log_filter_flag=True) + return portg + + def get_all_volume_groups(self): + url = "/lungroup" + lungroup = self.paginated_call(url, None, "GET", log_filter_flag=True) + return lungroup + def clear_alert(self, sequence_number): url = "/alarm/currentalarm?sequence=%s" % sequence_number diff --git a/delfin/task_manager/tasks/resources.py b/delfin/task_manager/tasks/resources.py index 1da8961da..5803fcefe 100644 --- a/delfin/task_manager/tasks/resources.py +++ b/delfin/task_manager/tasks/resources.py @@ -19,6 +19,9 @@ from delfin import coordination from delfin import db +from delfin.db.sqlalchemy.models import StorageHostGrpHostRel +from delfin.db.sqlalchemy.models import VolGrpVolRel +from delfin.db.sqlalchemy.models import PortGrpPortRel from delfin import exception from delfin.common import constants from delfin.drivers import api as driverapi @@ -80,6 +83,77 @@ def _check_deleted(func, *args, **kwargs): return _check_deleted +def _build_storage_host_group_relations(ctx, storage_id, + storage_host_groups): + """ Builds storage host group to host relations.""" + db.storage_host_grp_host_rels_delete_by_storage(ctx, + storage_id) + storage_host_grp_relation_list = [] + for storage_host_group in storage_host_groups: + storage_hosts = storage_host_group.pop('storage_hosts', None) + if not storage_hosts: + continue + storage_hosts = storage_hosts.split(',') + + for storage_host in storage_hosts: + storage_host_group_relation = { + StorageHostGrpHostRel.storage_id.name: storage_id, + StorageHostGrpHostRel.native_storage_host_group_id.name: + storage_host_group['native_storage_host_group_id'], + StorageHostGrpHostRel.native_storage_host_id.name: + storage_host + } + storage_host_grp_relation_list \ + .append(storage_host_group_relation) + + db.storage_host_grp_host_rels_create( + ctx, storage_host_grp_relation_list) + + +def _build_volume_group_relations(ctx, storage_id, volume_groups): + """ Builds volume group to volume relations.""" + db.vol_grp_vol_rels_delete_by_storage(ctx, storage_id) + volume_group_relation_list = [] + for volume_group in volume_groups: + volumes = volume_group.pop('volumes', None) + if not volumes: + continue + volumes = volumes.split(',') + + for volume in volumes: + volume_group_relation = { + VolGrpVolRel.storage_id.name: storage_id, + VolGrpVolRel.native_volume_group_id.name: + volume_group['native_volume_group_id'], + VolGrpVolRel.native_volume_id.name: volume} + volume_group_relation_list.append(volume_group_relation) + + db.vol_grp_vol_rels_create(ctx, volume_group_relation_list) + + +def _build_port_group_relations(ctx, storage_id, port_groups): + """ Builds resource group to resource relations.""" + db.port_grp_port_rels_delete_by_storage(ctx, storage_id) + + port_group_relation_list = [] + for port_group in port_groups: + ports = port_group.pop('ports', None) + if not ports: + continue + ports = ports.split(',') + + for port in ports: + port_group_relation = { + PortGrpPortRel.storage_id.name: storage_id, + PortGrpPortRel.native_port_group_id .name: + port_group['native_port_group_id'], + PortGrpPortRel.native_port_id.name: port + } + port_group_relation_list.append(port_group_relation) + + db.port_grp_port_rels_create(ctx, port_group_relation_list) + + class StorageResourceTask(object): def __init__(self, context, storage_id): @@ -741,6 +815,12 @@ def sync(self): # Build relation between host grp and host to be handled here. storage_host_groups = self.driver_api \ .list_storage_host_groups(self.context, self.storage_id) + if storage_host_groups: + _build_storage_host_group_relations( + self.context, self.storage_id, storage_host_groups) + LOG.info('Building host group relations successful for ' + 'storage id:{0}'.format(self.storage_id)) + db_storage_host_groups = db.storage_host_groups_get_all( self.context, filters={"storage_id": self.storage_id}) @@ -777,6 +857,8 @@ def sync(self): def remove(self): LOG.info('Remove storage host groups for storage id:{0}' .format(self.storage_id)) + db.storage_host_grp_host_rels_delete_by_storage(self.context, + self.storage_id) db.storage_host_groups_delete_by_storage(self.context, self.storage_id) @@ -797,6 +879,12 @@ def sync(self): # Build relation between port grp and port to be handled here. port_groups = self.driver_api \ .list_port_groups(self.context, self.storage_id) + if port_groups: + _build_port_group_relations( + self.context, self.storage_id, port_groups) + LOG.info('Building port group relations successful for ' + 'storage id:{0}'.format(self.storage_id)) + db_port_groups = db.port_groups_get_all( self.context, filters={"storage_id": self.storage_id}) @@ -831,6 +919,8 @@ def sync(self): def remove(self): LOG.info('Remove port groups for storage id:{0}' .format(self.storage_id)) + db.port_grp_port_rels_delete_by_storage(self.context, + self.storage_id) db.port_groups_delete_by_storage(self.context, self.storage_id) @@ -851,6 +941,12 @@ def sync(self): # Build relation between volume grp and volume to be handled here. volume_groups = self.driver_api \ .list_volume_groups(self.context, self.storage_id) + if volume_groups: + _build_volume_group_relations( + self.context, self.storage_id, volume_groups) + LOG.info('Building volume group relations successful for ' + 'storage id:{0}'.format(self.storage_id)) + db_volume_groups = db.volume_groups_get_all( self.context, filters={"storage_id": self.storage_id}) @@ -885,6 +981,7 @@ def sync(self): def remove(self): LOG.info('Remove volume groups for storage id:{0}' .format(self.storage_id)) + db.vol_grp_vol_rels_delete_by_storage(self.context, self.storage_id) db.volume_groups_delete_by_storage(self.context, self.storage_id) diff --git a/delfin/tests/e2e/GetResources.robot b/delfin/tests/e2e/GetResources.robot index aea2c4250..b74a14c4e 100644 --- a/delfin/tests/e2e/GetResources.robot +++ b/delfin/tests/e2e/GetResources.robot @@ -112,7 +112,7 @@ Get All Storages Open Application ${array_id}= Register Test Storage - Sleep 5s + Sleep 10s Close Application @{storages}= Get All Storages @@ -120,4 +120,4 @@ Close Application ${storage_id}= Get Value From Json ${storage} $..id Delete Storage With ID ${storage_id[0]} END - Sleep 5s + Sleep 10s diff --git a/delfin/tests/e2e/GetStorage.robot b/delfin/tests/e2e/GetStorage.robot index 1b1579bac..919626ec5 100644 --- a/delfin/tests/e2e/GetStorage.robot +++ b/delfin/tests/e2e/GetStorage.robot @@ -62,7 +62,7 @@ Delete Storage With ID Create Session delfin ${delfin_url} ${resp_del}= DELETE On Session delfin storages/${storage_id} Status Should Be 202 ${resp_del} - Sleep 5s + Sleep 10s Register Test Storage ${test}= Load Json From File ${CURDIR}/test.json diff --git a/delfin/tests/e2e/RegisterStorage.robot b/delfin/tests/e2e/RegisterStorage.robot index 745e509d7..6542525b5 100644 --- a/delfin/tests/e2e/RegisterStorage.robot +++ b/delfin/tests/e2e/RegisterStorage.robot @@ -66,7 +66,7 @@ Register Storage with valid access_info Test Register Storage with same access_info Test [Tags] DELFIN - Sleep 5s + Sleep 10s ${storage_test}= Register Test Storage ${test}= Load Json From File ${CURDIR}/test.json @@ -93,4 +93,4 @@ Delete Storage With ID Create Session delfin ${delfin_url} ${resp_del}= DELETE On Session delfin storages/${storage_id} Status Should Be 202 ${resp_del} - Sleep 5s + Sleep 10s diff --git a/delfin/tests/e2e/RemoveStorage.robot b/delfin/tests/e2e/RemoveStorage.robot index 0c2d9ce51..356aa38a0 100644 --- a/delfin/tests/e2e/RemoveStorage.robot +++ b/delfin/tests/e2e/RemoveStorage.robot @@ -12,7 +12,7 @@ ${delfin_url} http://localhost:8190/v1 Delete Storage with valid storage_id [Tags] DELFIN - Sleep 5s + Sleep 10s ${storage_id_test}= Register Test Storage Create Session delfin ${delfin_url} ${resp_del}= DELETE On Session delfin storages/${storage_id_test} diff --git a/delfin/tests/e2e/UpdateAccessInfo.robot b/delfin/tests/e2e/UpdateAccessInfo.robot index bc514f34c..f48384053 100644 --- a/delfin/tests/e2e/UpdateAccessInfo.robot +++ b/delfin/tests/e2e/UpdateAccessInfo.robot @@ -97,7 +97,7 @@ Delete Storage With ID Create Session delfin ${delfin_url} ${resp_del}= DELETE On Session delfin storages/${storage_id} Status Should Be 202 ${resp_del} - Sleep 5s + Sleep 10s Get All Storages @@ -113,9 +113,9 @@ Close Application ${storage_id}= Get Value From Json ${storage} $..id Delete Storage With ID ${storage_id[0]} END - Sleep 5s + Sleep 10s Open Application ${array_id}= Register Test Storage - Sleep 5s + Sleep 10s diff --git a/delfin/tests/unit/drivers/huawei/oceanstor/test_oceanstor.py b/delfin/tests/unit/drivers/huawei/oceanstor/test_oceanstor.py index 20eaa042f..e2676a476 100644 --- a/delfin/tests/unit/drivers/huawei/oceanstor/test_oceanstor.py +++ b/delfin/tests/unit/drivers/huawei/oceanstor/test_oceanstor.py @@ -217,7 +217,7 @@ def test_list_storage_pools(self): side_effect=exception.DelfinException): with self.assertRaises(Exception) as exc: driver.list_storage_pools(context) - self.assertIn('Exception from Storage Backend', + self.assertIn('An unknown exception occurred', str(exc.exception)) def test_list_volumes(self): @@ -326,7 +326,7 @@ def test_list_volumes(self): side_effect=exception.DelfinException): with self.assertRaises(Exception) as exc: driver.list_volumes(context) - self.assertIn('Exception from Storage Backend', + self.assertIn('An unknown exception occurred', str(exc.exception)) def test_list_ports(self): @@ -598,8 +598,7 @@ def test_list_ports(self): side_effect=TypeError): with self.assertRaises(Exception) as exc: driver.list_ports(context) - self.assertIn('The results are invalid', - str(exc.exception)) + self.assertIn('', str(exc.exception)) def test_list_controllers(self): driver = create_driver() @@ -700,8 +699,7 @@ def test_list_controllers(self): side_effect=TypeError): with self.assertRaises(Exception) as exc: driver.list_controllers(context) - self.assertIn('The results are invalid', - str(exc.exception)) + self.assertIn('', str(exc.exception)) def test_list_disks(self): driver = create_driver() @@ -841,8 +839,7 @@ def test_list_disks(self): side_effect=TypeError): with self.assertRaises(Exception) as exc: driver.list_disks(context) - self.assertIn('The results are invalid', - str(exc.exception)) + self.assertIn('', str(exc.exception)) def test_list_filesystems(self): driver = create_driver() @@ -973,8 +970,7 @@ def test_list_filesystems(self): side_effect=TypeError): with self.assertRaises(Exception) as exc: driver.list_filesystems(context) - self.assertIn('The results are invalid', - str(exc.exception)) + self.assertIn('', str(exc.exception)) def test_list_qtrees(self): driver = create_driver() @@ -1057,8 +1053,7 @@ def test_list_qtrees(self): side_effect=TypeError): with self.assertRaises(Exception) as exc: driver.list_qtrees(context) - self.assertIn('The results are invalid', - str(exc.exception)) + self.assertIn('', str(exc.exception)) def test_list_shares(self): driver = create_driver() @@ -1150,9 +1145,427 @@ def test_list_shares(self): side_effect=TypeError): with self.assertRaises(Exception) as exc: driver.list_shares(context) - self.assertIn('The results are invalid', + self.assertIn('', str(exc.exception)) + + def test_list_storage_host_initiators(self): + driver = create_driver() + expected = [ + { + 'name': '12', + 'description': 'FC Initiator', + 'alias': '1212121212121212', + 'storage_id': '12345', + 'native_storage_host_initiator_id': '1212121212121212', + 'wwn': '1212121212121212', + 'status': 'online', + 'native_storage_host_id': '0' + } + ] + + ret = [ + { + 'data': [ + { + "HEALTHSTATUS": "1", + "ID": "1212121212121212", + "ISFREE": "true", + "MULTIPATHTYPE": "1", + "NAME": "12", + "OPERATIONSYSTEM": "1", + "PARENTID": "0", + "PARENTTYPE": 0, + "PARENTNAME": "Host001", + "RUNNINGSTATUS": "27", + "TYPE": 223, + "FAILOVERMODE": "3", + "SPECIALMODETYPE": "2", + "PATHTYPE": "1" + } + ], + 'error': { + 'code': 0, + 'description': '0' + } + }, + { + 'data': [ + { + "HEALTHSTATUS": "1", + "ID": "111111111111111111", + "ISFREE": "false", + "MULTIPATHTYPE": "1", + "OPERATIONSYSTEM": "255", + "PARENTID": "0", + "PARENTNAME": "Host001", + "PARENTTYPE": 21, + "RUNNINGSTATUS": "28", + "TYPE": 222, + "USECHAP": "false", + "FAILOVERMODE": "3", + "SPECIALMODETYPE": "2", + "PATHTYPE": "1" + } + ], + 'error': { + 'code': 0, + 'description': '0' + } + }, + { + 'data': [ + { + "HEALTHSTATUS": "1", + "ID": "1111111111111119", + "ISFREE": "true", + "MULTIPATHTYPE": "1", + "NAME": "", + "OPERATIONSYSTEM": "1", + "RUNNINGSTATUS": "28", + "TYPE": 16499, + "FAILOVERMODE": "3", + "SPECIALMODETYPE": "2", + "PATHTYPE": "1" + } + ], + 'error': { + 'code': 0, + 'description': '0' + } + }, + ] + with mock.patch.object(RestClient, 'do_call', side_effect=ret): + initators = driver.list_storage_host_initiators(context) + self.assertDictEqual(initators[0], expected[0]) + + with mock.patch.object(RestClient, 'get_all_initiators', + side_effect=exception.DelfinException): + with self.assertRaises(Exception) as exc: + driver.list_storage_host_initiators(context) + self.assertIn('An unknown exception occurred', str(exc.exception)) + with mock.patch.object(RestClient, 'get_all_initiators', + side_effect=TypeError): + with self.assertRaises(Exception) as exc: + driver.list_storage_host_initiators(context) + self.assertIn('', str(exc.exception)) + + def test_list_storage_hosts(self): + driver = create_driver() + expected = [ + { + 'name': 'Host001', + 'description': '', + 'storage_id': '12345', + 'native_storage_host_id': '0', + 'os_type': 'Linux', + 'status': 'normal', + 'ip_address': '' + } + ] + + ret = [ + { + 'data': [ + { + "DESCRIPTION": "", + "HEALTHSTATUS": "1", + "ID": "0", + "INITIATORNUM": "0", + "IP": "", + "ISADD2HOSTGROUP": "true", + "LOCATION": "", + "MODEL": "", + "NAME": "Host001", + "NETWORKNAME": "", + "OPERATIONSYSTEM": "0", + "RUNNINGSTATUS": "1", + "TYPE": 21, + "vstoreId": "4", + "vstoreName": "vStore004" + } + ], + 'error': { + 'code': 0, + 'description': '0' + } + } + ] + with mock.patch.object(RestClient, 'do_call', side_effect=ret): + hosts = driver.list_storage_hosts(context) + self.assertDictEqual(hosts[0], expected[0]) + + with mock.patch.object(RestClient, 'get_all_hosts', + side_effect=exception.DelfinException): + with self.assertRaises(Exception) as exc: + driver.list_storage_hosts(context) + self.assertIn('An unknown exception occurred', + str(exc.exception)) + + with mock.patch.object(RestClient, 'get_all_hosts', + side_effect=TypeError): + with self.assertRaises(Exception) as exc: + driver.list_storage_hosts(context) + self.assertIn('', str(exc.exception)) + + def test_list_storage_host_groups(self): + driver = create_driver() + expected = [ + { + 'name': 'hostgroup1', + 'description': '', + 'storage_id': '12345', + 'native_storage_host_group_id': '0', + 'storage_hosts': '123' + } + ] + + ret = [ + { + 'data': [ + { + "DESCRIPTION": "", + "ID": "0", + "ISADD2MAPPINGVIEW": "false", + "NAME": "hostgroup1", + "TYPE": 14, + "vstoreId": "4", + "vstoreName": "vStore004" + }, + ], + 'error': { + 'code': 0, + 'description': '0' + } + }, + { + 'data': [ + { + "ID": "123", + }, + ], + 'error': { + 'code': 0, + 'description': '0' + } + } + ] + with mock.patch.object(RestClient, 'do_call', side_effect=ret): + hg = driver.list_storage_host_groups(context) + self.assertDictEqual(hg[0], expected[0]) + + with mock.patch.object(RestClient, 'get_all_host_groups', + side_effect=exception.DelfinException): + with self.assertRaises(Exception) as exc: + driver.list_storage_host_groups(context) + self.assertIn('An unknown exception occurred', + str(exc.exception)) + + with mock.patch.object(RestClient, 'get_all_host_groups', + side_effect=TypeError): + with self.assertRaises(Exception) as exc: + driver.list_storage_host_groups(context) + self.assertIn('', str(exc.exception)) + + def test_list_port_groups(self): + driver = create_driver() + expected = [ + { + 'name': 'PortGroup001', + 'description': '', + 'storage_id': '12345', + 'native_port_group_id': '0', + 'ports': '123,124,125', + } + ] + + ret = [ + { + 'data': [ + { + "DESCRIPTION": "", + "ID": "0", + "NAME": "PortGroup001", + "TYPE": 257 + } + ], + 'error': { + 'code': 0, + 'description': '0' + } + }, + { + 'data': [ + { + "ID": "123", + } + ], + 'error': { + 'code': 0, + 'description': '0' + } + }, + { + 'data': [ + { + "ID": "124", + } + ], + 'error': { + 'code': 0, + 'description': '0' + } + }, + { + 'data': [ + { + "ID": "125", + } + ], + 'error': { + 'code': 0, + 'description': '0' + } + }, + ] + with mock.patch.object(RestClient, 'do_call', side_effect=ret): + port_groups = driver.list_port_groups(context) + self.assertDictEqual(port_groups[0], expected[0]) + + with mock.patch.object(RestClient, 'get_all_port_groups', + side_effect=exception.DelfinException): + with self.assertRaises(Exception) as exc: + driver.list_port_groups(context) + self.assertIn('An unknown exception occurred', + str(exc.exception)) + + with mock.patch.object(RestClient, 'get_all_port_groups', + side_effect=TypeError): + with self.assertRaises(Exception) as exc: + driver.list_port_groups(context) + self.assertIn('', str(exc.exception)) + + def test_list_volume_groups(self): + driver = create_driver() + expected = [ + { + 'name': 'LUNGroup001', + 'description': '', + 'storage_id': '12345', + 'native_volume_group_id': '0', + 'volumes': '123' + } + ] + + ret = [ + { + 'data': [ + { + "APPTYPE": "0", + "CAPCITY": "2097152", + "CONFIGDATA": "", + "DESCRIPTION": "", + "GROUPTYPE": "0", + "ID": "0", + "ISADD2MAPPINGVIEW": "false", + "NAME": "LUNGroup001", + "TYPE": 256, + "vstoreId": "4", + "vstoreName": "vStore004" + } + ], + 'error': { + 'code': 0, + 'description': '0' + } + }, + { + 'data': [ + { + "ID": "123", + } + ], + 'error': { + 'code': 0, + 'description': '0' + } + }, + ] + with mock.patch.object(RestClient, 'do_call', side_effect=ret): + volume_groups = driver.list_volume_groups(context) + self.assertDictEqual(volume_groups[0], expected[0]) + + with mock.patch.object(RestClient, 'get_all_volume_groups', + side_effect=exception.DelfinException): + with self.assertRaises(Exception) as exc: + driver.list_volume_groups(context) + self.assertIn('An unknown exception occurred', + str(exc.exception)) + + with mock.patch.object(RestClient, 'get_all_volume_groups', + side_effect=TypeError): + with self.assertRaises(Exception) as exc: + driver.list_volume_groups(context) + self.assertIn('', str(exc.exception)) + + @mock.patch.object(RestClient, 'get_all_associate_mapping_views') + @mock.patch.object(RestClient, 'get_all_port_groups') + @mock.patch.object(RestClient, 'get_all_volume_groups') + @mock.patch.object(RestClient, 'get_all_host_groups') + def test_list_masking_views(self, mock_hg, mock_vg, + mock_pg, mock_associate): + driver = create_driver() + expected = [ + { + 'name': 'MappingView001', + 'description': '', + 'storage_id': '12345', + 'native_masking_view_id': '1', + } + ] + + ret = [ + { + 'data': [ + { + "DESCRIPTION": "", + "ENABLEINBANDCOMMAND": "true", + "ID": "1", + "INBANDLUNWWN": "", + "NAME": "MappingView001", + "TYPE": 245, + "vstoreId": "4", + "vstoreName": "vStore004" + } + ], + 'error': { + 'code': 0, + 'description': '0' + } + } + ] + mock_hg.return_value = [] + mock_vg.return_value = [] + mock_pg.return_value = [] + mock_associate.return_value = [] + + with mock.patch.object(RestClient, 'do_call', side_effect=ret): + view = driver.list_masking_views(context) + self.assertDictEqual(view[0], expected[0]) + + with mock.patch.object(RestClient, 'get_all_mapping_views', + side_effect=exception.DelfinException): + with self.assertRaises(Exception) as exc: + driver.list_masking_views(context) + self.assertIn('An unknown exception occurred', + str(exc.exception)) + + with mock.patch.object(RestClient, 'get_all_mapping_views', + side_effect=TypeError): + with self.assertRaises(Exception) as exc: + driver.list_masking_views(context) + self.assertIn('', str(exc.exception)) + @mock.patch.object(RestClient, 'get_disk_metrics') @mock.patch.object(RestClient, 'get_port_metrics') @mock.patch.object(RestClient, 'get_controller_metrics') @@ -1216,8 +1629,7 @@ def test_collect_perf_metrics(self, mock_di, mock_en, driver.collect_perf_metrics(context, 0, {'disk': {'iops': 'iops'}}, 0, 0) - self.assertIn('The results are invalid', - str(exc.exception)) + self.assertIn('', str(exc.exception)) def test_get_capabilities(self): driver = create_driver() diff --git a/delfin/tests/unit/drivers/huawei/oceanstor/test_rest_client.py b/delfin/tests/unit/drivers/huawei/oceanstor/test_rest_client.py index f255f58eb..400040c1f 100644 --- a/delfin/tests/unit/drivers/huawei/oceanstor/test_rest_client.py +++ b/delfin/tests/unit/drivers/huawei/oceanstor/test_rest_client.py @@ -13,6 +13,8 @@ # limitations under the License. from unittest import TestCase, mock +from unittest.mock import call + from requests.sessions import Session from delfin import exception @@ -144,6 +146,93 @@ def test_get_all_pools(self, mock_login, mock_call): mock_call.assert_called_with("/storagepool", None, 'GET', log_filter_flag=True) + @mock.patch.object(RestClient, 'paginated_call') + @mock.patch.object(RestClient, 'login') + def test_get_all_hosts(self, mock_login, mock_call): + mock_login.return_value = None + mock_call.return_value = RESP + kwargs = ACCESS_INFO + rest_client = RestClient(**kwargs) + data = rest_client.get_all_hosts() + self.assertEqual(data['data']['data'], 'dummy') + mock_call.assert_called_with("/host", None, + 'GET', log_filter_flag=True) + + @mock.patch.object(RestClient, 'paginated_call') + @mock.patch.object(RestClient, 'login') + def test_get_all_host_groups(self, mock_login, mock_call): + mock_login.return_value = None + mock_call.return_value = RESP + kwargs = ACCESS_INFO + rest_client = RestClient(**kwargs) + data = rest_client.get_all_host_groups() + self.assertEqual(data['data']['data'], 'dummy') + mock_call.assert_called_with("/hostgroup", None, + 'GET', log_filter_flag=True) + + @mock.patch.object(RestClient, 'paginated_call') + @mock.patch.object(RestClient, 'login') + def test_get_all_port_groups(self, mock_login, mock_call): + mock_login.return_value = None + mock_call.return_value = RESP + kwargs = ACCESS_INFO + rest_client = RestClient(**kwargs) + data = rest_client.get_all_port_groups() + self.assertEqual(data['data']['data'], 'dummy') + mock_call.assert_called_with("/portgroup", None, + 'GET', log_filter_flag=True) + + @mock.patch.object(RestClient, 'paginated_call') + @mock.patch.object(RestClient, 'login') + def test_get_all_volume_groups(self, mock_login, mock_call): + mock_login.return_value = None + mock_call.return_value = RESP + kwargs = ACCESS_INFO + rest_client = RestClient(**kwargs) + data = rest_client.get_all_volume_groups() + self.assertEqual(data['data']['data'], 'dummy') + mock_call.assert_called_with("/lungroup", None, + 'GET', log_filter_flag=True) + + @mock.patch.object(RestClient, 'paginated_call') + @mock.patch.object(RestClient, 'login') + def test_get_all_volumes(self, mock_login, mock_call): + mock_login.return_value = None + mock_call.return_value = RESP + kwargs = ACCESS_INFO + rest_client = RestClient(**kwargs) + data = rest_client.get_all_volumes() + self.assertEqual(data['data']['data'], 'dummy') + mock_call.assert_called_with("/lun", None, + 'GET', log_filter_flag=True) + + @mock.patch.object(RestClient, 'paginated_call') + @mock.patch.object(RestClient, 'login') + def test_get_all_initiators(self, mock_login, mock_call): + mock_login.return_value = None + mock_call.side_effects = ["", "", ""] + kwargs = ACCESS_INFO + rest_client = RestClient(**kwargs) + rest_client.get_all_initiators() + call1 = call("/fc_initiator", None, 'GET', log_filter_flag=True) + call2 = call("/iscsi_initiator", None, 'GET', log_filter_flag=True) + call3 = call("/ib_initiator", None, 'GET', log_filter_flag=True) + + calls = [call1, call2, call3] + mock_call.assert_has_calls(calls) + + @mock.patch.object(RestClient, 'paginated_call') + @mock.patch.object(RestClient, 'login') + def test_get_all_mapping_views(self, mock_login, mock_call): + mock_login.return_value = None + mock_call.return_value = RESP + kwargs = ACCESS_INFO + rest_client = RestClient(**kwargs) + data = rest_client.get_all_mapping_views() + self.assertEqual(data['data']['data'], 'dummy') + mock_call.assert_called_with("/mappingview", None, + 'GET', log_filter_flag=True) + @mock.patch.object(RestClient, 'paginated_call') @mock.patch.object(RestClient, 'login') def test_get_volumes(self, mock_login, mock_call): diff --git a/delfin/tests/unit/task_manager/test_resources.py b/delfin/tests/unit/task_manager/test_resources.py index b51056fba..dd39bc9ef 100644 --- a/delfin/tests/unit/task_manager/test_resources.py +++ b/delfin/tests/unit/task_manager/test_resources.py @@ -924,3 +924,109 @@ def test_remove(self, mock_volume_groups_del): context, 'c5c91c98-91aa-40e6-85ac-37a1d3b32bda') volume_group_obj.remove() self.assertTrue(mock_volume_groups_del.called) + + +class TestPortGroupTask(test.TestCase): + @mock.patch.object(coordination.LOCK_COORDINATOR, 'get_lock') + @mock.patch('delfin.drivers.api.API.list_port_groups') + @mock.patch('delfin.db.port_groups_get_all') + @mock.patch('delfin.db.port_groups_delete') + @mock.patch('delfin.db.port_groups_update') + @mock.patch('delfin.db.port_groups_create') + def test_sync_successful(self, mock_port_group_create, + mock_port_group_update, + mock_port_group_del, + mock_port_groups_get_all, + mock_list_port_groups, get_lock): + ctxt = context.get_admin_context() + port_group_obj = resources.PortGroupTask( + ctxt, 'c5c91c98-91aa-40e6-85ac-37a1d3b32bda') + port_group_obj.sync() + self.assertTrue(mock_list_port_groups.called) + self.assertTrue(mock_port_groups_get_all.called) + self.assertTrue(get_lock.called) + + # Collect the storage host groups from fake_storage + fake_storage_obj = fake_storage.FakeStorageDriver() + + # Add the storage host groups to DB + mock_list_port_groups.return_value \ + = fake_storage_obj.list_port_groups(context) + mock_port_groups_get_all.return_value = list() + port_group_obj.sync() + self.assertTrue(mock_port_group_create.called) + + # Update the storage host groups to DB + mock_list_port_groups.return_value \ + = port_groups_list + mock_port_groups_get_all.return_value \ + = port_groups_list + port_group_obj.sync() + self.assertTrue(mock_port_group_update.called) + + # Delete the storage host groups to DB + mock_list_port_groups.return_value = list() + mock_port_groups_get_all.return_value \ + = port_groups_list + port_group_obj.sync() + self.assertTrue(mock_port_group_del.called) + + @mock.patch('delfin.db.port_groups_delete_by_storage') + def test_remove(self, mock_port_groups_del): + port_group_obj = resources.PortGroupTask( + context, 'c5c91c98-91aa-40e6-85ac-37a1d3b32bda') + port_group_obj.remove() + self.assertTrue(mock_port_groups_del.called) + + +class TestMaskingViewTask(test.TestCase): + @mock.patch.object(coordination.LOCK_COORDINATOR, 'get_lock') + @mock.patch('delfin.drivers.api.API.list_masking_views') + @mock.patch('delfin.db.masking_views_get_all') + @mock.patch('delfin.db.masking_views_delete') + @mock.patch('delfin.db.masking_views_update') + @mock.patch('delfin.db.masking_views_create') + def test_sync_successful(self, mock_masking_view_create, + mock_masking_view_update, + mock_masking_view_del, + mock_masking_views_get_all, + mock_list_masking_views, get_lock): + cntxt = context.get_admin_context() + masking_view_obj = resources.MaskingViewTask( + cntxt, 'c5c91c98-91aa-40e6-85ac-37a1d3b32bda') + masking_view_obj.sync() + self.assertTrue(mock_list_masking_views.called) + self.assertTrue(mock_masking_views_get_all.called) + self.assertTrue(get_lock.called) + + # Collect the volume groups from fake_storage + fake_storage_obj = fake_storage.FakeStorageDriver() + + # Add the volume groups to DB + mock_list_masking_views.return_value \ + = fake_storage_obj.list_masking_views(context) + mock_masking_views_get_all.return_value = list() + masking_view_obj.sync() + self.assertTrue(mock_masking_view_create.called) + + # Update the volume groups to DB + mock_list_masking_views.return_value \ + = masking_views_list + mock_masking_views_get_all.return_value \ + = masking_views_list + masking_view_obj.sync() + self.assertTrue(mock_masking_view_update.called) + + # Delete the volume groups to DB + mock_list_masking_views.return_value = list() + mock_masking_views_get_all.return_value \ + = masking_views_list + masking_view_obj.sync() + self.assertTrue(mock_masking_view_del.called) + + @mock.patch('delfin.db.masking_views_delete_by_storage') + def test_remove(self, mock_masking_views_del): + masking_view_obj = resources.MaskingViewTask( + context, 'c5c91c98-91aa-40e6-85ac-37a1d3b32bda') + masking_view_obj.remove() + self.assertTrue(mock_masking_views_del.called) diff --git a/openapi-spec/swagger.yaml b/openapi-spec/swagger.yaml index f05cd8ecb..7b417efc1 100644 --- a/openapi-spec/swagger.yaml +++ b/openapi-spec/swagger.yaml @@ -2323,7 +2323,7 @@ paths: '/v1/storages/{storage_id}/storage-host-initiators': get: tags: - - Storage host initiators + - Masking views description: List all storage host initiators. parameters: - name: storage_id @@ -2480,7 +2480,7 @@ paths: '/v1/storages/{storage_id}/storage-hosts': get: tags: - - Storage hosts + - Masking views description: List all storage hosts. parameters: - name: storage_id @@ -2626,6 +2626,357 @@ paths: application/json: schema: $ref: '#/components/schemas/ErrorSpec' + '/v1/storages/{storage_id}/storage-host-groups': + get: + tags: + - Masking views + description: List all storage host groups. + parameters: + - name: storage_id + in: path + description: Database ID created for a storage backend. + required: true + style: simple + explode: false + schema: + type: string + - name: limit + in: query + description: >- + Requests a page size of items. Returns a number of items up to a + limit value. Use the limit parameter to make an initial limited + request and use the ID of the last-seen item from the response as + the marker parameter value in a subsequent limited request. + required: false + style: form + explode: true + schema: + minimum: 1 + type: integer + format: int32 + - name: offset + in: query + description: >- + Used in conjunction with limit to return a slice of items. + offset is where to start in the list. + required: false + style: form + explode: true + schema: + minimum: 0 + type: integer + format: int32 + - name: sort + in: query + description: >- + Comma-separated list of sort keys and optional sort directions in + the form of key:val + required: false + style: form + explode: true + schema: + type: string + example: 'sort=name:desc,id:asc' + - name: name + in: query + description: The storage host group name + required: false + style: form + explode: true + schema: + type: string + - name: id + in: query + description: Database ID created for a storage host group. + required: false + style: form + explode: true + schema: + type: string + - name: description + in: query + description: The storage host group description + required: false + style: form + explode: true + schema: + type: string + - name: native_storage_host_group_id + in: query + description: Actual ID of the storage host group in the storage backend. + required: false + style: form + explode: true + schema: + type: string + responses: + '200': + description: List storage host groups query was success + content: + application/json: + schema: + type: object + required: + - storage_host_groups + additionalProperties: true + properties: + storage_hosts: + type: array + title: the storage host group schema + items: + $ref: '#/components/schemas/StorageHostGroupRespSpec' + '401': + description: NotAuthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorSpec' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorSpec' + '500': + description: An unexpected error occured. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorSpec' + '/v1/storages/{storage_id}/port-groups': + get: + tags: + - Masking views + description: List all port groups. + parameters: + - name: storage_id + in: path + description: Database ID created for a storage backend. + required: true + style: simple + explode: false + schema: + type: string + - name: limit + in: query + description: >- + Requests a page size of items. Returns a number of items up to a + limit value. Use the limit parameter to make an initial limited + request and use the ID of the last-seen item from the response as + the marker parameter value in a subsequent limited request. + required: false + style: form + explode: true + schema: + minimum: 1 + type: integer + format: int32 + - name: offset + in: query + description: >- + Used in conjunction with limit to return a slice of items. + offset is where to start in the list. + required: false + style: form + explode: true + schema: + minimum: 0 + type: integer + format: int32 + - name: sort + in: query + description: >- + Comma-separated list of sort keys and optional sort directions in + the form of key:val + required: false + style: form + explode: true + schema: + type: string + example: 'sort=name:desc,id:asc' + - name: name + in: query + description: The port group name + required: false + style: form + explode: true + schema: + type: string + - name: id + in: query + description: Database ID created for a port group. + required: false + style: form + explode: true + schema: + type: string + - name: description + in: query + description: The port group description + required: false + style: form + explode: true + schema: + type: string + - name: native_port_group_id + in: query + description: Actual ID of the port group in the storage backend. + required: false + style: form + explode: true + schema: + type: string + responses: + '200': + description: List port groups query was success + content: + application/json: + schema: + type: object + required: + - storage_hosts + additionalProperties: true + properties: + storage_hosts: + type: array + title: the port groups schema + items: + $ref: '#/components/schemas/PortGroupRespSpec' + '401': + description: NotAuthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorSpec' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorSpec' + '500': + description: An unexpected error occured. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorSpec' + '/v1/storages/{storage_id}/volume-groups': + get: + tags: + - Masking views + description: List all volume groups. + parameters: + - name: storage_id + in: path + description: Database ID created for a storage backend. + required: true + style: simple + explode: false + schema: + type: string + - name: limit + in: query + description: >- + Requests a page size of items. Returns a number of items up to a + limit value. Use the limit parameter to make an initial limited + request and use the ID of the last-seen item from the response as + the marker parameter value in a subsequent limited request. + required: false + style: form + explode: true + schema: + minimum: 1 + type: integer + format: int32 + - name: offset + in: query + description: >- + Used in conjunction with limit to return a slice of items. + offset is where to start in the list. + required: false + style: form + explode: true + schema: + minimum: 0 + type: integer + format: int32 + - name: sort + in: query + description: >- + Comma-separated list of sort keys and optional sort directions in + the form of key:val + required: false + style: form + explode: true + schema: + type: string + example: 'sort=name:desc,id:asc' + - name: name + in: query + description: The volume group name + required: false + style: form + explode: true + schema: + type: string + - name: id + in: query + description: Database ID created for a volume group. + required: false + style: form + explode: true + schema: + type: string + - name: description + in: query + description: The volume group description + required: false + style: form + explode: true + schema: + type: string + - name: native_volume_group_id + in: query + description: Actual ID of the volume group in the storage backend. + required: false + style: form + explode: true + schema: + type: string + responses: + '200': + description: List volume groups query was success + content: + application/json: + schema: + type: object + required: + - storage_hosts + additionalProperties: true + properties: + storage_hosts: + type: array + title: the volume groups schema + items: + $ref: '#/components/schemas/VolumeGroupRespSpec' + '401': + description: NotAuthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorSpec' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorSpec' + '500': + description: An unexpected error occured. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorSpec' '/v1/storages/{storage_id}/masking-views': get: tags: @@ -3956,6 +4307,87 @@ components: type: string readOnly: true description: List of storage host initiator native ids. + StorageHostGroupRespSpec: + description: >- + Storage host group is a consumer of volume from storage. + allOf: + - $ref: '#/components/schemas/BaseModel' + - type: object + properties: + name: + type: string + description: + type: string + readOnly: true + storage_id: + type: string + readOnly: true + example: 084bf71e-a102-11e7-88a8-e31fe6d52248 + native_storage_host_group_id: + type: string + readOnly: true + description: Actual ID of the storage host group in the storage backend. + example: storage_host_group0 + storage_hosts: + type: array + items: + type: string + readOnly: true + description: List of storage host native ids. + PortGroupRespSpec: + description: >- + Port group is collection of ports from storage. + allOf: + - $ref: '#/components/schemas/BaseModel' + - type: object + properties: + name: + type: string + description: + type: string + readOnly: true + storage_id: + type: string + readOnly: true + example: 084bf71e-a102-11e7-88a8-e31fe6d52248 + native_port_group_id: + type: string + readOnly: true + description: Actual ID of the port group in the storage backend. + example: port_group_0 + ports: + type: array + items: + type: string + readOnly: true + description: List of ports native ids. + VolumeGroupRespSpec: + description: >- + Volume group is collection of volumes from storage. + allOf: + - $ref: '#/components/schemas/BaseModel' + - type: object + properties: + name: + type: string + description: + type: string + readOnly: true + storage_id: + type: string + readOnly: true + example: 084bf71e-a102-11e7-88a8-e31fe6d52248 + native_volume_group_id: + type: string + readOnly: true + description: Actual ID of the volume group in the storage backend. + example: volume_group_0 + volumes: + type: array + items: + type: string + readOnly: true + description: List of volumes native ids. MaskingViewRespSpec: description: >- Masking view is and object which shows the path from host and lun.