# (c) Copyright 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Generic linux Fibre Channel utilities.""" import errno import os from oslo_concurrency import processutils as putils from oslo_log import log as logging import six from os_brick.initiator import linuxscsi LOG = logging.getLogger(__name__) class LinuxFibreChannel(linuxscsi.LinuxSCSI): def has_fc_support(self): FC_HOST_SYSFS_PATH = '/sys/class/fc_host' if os.path.isdir(FC_HOST_SYSFS_PATH): return True else: return False def _get_hba_channel_scsi_target(self, hba, conn_props): """Try to get the HBA channel and SCSI target for an HBA. This method only works for Fibre Channel targets that implement a single WWNN for all ports, so caller should expect us to return either None or an empty list. :returns: List or None """ # We want the target's WWPNs, so we use the initiator_target_map if # present for this hba or default to target_wwns if not present. wwpns = conn_props['target_wwn'] if 'initiator_target_map' in conn_props: wwpns = conn_props['initiator_target_map'].get(hba['port_name'], wwpns) # If it's not a string then it's an iterable (most likely a list), # so we need to create a BRE for the grep query. if not isinstance(wwpns, six.string_types): wwpns = '\|'.join(wwpns) # Leave only the number from the host_device field (ie: host6) host_device = hba['host_device'] if host_device and len(host_device) > 4: host_device = host_device[4:] path = '/sys/class/fc_transport/target%s:' % host_device # Since we'll run the command in a shell ensure BRE are being used cmd = 'grep -Gil "%(wwpns)s" %(path)s*/port_name' % {'wwpns': wwpns, 'path': path} try: # We need to run command in shell to expand the * glob out, _err = self._execute(cmd, shell=True) return [line.split('/')[4].split(':')[1:] for line in out.split('\n') if line.startswith(path)] except Exception as exc: LOG.debug('Could not get HBA channel and SCSI target ID, path: ' '%(path)s*, reason: %(reason)s', {'path': path, 'reason': exc}) return None def rescan_hosts(self, hbas, connection_properties): target_lun = connection_properties['target_lun'] for hba in hbas: # Try to get HBA channel and SCSI target to use as filters cts = self._get_hba_channel_scsi_target(hba, connection_properties) # If we couldn't get the channel and target use wildcards if not cts: cts = [('-', '-')] for hba_channel, target_id in cts: LOG.debug('Scanning host %(host)s (wwnn: %(wwnn)s, c: ' '%(channel)s, t: %(target)s, l: %(lun)s)', {'host': hba['host_device'], 'wwnn': hba['node_name'], 'channel': hba_channel, 'target': target_id, 'lun': target_lun}) self.echo_scsi_command( "/sys/class/scsi_host/%s/scan" % hba['host_device'], "%(c)s %(t)s %(l)s" % {'c': hba_channel, 't': target_id, 'l': target_lun}) def get_fc_hbas(self): """Get the Fibre Channel HBA information.""" if not self.has_fc_support(): # there is no FC support in the kernel loaded # so there is no need to even try to run systool LOG.debug("No Fibre Channel support detected on system.") return [] out = None try: out, _err = self._execute('systool', '-c', 'fc_host', '-v', run_as_root=True, root_helper=self._root_helper) except putils.ProcessExecutionError as exc: # This handles the case where rootwrap is used # and systool is not installed # 96 = nova.cmd.rootwrap.RC_NOEXECFOUND: if exc.exit_code == 96: LOG.warning("systool is not installed") return [] except OSError as exc: # This handles the case where rootwrap is NOT used # and systool is not installed if exc.errno == errno.ENOENT: LOG.warning("systool is not installed") return [] # No FC HBAs were found if out is None: return [] lines = out.split('\n') # ignore the first 2 lines lines = lines[2:] hbas = [] hba = {} lastline = None for line in lines: line = line.strip() # 2 newlines denotes a new hba port if line == '' and lastline == '': if len(hba) > 0: hbas.append(hba) hba = {} else: val = line.split('=') if len(val) == 2: key = val[0].strip().replace(" ", "") value = val[1].strip() hba[key] = value.replace('"', '') lastline = line return hbas def get_fc_hbas_info(self): """Get Fibre Channel WWNs and device paths from the system, if any.""" # Note(walter-boring) modern Linux kernels contain the FC HBA's in /sys # and are obtainable via the systool app hbas = self.get_fc_hbas() hbas_info = [] for hba in hbas: wwpn = hba['port_name'].replace('0x', '') wwnn = hba['node_name'].replace('0x', '') device_path = hba['ClassDevicepath'] device = hba['ClassDevice'] hbas_info.append({'port_name': wwpn, 'node_name': wwnn, 'host_device': device, 'device_path': device_path}) return hbas_info def get_fc_wwpns(self): """Get Fibre Channel WWPNs from the system, if any.""" # Note(walter-boring) modern Linux kernels contain the FC HBA's in /sys # and are obtainable via the systool app hbas = self.get_fc_hbas() wwpns = [] for hba in hbas: if hba['port_state'] == 'Online': wwpn = hba['port_name'].replace('0x', '') wwpns.append(wwpn) return wwpns def get_fc_wwnns(self): """Get Fibre Channel WWNNs from the system, if any.""" # Note(walter-boring) modern Linux kernels contain the FC HBA's in /sys # and are obtainable via the systool app hbas = self.get_fc_hbas() wwnns = [] for hba in hbas: if hba['port_state'] == 'Online': wwnn = hba['node_name'].replace('0x', '') wwnns.append(wwnn) return wwnns class LinuxFibreChannelS390X(LinuxFibreChannel): def get_fc_hbas_info(self): """Get Fibre Channel WWNs and device paths from the system, if any.""" hbas = self.get_fc_hbas() hbas_info = [] for hba in hbas: if hba['port_state'] == 'Online': wwpn = hba['port_name'].replace('0x', '') wwnn = hba['node_name'].replace('0x', '') device_path = hba['ClassDevicepath'] device = hba['ClassDevice'] hbas_info.append({'port_name': wwpn, 'node_name': wwnn, 'host_device': device, 'device_path': device_path}) return hbas_info def configure_scsi_device(self, device_number, target_wwn, lun): """Write the LUN to the port's unit_add attribute. If auto-discovery of Fibre-Channel target ports is disabled on s390 platforms, ports need to be added to the configuration. If auto-discovery of LUNs is disabled on s390 platforms luns need to be added to the configuration through the unit_add interface """ LOG.debug("Configure lun for s390: device_number=%(device_num)s " "target_wwn=%(target_wwn)s target_lun=%(target_lun)s", {'device_num': device_number, 'target_wwn': target_wwn, 'target_lun': lun}) filepath = ("/sys/bus/ccw/drivers/zfcp/%s/%s" % (device_number, target_wwn)) if not (os.path.exists(filepath)): zfcp_device_command = ("/sys/bus/ccw/drivers/zfcp/%s/port_rescan" % (device_number)) LOG.debug("port_rescan call for s390: %s", zfcp_device_command) try: self.echo_scsi_command(zfcp_device_command, "1") except putils.ProcessExecutionError as exc: LOG.warning("port_rescan call for s390 failed exit" " %(code)s, stderr %(stderr)s", {'code': exc.exit_code, 'stderr': exc.stderr}) zfcp_device_command = ("/sys/bus/ccw/drivers/zfcp/%s/%s/unit_add" % (device_number, target_wwn)) LOG.debug("unit_add call for s390 execute: %s", zfcp_device_command) try: self.echo_scsi_command(zfcp_device_command, lun) except putils.ProcessExecutionError as exc: LOG.warning("unit_add call for s390 failed exit %(code)s, " "stderr %(stderr)s", {'code': exc.exit_code, 'stderr': exc.stderr}) def deconfigure_scsi_device(self, device_number, target_wwn, lun): """Write the LUN to the port's unit_remove attribute. If auto-discovery of LUNs is disabled on s390 platforms luns need to be removed from the configuration through the unit_remove interface """ LOG.debug("Deconfigure lun for s390: " "device_number=%(device_num)s " "target_wwn=%(target_wwn)s target_lun=%(target_lun)s", {'device_num': device_number, 'target_wwn': target_wwn, 'target_lun': lun}) zfcp_device_command = ("/sys/bus/ccw/drivers/zfcp/%s/%s/unit_remove" % (device_number, target_wwn)) LOG.debug("unit_remove call for s390 execute: %s", zfcp_device_command) try: self.echo_scsi_command(zfcp_device_command, lun) except putils.ProcessExecutionError as exc: LOG.warning("unit_remove call for s390 failed exit %(code)s, " "stderr %(stderr)s", {'code': exc.exit_code, 'stderr': exc.stderr})