def _get_ips_iqns_luns(self, connection_properties, discover=True): """Build a list of ips, iqns, and luns. Used only when doing multipath, we have 3 cases: - All information is in the connection properties - We have to do an iSCSI discovery to get the information - We don't want to do another discovery and we query the discoverydb :param connection_properties: The dictionary that describes all of the target volume attributes. :type connection_properties: dict :param discover: Wheter doing an iSCSI discovery is acceptable. :type discover: bool :returns: list of tuples of (ip, iqn, lun) """ try: if ('target_portals' in connection_properties and 'target_iqns' in connection_properties): # Use targets specified by connection_properties ips_iqns_luns = list( zip(connection_properties['target_portals'], connection_properties['target_iqns'], self._get_luns(connection_properties))) else: method = (self._discover_iscsi_portals if discover else self._get_discoverydb_portals) ips_iqns_luns = method(connection_properties) except exception.TargetPortalsNotFound: raise except Exception: if 'target_portals' in connection_properties: raise exception.TargetPortalsNotFound( target_portals=connection_properties['target_portals']) if 'target_portal' in connection_properties: raise exception.TargetPortalNotFound( target_portal=connection_properties['target_portal']) raise if not connection_properties.get('target_iqns'): # There are two types of iSCSI multipath devices. One which # shares the same iqn between multiple portals, and the other # which use different iqns on different portals. # Try to identify the type by checking the iscsiadm output # if the iqn is used by multiple portals. If it is, it's # the former, so use the supplied iqn. Otherwise, it's the # latter, so try the ip,iqn combinations to find the targets # which constitutes the multipath device. main_iqn = connection_properties['target_iqn'] all_portals = {(ip, lun) for ip, iqn, lun in ips_iqns_luns} match_portals = {(ip, lun) for ip, iqn, lun in ips_iqns_luns if iqn == main_iqn} if len(all_portals) == len(match_portals): ips_iqns_luns = [(p[0], main_iqn, p[1]) for p in all_portals] return ips_iqns_luns
def _get_potential_volume_paths(self, connection_properties, connect_to_portal=True, use_rescan=True): """Build a list of potential volume paths that exist. Given a list of target_portals in the connection_properties, a list of paths might exist on the system during discovery. This method's job is to build that list of potential paths for a volume that might show up. This is used during connect_volume time, in which case we want to connect to the iSCSI target portal. During get_volume_paths time, we are looking to find a list of existing volume paths for the connection_properties. In this case, we don't want to connect to the portal. If we blindly try and connect to a portal, it could create a new iSCSI session that didn't exist previously, and then leave it stale. :param connection_properties: The dictionary that describes all of the target volume attributes. :type connection_properties: dict :param connect_to_portal: Do we want to try a new connection to the target portal(s)? Set this to False if you want to search for existing volumes, not discover new volumes. :param connect_to_portal: bool :param use_rescan: Issue iSCSI rescan during discovery? :type use_rescan: bool :returns: dict """ target_props = None connected_to_portal = False if self.use_multipath: LOG.info("Multipath discovery for iSCSI enabled") # Multipath installed, discovering other targets if available try: ips_iqns_luns = self._discover_iscsi_portals( connection_properties) except Exception: if 'target_portals' in connection_properties: raise exception.TargetPortalsNotFound( target_portals=connection_properties['target_portals']) elif 'target_portal' in connection_properties: raise exception.TargetPortalNotFound( target_portal=connection_properties['target_portal']) else: raise if not connection_properties.get('target_iqns'): # There are two types of iSCSI multipath devices. One which # shares the same iqn between multiple portals, and the other # which use different iqns on different portals. # Try to identify the type by checking the iscsiadm output # if the iqn is used by multiple portals. If it is, it's # the former, so use the supplied iqn. Otherwise, it's the # latter, so try the ip,iqn combinations to find the targets # which constitutes the multipath device. main_iqn = connection_properties['target_iqn'] all_portals = {(ip, lun) for ip, iqn, lun in ips_iqns_luns} match_portals = {(ip, lun) for ip, iqn, lun in ips_iqns_luns if iqn == main_iqn} if len(all_portals) == len(match_portals): ips_iqns_luns = [(p[0], main_iqn, p[1]) for p in all_portals] for ip, iqn, lun in ips_iqns_luns: props = copy.deepcopy(connection_properties) props['target_portal'] = ip props['target_iqn'] = iqn if connect_to_portal: if self._connect_to_iscsi_portal(props): connected_to_portal = True if use_rescan: self._rescan_iscsi(ips_iqns_luns) host_devices = self._get_device_path(connection_properties) else: LOG.info("Multipath discovery for iSCSI not enabled.") iscsi_sessions = [] if not connect_to_portal: iscsi_sessions = self._get_iscsi_sessions() host_devices = [] target_props = connection_properties for props in self._iterate_all_targets(connection_properties): if connect_to_portal: if self._connect_to_iscsi_portal(props): target_props = props connected_to_portal = True host_devices = self._get_device_path(props) break else: LOG.warning( 'Failed to connect to iSCSI portal %(portal)s.', {'portal': props['target_portal']}) else: # If we aren't trying to connect to the portal, we # want to find ALL possible paths from all of the # alternate portals if props['target_portal'] in iscsi_sessions: paths = self._get_device_path(props) host_devices = list(set(paths + host_devices)) if connect_to_portal and not connected_to_portal: msg = _("Could not login to any iSCSI portal.") LOG.error(msg) raise exception.FailedISCSITargetPortalLogin(message=msg) return host_devices, target_props
def _get_discoverydb_portals(self, connection_properties): """Retrieve iscsi portals information from the discoverydb. Example of discoverydb command output: SENDTARGETS: DiscoveryAddress: 192.168.1.33,3260 DiscoveryAddress: 192.168.1.2,3260 Target: iqn.2004-04.com.qnap:ts-831x:iscsi.cinder-20170531114245.9eff88 Portal: 192.168.1.3:3260,1 Iface Name: default Portal: 192.168.1.2:3260,1 Iface Name: default Target: iqn.2004-04.com.qnap:ts-831x:iscsi.cinder-20170531114447.9eff88 Portal: 192.168.1.3:3260,1 Iface Name: default Portal: 192.168.1.2:3260,1 Iface Name: default DiscoveryAddress: 192.168.1.38,3260 iSNS: No targets found. STATIC: No targets found. FIRMWARE: No targets found. :param connection_properties: The dictionary that describes all of the target volume attributes. :type connection_properties: dict :returns: list of tuples of (ip, iqn, lun) """ ip, port = connection_properties['target_portal'].rsplit(':', 1) # NOTE(geguileo): I don't know if IPv6 will be reported with [] # or not, so we'll make them optional. ip = ip.replace('[', '\[?').replace(']', '\]?') out = self._run_iscsiadm_bare(['-m', 'discoverydb', '-o', 'show', '-P', 1])[0] or "" regex = ''.join(('^SENDTARGETS:\n.*?^DiscoveryAddress: ', ip, ',', port, '.*?\n(.*?)^(?:DiscoveryAddress|iSNS):.*')) LOG.debug('Regex to get portals from discoverydb: %s', regex) info = re.search(regex, out, re.DOTALL | re.MULTILINE) ips = [] iqns = [] if info: iscsi_transport = ('iser' if self._get_transport() == 'iser' else 'default') iface = 'Iface Name: ' + iscsi_transport current_iqn = '' current_ip = '' for line in info.group(1).splitlines(): line = line.strip() if line.startswith('Target:'): current_iqn = line[8:] elif line.startswith('Portal:'): current_ip = line[8:].split(',')[0] elif line.startswith(iface): if current_iqn and current_ip: iqns.append(current_iqn) ips.append(current_ip) current_ip = '' if not iqns: raise exception.TargetPortalsNotFound( _('Unable to find target portals information on discoverydb.')) luns = self._get_luns(connection_properties, iqns) return list(zip(ips, iqns, luns))