1# (c) Copyright 2013 Hewlett-Packard Development Company, L.P. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); you may 4# not use this file except in compliance with the License. You may obtain 5# a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12# License for the specific language governing permissions and limitations 13# under the License. 14 15"""Generic linux Fibre Channel utilities.""" 16 17import errno 18import os 19 20from oslo_concurrency import processutils as putils 21from oslo_log import log as logging 22 23from os_brick.initiator import linuxscsi 24 25LOG = logging.getLogger(__name__) 26 27 28class LinuxFibreChannel(linuxscsi.LinuxSCSI): 29 30 def has_fc_support(self): 31 FC_HOST_SYSFS_PATH = '/sys/class/fc_host' 32 if os.path.isdir(FC_HOST_SYSFS_PATH): 33 return True 34 else: 35 return False 36 37 def _get_hba_channel_scsi_target_lun(self, hba, conn_props): 38 """Get HBA channels, SCSI targets, LUNs to FC targets for given HBA. 39 40 Given an HBA and the connection properties we look for the HBA channels 41 and SCSI targets for each of the FC targets that this HBA has been 42 granted permission to connect. 43 44 For drivers that don't return an initiator to target map we try to find 45 the info for all the target ports. 46 47 For drivers that return an initiator_target_map we use the 48 initiator_target_lun_map entry that was generated by the FC connector 49 based on the contents of the connection information data to know which 50 target ports to look for. 51 52 :returns: 2-Tuple with the first entry being a list of [c, t, l] 53 entries where the target port was found, and the second entry of the 54 tuple being a set of luns for ports that were not found. 55 """ 56 # We want the targets' WWPNs, so we use the initiator_target_map if 57 # present for this hba or default to targets if not present. 58 targets = conn_props['targets'] 59 if conn_props.get('initiator_target_map') is not None: 60 # This map we try to use was generated by the FC connector 61 targets = conn_props['initiator_target_lun_map'].get( 62 hba['port_name'], targets) 63 64 # Leave only the number from the host_device field (ie: host6) 65 host_device = hba['host_device'] 66 if host_device and len(host_device) > 4: 67 host_device = host_device[4:] 68 69 path = '/sys/class/fc_transport/target%s:' % host_device 70 ctls = [] 71 luns_not_found = set() 72 for wwpn, lun in targets: 73 cmd = 'grep -Gil "%(wwpns)s" %(path)s*/port_name' % {'wwpns': wwpn, 74 'path': path} 75 try: 76 # We need to run command in shell to expand the * glob 77 out, _err = self._execute(cmd, shell=True) 78 ctls += [line.split('/')[4].split(':')[1:] + [lun] 79 for line in out.split('\n') if line.startswith(path)] 80 except Exception as exc: 81 LOG.debug('Could not get HBA channel and SCSI target ID, path:' 82 ' %(path)s*, reason: %(reason)s', {'path': path, 83 'reason': exc}) 84 # If we didn't find any paths add it to the not found list 85 luns_not_found.add(lun) 86 return ctls, luns_not_found 87 88 def rescan_hosts(self, hbas, connection_properties): 89 LOG.debug('Rescaning HBAs %(hbas)s with connection properties ' 90 '%(conn_props)s', {'hbas': hbas, 91 'conn_props': connection_properties}) 92 # Use initiator_target_lun_map (generated from initiator_target_map by 93 # the FC connector) as HBA exclusion map 94 ports = connection_properties.get('initiator_target_lun_map') 95 if ports: 96 hbas = [hba for hba in hbas if hba['port_name'] in ports] 97 LOG.debug('Using initiator target map to exclude HBAs: %s', 98 hbas) 99 100 # Most storage arrays get their target ports automatically detected 101 # by the Linux FC initiator and sysfs gets populated with that 102 # information, but there are some that don't. We'll do a narrow scan 103 # using the channel, target, and LUN for the former and a wider scan 104 # for the latter. If all paths to a former type of array were down on 105 # the system boot the array could look like it's of the latter type 106 # and make us bring us unwanted volumes into the system by doing a 107 # broad scan. To prevent this from happening Cinder drivers can use 108 # the "enable_wildcard_scan" key in the connection_info to let us know 109 # they don't want us to do broad scans even in those cases. 110 broad_scan = connection_properties.get('enable_wildcard_scan', True) 111 if not broad_scan: 112 LOG.debug('Connection info disallows broad SCSI scanning') 113 114 process = [] 115 skipped = [] 116 get_ctls = self._get_hba_channel_scsi_target_lun 117 for hba in hbas: 118 ctls, luns_wildcards = get_ctls(hba, connection_properties) 119 # If we found the target ports, ignore HBAs that din't find them 120 if ctls: 121 process.append((hba, ctls)) 122 123 # If target ports not found and should have, then the HBA is not 124 # connected to our storage 125 elif not broad_scan: 126 LOG.debug('Skipping HBA %s, nothing to scan, target port ' 127 'not connected to initiator', hba['node_name']) 128 129 # If we haven't found any target ports we may need to do broad 130 # SCSI scans 131 elif not process: 132 skipped.append((hba, 133 [('-', '-', lun) for lun in luns_wildcards])) 134 135 # If we didn't find any target ports use wildcards if they are enabled 136 process = process or skipped 137 138 for hba, ctls in process: 139 for hba_channel, target_id, target_lun in ctls: 140 LOG.debug('Scanning %(host)s (wwnn: %(wwnn)s, c: ' 141 '%(channel)s, t: %(target)s, l: %(lun)s)', 142 {'host': hba['host_device'], 143 'wwnn': hba['node_name'], 'channel': hba_channel, 144 'target': target_id, 'lun': target_lun}) 145 self.echo_scsi_command( 146 "/sys/class/scsi_host/%s/scan" % hba['host_device'], 147 "%(c)s %(t)s %(l)s" % {'c': hba_channel, 148 't': target_id, 149 'l': target_lun}) 150 151 def get_fc_hbas(self): 152 """Get the Fibre Channel HBA information.""" 153 154 if not self.has_fc_support(): 155 # there is no FC support in the kernel loaded 156 # so there is no need to even try to run systool 157 LOG.debug("No Fibre Channel support detected on system.") 158 return [] 159 160 out = None 161 try: 162 out, _err = self._execute('systool', '-c', 'fc_host', '-v', 163 run_as_root=True, 164 root_helper=self._root_helper) 165 except putils.ProcessExecutionError as exc: 166 # This handles the case where rootwrap is used 167 # and systool is not installed 168 # 96 = nova.cmd.rootwrap.RC_NOEXECFOUND: 169 if exc.exit_code == 96: 170 LOG.warning("systool is not installed") 171 return [] 172 except OSError as exc: 173 # This handles the case where rootwrap is NOT used 174 # and systool is not installed 175 if exc.errno == errno.ENOENT: 176 LOG.warning("systool is not installed") 177 return [] 178 179 # No FC HBAs were found 180 if out is None: 181 return [] 182 183 lines = out.split('\n') 184 # ignore the first 2 lines 185 lines = lines[2:] 186 hbas = [] 187 hba = {} 188 lastline = None 189 for line in lines: 190 line = line.strip() 191 # 2 newlines denotes a new hba port 192 if line == '' and lastline == '': 193 if len(hba) > 0: 194 hbas.append(hba) 195 hba = {} 196 else: 197 val = line.split('=') 198 if len(val) == 2: 199 key = val[0].strip().replace(" ", "") 200 value = val[1].strip() 201 hba[key] = value.replace('"', '') 202 lastline = line 203 204 return hbas 205 206 def get_fc_hbas_info(self): 207 """Get Fibre Channel WWNs and device paths from the system, if any.""" 208 209 # Note(walter-boring) modern Linux kernels contain the FC HBA's in /sys 210 # and are obtainable via the systool app 211 hbas = self.get_fc_hbas() 212 213 hbas_info = [] 214 for hba in hbas: 215 wwpn = hba['port_name'].replace('0x', '') 216 wwnn = hba['node_name'].replace('0x', '') 217 device_path = hba['ClassDevicepath'] 218 device = hba['ClassDevice'] 219 hbas_info.append({'port_name': wwpn, 220 'node_name': wwnn, 221 'host_device': device, 222 'device_path': device_path}) 223 return hbas_info 224 225 def get_fc_wwpns(self): 226 """Get Fibre Channel WWPNs from the system, if any.""" 227 228 # Note(walter-boring) modern Linux kernels contain the FC HBA's in /sys 229 # and are obtainable via the systool app 230 hbas = self.get_fc_hbas() 231 232 wwpns = [] 233 for hba in hbas: 234 if hba['port_state'] == 'Online': 235 wwpn = hba['port_name'].replace('0x', '') 236 wwpns.append(wwpn) 237 238 return wwpns 239 240 def get_fc_wwnns(self): 241 """Get Fibre Channel WWNNs from the system, if any.""" 242 243 # Note(walter-boring) modern Linux kernels contain the FC HBA's in /sys 244 # and are obtainable via the systool app 245 hbas = self.get_fc_hbas() 246 247 wwnns = [] 248 for hba in hbas: 249 if hba['port_state'] == 'Online': 250 wwnn = hba['node_name'].replace('0x', '') 251 wwnns.append(wwnn) 252 253 return wwnns 254 255 256class LinuxFibreChannelS390X(LinuxFibreChannel): 257 def get_fc_hbas_info(self): 258 """Get Fibre Channel WWNs and device paths from the system, if any.""" 259 260 hbas = self.get_fc_hbas() 261 262 hbas_info = [] 263 for hba in hbas: 264 if hba['port_state'] == 'Online': 265 wwpn = hba['port_name'].replace('0x', '') 266 wwnn = hba['node_name'].replace('0x', '') 267 device_path = hba['ClassDevicepath'] 268 device = hba['ClassDevice'] 269 hbas_info.append({'port_name': wwpn, 270 'node_name': wwnn, 271 'host_device': device, 272 'device_path': device_path}) 273 return hbas_info 274 275 def configure_scsi_device(self, device_number, target_wwn, lun): 276 """Write the LUN to the port's unit_add attribute. 277 278 If auto-discovery of Fibre-Channel target ports is 279 disabled on s390 platforms, ports need to be added to 280 the configuration. 281 If auto-discovery of LUNs is disabled on s390 platforms 282 luns need to be added to the configuration through the 283 unit_add interface 284 """ 285 LOG.debug("Configure lun for s390: device_number=%(device_num)s " 286 "target_wwn=%(target_wwn)s target_lun=%(target_lun)s", 287 {'device_num': device_number, 288 'target_wwn': target_wwn, 289 'target_lun': lun}) 290 filepath = ("/sys/bus/ccw/drivers/zfcp/%s/%s" % 291 (device_number, target_wwn)) 292 if not (os.path.exists(filepath)): 293 zfcp_device_command = ("/sys/bus/ccw/drivers/zfcp/%s/port_rescan" % 294 (device_number)) 295 LOG.debug("port_rescan call for s390: %s", zfcp_device_command) 296 try: 297 self.echo_scsi_command(zfcp_device_command, "1") 298 except putils.ProcessExecutionError as exc: 299 LOG.warning("port_rescan call for s390 failed exit" 300 " %(code)s, stderr %(stderr)s", 301 {'code': exc.exit_code, 'stderr': exc.stderr}) 302 303 zfcp_device_command = ("/sys/bus/ccw/drivers/zfcp/%s/%s/unit_add" % 304 (device_number, target_wwn)) 305 LOG.debug("unit_add call for s390 execute: %s", zfcp_device_command) 306 try: 307 self.echo_scsi_command(zfcp_device_command, lun) 308 except putils.ProcessExecutionError as exc: 309 LOG.warning("unit_add call for s390 failed exit %(code)s, " 310 "stderr %(stderr)s", 311 {'code': exc.exit_code, 'stderr': exc.stderr}) 312 313 def deconfigure_scsi_device(self, device_number, target_wwn, lun): 314 """Write the LUN to the port's unit_remove attribute. 315 316 If auto-discovery of LUNs is disabled on s390 platforms 317 luns need to be removed from the configuration through the 318 unit_remove interface 319 """ 320 LOG.debug("Deconfigure lun for s390: " 321 "device_number=%(device_num)s " 322 "target_wwn=%(target_wwn)s target_lun=%(target_lun)s", 323 {'device_num': device_number, 324 'target_wwn': target_wwn, 325 'target_lun': lun}) 326 zfcp_device_command = ("/sys/bus/ccw/drivers/zfcp/%s/%s/unit_remove" % 327 (device_number, target_wwn)) 328 LOG.debug("unit_remove call for s390 execute: %s", zfcp_device_command) 329 try: 330 self.echo_scsi_command(zfcp_device_command, lun) 331 except putils.ProcessExecutionError as exc: 332 LOG.warning("unit_remove call for s390 failed exit %(code)s, " 333 "stderr %(stderr)s", 334 {'code': exc.exit_code, 'stderr': exc.stderr}) 335