diff --git a/delfin/drivers/dell_emc/vnx/vnx_block/component_handler.py b/delfin/drivers/dell_emc/vnx/vnx_block/component_handler.py index 57310ec4c..914885c22 100644 --- a/delfin/drivers/dell_emc/vnx/vnx_block/component_handler.py +++ b/delfin/drivers/dell_emc/vnx/vnx_block/component_handler.py @@ -11,6 +11,7 @@ # 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 copy import re import six @@ -443,3 +444,124 @@ def get_iscsi_ports(self): name = '%s-%s' % (iscsi_port.get('sp'), iscsi_port.get('port_id')) iscsi_port_map[name] = iscsi_port return iscsi_port_map + + def list_masking_views(self, storage_id): + views = self.navi_handler.list_masking_views() + views_list = [] + host_vv_set = set() + if views: + for view in views: + name = view.get('storage_group_name') + host_names = view.get('host_names') + lun_ids = view.get('lun_ids') + if name: + if name == '~physical' or name == '~management': + continue + view_model_template = { + 'native_masking_view_id': view.get( + 'storage_group_uid'), + "name": view.get('storage_group_name'), + "storage_id": storage_id + } + if host_names and lun_ids: + host_names = list(set(host_names)) + for host_name in host_names: + host_id = host_name.replace(' ', '') + for lun_id in lun_ids: + host_vv_key = '%s_%s' % (host_id, lun_id) + if host_vv_key in host_vv_set: + continue + host_vv_set.add(host_vv_key) + view_model = copy.deepcopy(view_model_template) + view_model[ + 'native_storage_host_id'] = host_id + view_model['native_volume_id'] = lun_id + view_model[ + 'native_masking_view_id'] = '%s_%s_%s' % ( + view_model.get('native_masking_view_id'), + host_id, lun_id) + views_list.append(view_model) + return views_list + + def list_storage_host_initiators(self, storage_id): + initiators = self.navi_handler.list_hbas() + initiators_list = [] + initiator_set = set() + port_types = {} + if initiators: + ports = self.list_ports(storage_id) + for port in (ports or []): + if port and port.get('type'): + port_types[port.get('name')] = port.get('type') + for initiator in (initiators or []): + if initiator and initiator.get('hba_uid'): + hba_uid = initiator.get('hba_uid') + type = '' + if port_types: + ports = initiator.get('port_ids') + if ports: + port_id = list(ports)[0] + type = port_types.get(port_id, '') + host_id = initiator.get('server_name', '').replace(' ', '') + if host_id == hba_uid: + host_id = None + if not host_id: + continue + if hba_uid in initiator_set: + continue + initiator_set.add(hba_uid) + + initiator_model = { + "name": hba_uid, + "storage_id": storage_id, + "native_storage_host_initiator_id": hba_uid, + "wwn": hba_uid, + "type": consts.INITIATOR_TYPE_MAP.get( + type.upper(), constants.InitiatorType.UNKNOWN), + "status": constants.InitiatorStatus.ONLINE, + "native_storage_host_id": host_id + } + initiators_list.append(initiator_model) + return initiators_list + + def list_storage_hosts(self, storage_id): + hosts = self.navi_handler.list_hbas() + host_list = [] + host_ids = set() + host_ips = {} + for host in (hosts or []): + if host and host.get('server_name'): + os_type = constants.HostOSTypes.UNKNOWN + os_name = host.get('hba_vendor_description') + ip_addr = host.get('server_ip_address') + if ip_addr == 'UNKNOWN': + continue + if os_name and 'VMware ESXi' in os_name: + os_type = constants.HostOSTypes.VMWARE_ESX + id = host.get('server_name').replace(' ', '') + if id in host_ids: + continue + host_ids.add(id) + + if ip_addr in host_ips.keys(): + first_port_ids = host_ips.get(ip_addr) + cur_port_ids = host.get('port_ids') + add_host = False + intersections = list( + set(first_port_ids).intersection(set(cur_port_ids))) + if not intersections: + add_host = True + if not add_host: + continue + host_ips[ip_addr] = host.get('port_ids') + + host_model = { + "name": host.get('server_name'), + "storage_id": storage_id, + "native_storage_host_id": id, + "os_type": os_type, + "status": constants.HostStatus.NORMAL, + "ip_address": ip_addr + } + host_list.append(host_model) + return host_list diff --git a/delfin/drivers/dell_emc/vnx/vnx_block/consts.py b/delfin/drivers/dell_emc/vnx/vnx_block/consts.py index 9348d8c4c..d012a74bb 100644 --- a/delfin/drivers/dell_emc/vnx/vnx_block/consts.py +++ b/delfin/drivers/dell_emc/vnx/vnx_block/consts.py @@ -64,6 +64,8 @@ GET_LOG_API = 'getlog -date %(begin_time)s %(end_time)s' EMCVNX_VENDOR = 'DELL EMC' RAID_GROUP_ID_PREFIX = 'raid_group_' +GET_SG_LIST_HOST_API = 'storagegroup -messner -list -host' +GET_PORT_LIST_HBA_API = 'port -list -hba' STATUS_MAP = { 'Ready': constants.StoragePoolStatus.NORMAL, 'Offline': constants.StoragePoolStatus.OFFLINE, @@ -143,3 +145,12 @@ 'SAS': constants.PortType.SAS, 'UNKNOWN': constants.PortType.OTHER } +INITIATOR_TYPE_MAP = { + 'FC': constants.InitiatorType.FC, + 'FCOE': constants.InitiatorType.FC, + 'ISCSI': constants.InitiatorType.ISCSI, + 'SAS': constants.InitiatorType.SAS, + 'UNKNOWN': constants.InitiatorType.UNKNOWN +} +ALU_PAIRS_PATTERN = '^[0-9]+\\s+[0-9]+$' +HBA_UID_PATTERN = "^\\s*HBA UID\\s+SP Name\\s+SPPort" diff --git a/delfin/drivers/dell_emc/vnx/vnx_block/navi_handler.py b/delfin/drivers/dell_emc/vnx/vnx_block/navi_handler.py index 767815976..7483ec955 100644 --- a/delfin/drivers/dell_emc/vnx/vnx_block/navi_handler.py +++ b/delfin/drivers/dell_emc/vnx/vnx_block/navi_handler.py @@ -11,6 +11,7 @@ # 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 threading import six @@ -606,3 +607,96 @@ def navi_exe(self, command_str, host_ip=None): raise e finally: self.session_lock.release() + + def list_masking_views(self): + return self.get_resources_info(consts.GET_SG_LIST_HOST_API, + self.cli_sg_to_list) + + def cli_sg_to_list(self, resource_info): + obj_list = [] + obj_model = {} + try: + obj_infos = resource_info.split('\n') + pattern = re.compile(consts.ALU_PAIRS_PATTERN) + for obj_info in obj_infos: + str_line = obj_info.strip() + if str_line: + if ':' not in str_line: + search_obj = pattern.search(str_line) + if search_obj: + str_info = str_line.split() + lun_ids = obj_model.get('lun_ids') + if lun_ids: + lun_ids.add(str_info[1]) + else: + lun_ids = set() + lun_ids.add(str_info[1]) + obj_model['lun_ids'] = lun_ids + else: + str_info = self.split_str_by_colon(str_line) + if 'Host name:' in str_line: + host_names = obj_model.get('host_names') + if host_names: + host_names.add(str_info[1]) + else: + host_names = set() + host_names.add(str_info[1]) + obj_model['host_names'] = host_names + continue + + obj_model = self.str_info_to_model(str_info, obj_model) + + if str_line.startswith('Shareable:'): + obj_list = self.add_model_to_list(obj_model, + obj_list) + obj_model = {} + except Exception as e: + err_msg = "arrange sg info error: %s", six.text_type(e) + LOG.error(err_msg) + raise exception.InvalidResults(err_msg) + return obj_list + + def list_hbas(self): + return self.get_resources_info(consts.GET_PORT_LIST_HBA_API, + self.cli_hba_to_list) + + def cli_hba_to_list(self, resource_info): + obj_list = [] + obj_model = {} + sp_name = '' + port_ids = set() + try: + obj_infos = resource_info.split('\n') + for obj_info in obj_infos: + str_line = obj_info.strip() + if str_line: + if 'Information about each HBA:' in obj_info: + if obj_model: + obj_model['port_ids'] = port_ids + obj_list = self.add_model_to_list(obj_model, + obj_list) + obj_model = {} + port_ids = set() + sp_name = '' + if ':' in obj_info: + str_info = self.split_str_by_colon(str_line) + obj_model = self.str_info_to_model(str_info, obj_model) + if 'SP Name:' in obj_info: + sp_name = obj_info.replace('SP Name:', '').replace( + 'SP', '').replace('\r', '').replace(' ', '') + if 'SP Port ID:' in obj_info: + port_id = obj_info.replace('SP Port ID:', + '').replace('\r', + '').replace( + ' ', '') + port_id = '%s-%s' % (sp_name, port_id) + port_ids.add(port_id) + + if obj_model: + obj_model['port_ids'] = port_ids + obj_list.append(obj_model) + except Exception as e: + err_msg = "arrange host info error: %s", six.text_type(e) + LOG.error(err_msg) + raise exception.InvalidResults(err_msg) + return obj_list diff --git a/delfin/drivers/dell_emc/vnx/vnx_block/vnx_block.py b/delfin/drivers/dell_emc/vnx/vnx_block/vnx_block.py index bd836b262..467094208 100644 --- a/delfin/drivers/dell_emc/vnx/vnx_block/vnx_block.py +++ b/delfin/drivers/dell_emc/vnx/vnx_block/vnx_block.py @@ -77,3 +77,12 @@ def clear_alert(self, context, sequence_number): @staticmethod def get_access_url(): return 'https://{ip}' + + def list_storage_host_initiators(self, context): + return self.com_handler.list_storage_host_initiators(self.storage_id) + + def list_storage_hosts(self, context): + return self.com_handler.list_storage_hosts(self.storage_id) + + def list_masking_views(self, context): + return self.com_handler.list_masking_views(self.storage_id) diff --git a/delfin/tests/unit/drivers/dell_emc/vnx/vnx_block/test_vnx_block.py b/delfin/tests/unit/drivers/dell_emc/vnx/vnx_block/test_vnx_block.py index 00309f476..0f137da28 100644 --- a/delfin/tests/unit/drivers/dell_emc/vnx/vnx_block/test_vnx_block.py +++ b/delfin/tests/unit/drivers/dell_emc/vnx/vnx_block/test_vnx_block.py @@ -354,6 +354,46 @@ I/O Module Type : SAS """ +VIEW_DATAS = """ +Storage Group Name: AIX_PowerHA_node2 +Storage Group UID: 0B:33:4A:6E:81:38:EC:11:90:2B:00:60:16:63 +HBA/SP Pairs: + + HBA UID SP Name SPPort + ------- ------- ------ + 20:00:00:00:C9:76:5E:79:10:00:00:00:C9:76:5E:79 SP A 6 +Host name: AIX_21 + 20:00:00:00:C9:75:80:4C:10:00:00:00:C9:75:80:4C SP B 3 +Host name: AIX_21 + +HLU/ALU Pairs: + + HLU Number ALU Number + ---------- ---------- + 1 335 +Shareable: YES +""" +HBA_DATAS = """ +Information about each HBA: + +HBA UID: 20:00:00:00:C9:9B:57:79:10:00:00:00:C9:9B:57:79 +Server Name: aix_ma +Server IP Address: 8.44.129.26 +HBA Model Description: +HBA Vendor Description: +HBA Device Driver Name: N/A +Information about each port of this HBA: + + SP Name: SP A + SP Port ID: 6 + HBA Devicename: N/A + Trusted: NO + Logged In: NO + Defined: YES + Initiator Type: 3 + StorageGroup Name: None +""" + AGENT_RESULT = { 'agent_rev': '7.33.1 (0.38)', 'name': 'K10', @@ -520,6 +560,35 @@ 'ipv6': None, 'ipv6_mask': None }] +VIEW_RESULT = [ + { + 'native_masking_view_id': '0B:33:4A:6E:81:38:EC:11:90:2B:00:' + '60:16:63_AIX_21_335', + 'name': 'AIX_PowerHA_node2', + 'storage_id': '12345', + 'native_storage_host_id': 'AIX_21', + 'native_volume_id': '335' + }] +INITIATOR_RESULT = [ + { + 'name': '20:00:00:00:C9:9B:57:79:10:00:00:00:C9:9B:57:79', + 'storage_id': '12345', + 'native_storage_host_initiator_id': '20:00:00:00:C9:9B:57:79:10:' + '00:00:00:C9:9B:57:79', + 'wwn': '20:00:00:00:C9:9B:57:79:10:00:00:00:C9:9B:57:79', + 'type': 'fc', + 'status': 'online', + 'native_storage_host_id': 'aix_ma' + }] +HOST_RESULT = [ + { + 'name': 'aix_ma', + 'storage_id': '12345', + 'native_storage_host_id': 'aix_ma', + 'os_type': 'Unknown', + 'status': 'normal', + 'ip_address': '8.44.129.26' + }] def create_driver(): @@ -696,3 +765,22 @@ def test_get_ports(self): BUS_PORT_DATAS, BUS_PORT_STATE_DATAS]) ports = self.driver.list_ports(context) self.assertDictEqual(ports[0], PORT_RESULT[0]) + + def test_get_masking_views(self): + NaviClient.exec = mock.Mock(side_effect=[VIEW_DATAS]) + views = self.driver.list_masking_views(context) + self.assertDictEqual(views[0], VIEW_RESULT[0]) + + def test_get_initiators(self): + NaviClient.exec = mock.Mock(side_effect=[HBA_DATAS, + IO_PORT_CONFIG_DATAS, + ISCSI_PORT_DATAS, PORT_DATAS, + BUS_PORT_DATAS, + BUS_PORT_STATE_DATAS]) + initiators = self.driver.list_storage_host_initiators(context) + self.assertDictEqual(initiators[0], INITIATOR_RESULT[0]) + + def test_get_hosts(self): + NaviClient.exec = mock.Mock(side_effect=[HBA_DATAS]) + hosts = self.driver.list_storage_hosts(context) + self.assertDictEqual(hosts[0], HOST_RESULT[0])