Пример #1
0
def get_volume_encryptor(root_helper,
                         connection_info,
                         keymgr,
                         execute=None,
                         *args, **kwargs):
    """Creates a VolumeEncryptor used to encrypt the specified volume.

    :param: the connection information used to attach the volume
    :returns VolumeEncryptor: the VolumeEncryptor for the volume
    """
    encryptor = nop.NoOpEncryptor(root_helper=root_helper,
                                  connection_info=connection_info,
                                  keymgr=keymgr,
                                  execute=execute,
                                  *args, **kwargs)

    location = kwargs.get('control_location', None)
    if location and location.lower() == 'front-end':  # case insensitive
        provider = kwargs.get('provider')

        # TODO(lyarwood): Remove the following in Pike and raise an
        # ERROR if provider is not a key in SUPPORTED_ENCRYPTION_PROVIDERS.
        # Until then continue to allow both the class name and path to be used.
        if provider in LEGACY_PROVIDER_CLASS_TO_FORMAT_MAP:
            LOG.warning(_LW("Use of the in tree encryptor class %(provider)s"
                            " by directly referencing the implementation class"
                            " will be blocked in the Pike release of"
                            " os-brick."), {'provider': provider})
            provider = LEGACY_PROVIDER_CLASS_TO_FORMAT_MAP[provider]

        if provider in FORMAT_TO_FRONTEND_ENCRYPTOR_MAP:
            provider = FORMAT_TO_FRONTEND_ENCRYPTOR_MAP[provider]
        elif provider is None:
            provider = "os_brick.encryptors.nop.NoOpEncryptor"
        else:
            LOG.warning(_LW("Use of the out of tree encryptor class "
                            "%(provider)s will be blocked with the Pike "
                            "release of os-brick."), {'provider': provider})

        try:
            encryptor = importutils.import_object(
                provider,
                root_helper,
                connection_info,
                keymgr,
                execute,
                **kwargs)
        except Exception as e:
            LOG.error(_LE("Error instantiating %(provider)s: %(exception)s"),
                      {'provider': provider, 'exception': e})
            raise

    msg = ("Using volume encryptor '%(encryptor)s' for connection: "
           "%(connection_info)s" %
           {'encryptor': encryptor, 'connection_info': connection_info})
    LOG.debug(strutils.mask_password(msg))

    return encryptor
Пример #2
0
    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(_LW("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(_LW("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
Пример #3
0
def is_luks(root_helper, device, execute=None):
    """Checks if the specified device uses LUKS for encryption.

    :param device: the device to check
    :returns: true if the specified device uses LUKS; false otherwise
    """
    try:
        # check to see if the device uses LUKS: exit status is 0
        # if the device is a LUKS partition and non-zero if not
        if execute is None:
            execute = priv_rootwrap.execute
        execute('cryptsetup',
                'isLuks',
                '--verbose',
                device,
                run_as_root=True,
                root_helper=root_helper,
                check_exit_code=True)
        return True
    except putils.ProcessExecutionError as e:
        LOG.warning(
            _LW("isLuks exited abnormally (status %(exit_code)s): "
                "%(stderr)s"), {
                    "exit_code": e.exit_code,
                    "stderr": e.stderr
                })
        return False
Пример #4
0
    def __init__(self, mount_type, root_helper, driver=None,
                 execute=None,
                 device_scan_attempts=initiator.DEVICE_SCAN_ATTEMPTS_DEFAULT,
                 *args, **kwargs):
        kwargs = kwargs or {}
        conn = kwargs.get('conn')
        mount_type_lower = mount_type.lower()
        if conn:
            mount_point_base = conn.get('mount_point_base')
            if mount_type_lower in ('nfs', 'glusterfs', 'scality',
                                    'quobyte', 'vzstorage'):
                kwargs[mount_type_lower + '_mount_point_base'] = (
                    kwargs.get(mount_type_lower + '_mount_point_base') or
                    mount_point_base)
        else:
            LOG.warning(_LW("Connection details not present."
                            " RemoteFsClient may not initialize properly."))

        if mount_type_lower == 'scality':
            cls = remotefs.ScalityRemoteFsClient
        else:
            cls = remotefs.RemoteFsClient
        self._remotefsclient = cls(mount_type, root_helper, execute=execute,
                                   *args, **kwargs)

        super(RemoteFsConnector, self).__init__(
            root_helper, driver=driver,
            execute=execute,
            device_scan_attempts=device_scan_attempts,
            *args, **kwargs)
 def flush_multipath_devices(self):
     try:
         self._execute('multipath', '-F', run_as_root=True,
                       root_helper=self._root_helper)
     except putils.ProcessExecutionError as exc:
         LOG.warning(_LW("multipath call failed exit %(code)s"),
                     {'code': exc.exit_code})
Пример #6
0
    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(
                _LW("unit_remove call for s390 failed exit %(code)s, "
                    "stderr %(stderr)s"), {
                        'code': exc.exit_code,
                        'stderr': exc.stderr
                    })
Пример #7
0
    def get_fc_hbas(self):
        """Get the Fibre Channel HBA information."""
        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(_LW("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(_LW("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
Пример #8
0
    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(
                    _LW("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(
                _LW("unit_add call for s390 failed exit %(code)s, "
                    "stderr %(stderr)s"), {
                        'code': exc.exit_code,
                        'stderr': exc.stderr
                    })
 def flush_device_io(self, device):
     """This is used to flush any remaining IO in the buffers."""
     try:
         LOG.debug("Flushing IO for device %s", device)
         self._execute('blockdev', '--flushbufs', device, run_as_root=True,
                       root_helper=self._root_helper)
     except putils.ProcessExecutionError as exc:
         LOG.warning(_LW("Failed to flush IO buffers prior to removing "
                         "device: %(code)s"), {'code': exc.exit_code})
Пример #10
0
    def extend_volume(self, connection_properties):
        """Update the local kernel's size information.

        Try and update the local kernel's size information
        for an FC volume.
        """
        volume_paths = self.get_volume_paths(connection_properties)
        if volume_paths:
            return self._linuxscsi.extend_volume(volume_paths[0])
        else:
            LOG.warning(_LW("Couldn't find any volume paths on the host to "
                            "extend volume for %(props)s"),
                        {'props': connection_properties})
            raise exception.VolumePathsNotFound()
Пример #11
0
    def get_initiator(self):
        """Secure helper to read file as root."""
        file_path = '/etc/iscsi/initiatorname.iscsi'
        try:
            lines, _err = self._execute('cat', file_path, run_as_root=True,
                                        root_helper=self._root_helper)

            for l in lines.split('\n'):
                if l.startswith('InitiatorName='):
                    return l[l.index('=') + 1:].strip()
        except putils.ProcessExecutionError:
            LOG.warning(_LW("Could not find the iSCSI Initiator File %s"),
                        file_path)
            return None
Пример #12
0
    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(_LW("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(_LW("unit_add call for s390 failed exit %(code)s, "
                            "stderr %(stderr)s"),
                        {'code': exc.exit_code, 'stderr': exc.stderr})
Пример #13
0
    def get_initiator(self):
        """Secure helper to read file as root."""
        file_path = '/etc/iscsi/initiatorname.iscsi'
        try:
            lines, _err = self._execute('cat',
                                        file_path,
                                        run_as_root=True,
                                        root_helper=self._root_helper)

            for l in lines.split('\n'):
                if l.startswith('InitiatorName='):
                    return l[l.index('=') + 1:].strip()
        except putils.ProcessExecutionError:
            LOG.warning(_LW("Could not find the iSCSI Initiator File %s"),
                        file_path)
            return None
Пример #14
0
    def find_multipath_device_path(self, wwn):
        """Look for the multipath device file for a volume WWN.

        Multipath devices can show up in several places on
        a linux system.

        1) When multipath friendly names are ON:
            a device file will show up in
            /dev/disk/by-id/dm-uuid-mpath-<WWN>
            /dev/disk/by-id/dm-name-mpath<N>
            /dev/disk/by-id/scsi-mpath<N>
            /dev/mapper/mpath<N>

        2) When multipath friendly names are OFF:
            /dev/disk/by-id/dm-uuid-mpath-<WWN>
            /dev/disk/by-id/scsi-<WWN>
            /dev/mapper/<WWN>

        """
        LOG.info(_LI("Find Multipath device file for volume WWN %(wwn)s"),
                 {'wwn': wwn})
        # First look for the common path
        wwn_dict = {'wwn': wwn}
        path = "/dev/disk/by-id/dm-uuid-mpath-%(wwn)s" % wwn_dict
        try:
            self.wait_for_path(path)
            return path
        except exception.VolumeDeviceNotFound:
            pass

        # for some reason the common path wasn't found
        # lets try the dev mapper path
        path = "/dev/mapper/%(wwn)s" % wwn_dict
        try:
            self.wait_for_path(path)
            return path
        except exception.VolumeDeviceNotFound:
            pass

        # couldn't find a path
        LOG.warning(
            _LW("couldn't find a valid multipath device path for "
                "%(wwn)s"), wwn_dict)
        return None
Пример #15
0
    def _get_multipath_device_map(self):
        out = self._run_multipath(['-ll'], check_exit_code=[0, 1])[0]
        mpath_line = [line for line in out.splitlines()
                      if not re.match(initiator.MULTIPATH_ERROR_REGEX, line)]
        mpath_dev = None
        mpath_map = {}
        for line in out.splitlines():
            m = initiator.MULTIPATH_DEV_CHECK_REGEX.split(line)
            if len(m) >= 2:
                mpath_dev = '/dev/mapper/' + m[0].split(" ")[0]
                continue
            m = initiator.MULTIPATH_PATH_CHECK_REGEX.split(line)
            if len(m) >= 2:
                mpath_map['/dev/' + m[1].split(" ")[0]] = mpath_dev

        if mpath_line and not mpath_map:
            LOG.warning(_LW("Failed to parse the output of multipath -ll. "
                            "stdout: %s"), out)
        return mpath_map
Пример #16
0
    def attach_volume(self, context, **kwargs):
        """Shadows the device and passes an unencrypted version to the
        instance.

        Transparent disk encryption is achieved by mounting the volume via
        dm-crypt and passing the resulting device to the instance. The
        instance is unaware of the underlying encryption due to modifying the
        original symbolic link to refer to the device mounted by dm-crypt.
        """

        key = self._get_key(context).get_encoded()
        passphrase = self._get_passphrase(key)

        try:
            self._open_volume(passphrase, **kwargs)
        except putils.ProcessExecutionError as e:
            if e.exit_code == 1 and not is_luks(self._root_helper,
                                                self.dev_path,
                                                execute=self._execute):
                # the device has never been formatted; format it and try again
                LOG.info(_LI("%s is not a valid LUKS device;"
                             " formatting device for first use"),
                         self.dev_path)
                self._format_volume(passphrase, **kwargs)
                self._open_volume(passphrase, **kwargs)
            elif e.exit_code == 2:
                # NOTE(lyarwood): Workaround bug#1633518 by replacing any
                # mangled passphrases that are found on the volume.
                # TODO(lyarwood): Remove workaround during R.
                LOG.warning(_LW("%s is not usable with the current "
                                "passphrase, attempting to use a mangled "
                                "passphrase to open the volume."),
                            self.dev_path)
                self._unmangle_volume(key, passphrase, **kwargs)
                self._open_volume(passphrase, **kwargs)
            else:
                raise

        # modify the original symbolic link to refer to the decrypted device
        self._execute('ln', '--symbolic', '--force',
                      '/dev/mapper/%s' % self.dev_name, self.symlink_path,
                      root_helper=self._root_helper,
                      run_as_root=True, check_exit_code=True)
    def find_multipath_device_path(self, wwn):
        """Look for the multipath device file for a volume WWN.

        Multipath devices can show up in several places on
        a linux system.

        1) When multipath friendly names are ON:
            a device file will show up in
            /dev/disk/by-id/dm-uuid-mpath-<WWN>
            /dev/disk/by-id/dm-name-mpath<N>
            /dev/disk/by-id/scsi-mpath<N>
            /dev/mapper/mpath<N>

        2) When multipath friendly names are OFF:
            /dev/disk/by-id/dm-uuid-mpath-<WWN>
            /dev/disk/by-id/scsi-<WWN>
            /dev/mapper/<WWN>

        """
        LOG.info(_LI("Find Multipath device file for volume WWN %(wwn)s"),
                 {'wwn': wwn})
        # First look for the common path
        wwn_dict = {'wwn': wwn}
        path = "/dev/disk/by-id/dm-uuid-mpath-%(wwn)s" % wwn_dict
        try:
            self.wait_for_path(path)
            return path
        except exception.VolumeDeviceNotFound:
            pass

        # for some reason the common path wasn't found
        # lets try the dev mapper path
        path = "/dev/mapper/%(wwn)s" % wwn_dict
        try:
            self.wait_for_path(path)
            return path
        except exception.VolumeDeviceNotFound:
            pass

        # couldn't find a path
        LOG.warning(_LW("couldn't find a valid multipath device path for "
                        "%(wwn)s"), wwn_dict)
        return None
Пример #18
0
def is_luks(root_helper, device, execute=None):
    """Checks if the specified device uses LUKS for encryption.

    :param device: the device to check
    :returns: true if the specified device uses LUKS; false otherwise
    """
    try:
        # check to see if the device uses LUKS: exit status is 0
        # if the device is a LUKS partition and non-zero if not
        if execute is None:
            execute = priv_rootwrap.execute
        execute('cryptsetup', 'isLuks', '--verbose', device,
                run_as_root=True, root_helper=root_helper,
                check_exit_code=True)
        return True
    except putils.ProcessExecutionError as e:
        LOG.warning(_LW("isLuks exited abnormally (status %(exit_code)s): "
                        "%(stderr)s"),
                    {"exit_code": e.exit_code, "stderr": e.stderr})
        return False
Пример #19
0
    def _is_crypt_device_available(self, dev_name):
        if not os.path.exists('/dev/mapper/%s' % dev_name):
            return False

        try:
            self._execute('cryptsetup', 'status', dev_name, run_as_root=True)
        except processutils.ProcessExecutionError as e:
            # If /dev/mapper/<dev_name> is a non-crypt block device (such as a
            # normal disk or multipath device), exit_code will be 1. In the
            # case, we will omit the warning message.
            if e.exit_code != 1:
                LOG.warning(
                    _LW('cryptsetup status %(dev_name)s exited '
                        'abnormally (status %(exit_code)s): %(err)s'), {
                            "dev_name": dev_name,
                            "exit_code": e.exit_code,
                            "err": e.stderr
                        })
            return False
        return True
Пример #20
0
    def _discover_mpath_device(self, device_wwn, connection_properties,
                               device_name):
        """This method discovers a multipath device.

        Discover a multipath device based on a defined connection_property
        and a device_wwn and return the multipath_id and path of the multipath
        enabled device if there is one.
        """

        path = self._linuxscsi.find_multipath_device_path(device_wwn)
        device_path = None
        multipath_id = None

        if path is None:
            # find_multipath_device only accept realpath not symbolic path
            device_realpath = os.path.realpath(device_name)
            mpath_info = self._linuxscsi.find_multipath_device(device_realpath)
            if mpath_info:
                device_path = mpath_info['device']
                multipath_id = device_wwn
            else:
                # we didn't find a multipath device.
                # so we assume the kernel only sees 1 device
                device_path = device_name
                LOG.debug(
                    "Unable to find multipath device name for "
                    "volume. Using path %(device)s for volume.",
                    {'device': device_path})
        else:
            device_path = path
            multipath_id = device_wwn
        if connection_properties.get('access_mode', '') != 'ro':
            try:
                # Sometimes the multipath devices will show up as read only
                # initially and need additional time/rescans to get to RW.
                self._linuxscsi.wait_for_rw(device_wwn, device_path)
            except exception.BlockDeviceReadOnly:
                LOG.warning(
                    _LW('Block device %s is still read-only. '
                        'Continuing anyway.'), device_path)
        return device_path, multipath_id
Пример #21
0
    def _get_iscsi_sessions(self):
        out, err = self._run_iscsi_session()

        iscsi_sessions = []

        if err:
            LOG.warning(
                _LW("Couldn't find iscsi sessions because "
                    "iscsiadm err: %s"), err)
        else:
            # parse the output from iscsiadm
            # lines are in the format of
            # tcp: [1] 192.168.121.250:3260,1 iqn.2010-10.org.openstack:volume-
            lines = out.split('\n')
            for line in lines:
                if line:
                    entries = line.split()
                    portal = entries[2].split(',')
                    iscsi_sessions.append(portal[0])

        return iscsi_sessions
Пример #22
0
 def validate_initiators(self):
     """Validates the list of requested initiator HBAs to be used
     when establishing iSCSI sessions.
     """
     valid_initiator_list = True
     if not self.initiator_list:
         LOG.info(_LI("No iSCSI initiator was explicitly requested. "
                      "The Microsoft iSCSI initiator will choose the "
                      "initiator when establishing sessions."))
     else:
         available_initiators = self._iscsi_utils.get_iscsi_initiators()
         for initiator in self.initiator_list:
             if initiator not in available_initiators:
                 msg = _LW("The requested initiator %(req_initiator)s "
                           "is not in the list of available initiators: "
                           "%(avail_initiators)s.")
                 LOG.warning(msg,
                             dict(req_initiator=initiator,
                                  avail_initiators=available_initiators))
                 valid_initiator_list = False
     return valid_initiator_list
Пример #23
0
    def _get_iscsi_sessions(self):
        out, err = self._run_iscsi_session()

        iscsi_sessions = []

        if err:
            LOG.warning(_LW("Couldn't find iscsi sessions because "
                        "iscsiadm err: %s"),
                        err)
        else:
            # parse the output from iscsiadm
            # lines are in the format of
            # tcp: [1] 192.168.121.250:3260,1 iqn.2010-10.org.openstack:volume-
            lines = out.split('\n')
            for line in lines:
                if line:
                    entries = line.split()
                    portal = entries[2].split(',')
                    iscsi_sessions.append(portal[0])

        return iscsi_sessions
Пример #24
0
    def _discover_mpath_device(self, device_wwn, connection_properties,
                               device_name):
        """This method discovers a multipath device.

        Discover a multipath device based on a defined connection_property
        and a device_wwn and return the multipath_id and path of the multipath
        enabled device if there is one.
        """

        path = self._linuxscsi.find_multipath_device_path(device_wwn)
        device_path = None
        multipath_id = None

        if path is None:
            # find_multipath_device only accept realpath not symbolic path
            device_realpath = os.path.realpath(device_name)
            mpath_info = self._linuxscsi.find_multipath_device(
                device_realpath)
            if mpath_info:
                device_path = mpath_info['device']
                multipath_id = device_wwn
            else:
                # we didn't find a multipath device.
                # so we assume the kernel only sees 1 device
                device_path = device_name
                LOG.debug("Unable to find multipath device name for "
                          "volume. Using path %(device)s for volume.",
                          {'device': device_path})
        else:
            device_path = path
            multipath_id = device_wwn
        if connection_properties.get('access_mode', '') != 'ro':
            try:
                # Sometimes the multipath devices will show up as read only
                # initially and need additional time/rescans to get to RW.
                self._linuxscsi.wait_for_rw(device_wwn, device_path)
            except exception.BlockDeviceReadOnly:
                LOG.warning(_LW('Block device %s is still read-only. '
                                'Continuing anyway.'), device_path)
        return device_path, multipath_id
Пример #25
0
    def _get_multipath_device_map(self):
        out = self._run_multipath(['-ll'], check_exit_code=[0, 1])[0]
        mpath_line = [
            line for line in out.splitlines()
            if not re.match(initiator.MULTIPATH_ERROR_REGEX, line)
        ]
        mpath_dev = None
        mpath_map = {}
        for line in out.splitlines():
            m = initiator.MULTIPATH_DEV_CHECK_REGEX.split(line)
            if len(m) >= 2:
                mpath_dev = '/dev/mapper/' + m[0].split(" ")[0]
                continue
            m = initiator.MULTIPATH_PATH_CHECK_REGEX.split(line)
            if len(m) >= 2:
                mpath_map['/dev/' + m[1].split(" ")[0]] = mpath_dev

        if mpath_line and not mpath_map:
            LOG.warning(
                _LW("Failed to parse the output of multipath -ll. "
                    "stdout: %s"), out)
        return mpath_map
Пример #26
0
    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(_LW("unit_remove call for s390 failed exit %(code)s, "
                            "stderr %(stderr)s"),
                        {'code': exc.exit_code, 'stderr': exc.stderr})
Пример #27
0
    def _validate_iface_transport(self, transport_iface):
        """Check that given iscsi_iface uses only supported transports

        Accepted transport names for provided iface param are
        be2iscsi, bnx2i, cxgb3i, cxgb4i, default, qla4xxx, ocs or iser.
        Note the difference between transport and iface;
        unlike default(iscsi_tcp)/iser, this is not one and the same for
        offloaded transports, where the default format is
        transport_name.hwaddress

        :param transport_iface: The iscsi transport type.
        :type transport_iface: str
        :returns: str
        """
        # Note that default(iscsi_tcp) and iser do not require a separate
        # iface file, just the transport is enough and do not need to be
        # validated. This is not the case for the other entries in
        # supported_transports array.
        if transport_iface in ['default', 'iser']:
            return transport_iface
        # Will return (6) if iscsi_iface file was not found, or (2) if iscsid
        # could not be contacted
        out = self._run_iscsiadm_bare(['-m',
                                       'iface',
                                       '-I',
                                       transport_iface],
                                      check_exit_code=[0, 2, 6])[0] or ""
        LOG.debug("iscsiadm %(iface)s configuration: stdout=%(out)s.",
                  {'iface': transport_iface, 'out': out})
        for data in [line.split() for line in out.splitlines()]:
            if data[0] == 'iface.transport_name':
                if data[2] in self.supported_transports:
                    return transport_iface

        LOG.warning(_LW("No useable transport found for iscsi iface %s. "
                        "Falling back to default transport."),
                    transport_iface)
        return 'default'
Пример #28
0
 def validate_initiators(self):
     """Validates the list of requested initiator HBAs to be used
     when establishing iSCSI sessions.
     """
     valid_initiator_list = True
     if not self.initiator_list:
         LOG.info(
             _LI("No iSCSI initiator was explicitly requested. "
                 "The Microsoft iSCSI initiator will choose the "
                 "initiator when establishing sessions."))
     else:
         available_initiators = self._iscsi_utils.get_iscsi_initiators()
         for initiator in self.initiator_list:
             if initiator not in available_initiators:
                 msg = _LW("The requested initiator %(req_initiator)s "
                           "is not in the list of available initiators: "
                           "%(avail_initiators)s.")
                 LOG.warning(
                     msg,
                     dict(req_initiator=initiator,
                          avail_initiators=available_initiators))
                 valid_initiator_list = False
     return valid_initiator_list
Пример #29
0
    def _validate_iface_transport(self, transport_iface):
        """Check that given iscsi_iface uses only supported transports

        Accepted transport names for provided iface param are
        be2iscsi, bnx2i, cxgb3i, cxgb4i, default, qla4xxx, ocs or iser.
        Note the difference between transport and iface;
        unlike default(iscsi_tcp)/iser, this is not one and the same for
        offloaded transports, where the default format is
        transport_name.hwaddress

        :param transport_iface: The iscsi transport type.
        :type transport_iface: str
        :returns: str
        """
        # Note that default(iscsi_tcp) and iser do not require a separate
        # iface file, just the transport is enough and do not need to be
        # validated. This is not the case for the other entries in
        # supported_transports array.
        if transport_iface in ['default', 'iser']:
            return transport_iface
        # Will return (6) if iscsi_iface file was not found, or (2) if iscsid
        # could not be contacted
        out = self._run_iscsiadm_bare(['-m', 'iface', '-I', transport_iface],
                                      check_exit_code=[0, 2, 6])[0] or ""
        LOG.debug("iscsiadm %(iface)s configuration: stdout=%(out)s.", {
            'iface': transport_iface,
            'out': out
        })
        for data in [line.split() for line in out.splitlines()]:
            if data[0] == 'iface.transport_name':
                if data[2] in self.supported_transports:
                    return transport_iface

        LOG.warning(
            _LW("No useable transport found for iscsi iface %s. "
                "Falling back to default transport."), transport_iface)
        return 'default'
Пример #30
0
def get_encryption_metadata(context, volume_api, volume_id, connection_info):
    metadata = {}
    if ('data' in connection_info and
            connection_info['data'].get('encrypted', False)):
        try:
            metadata = volume_api.get_volume_encryption_metadata(context,
                                                                 volume_id)
            if not metadata:
                LOG.warning(_LW(
                    'Volume %s should be encrypted but there is no '
                    'encryption metadata.'), volume_id)
        except Exception as e:
            LOG.error(_LE("Failed to retrieve encryption metadata for "
                          "volume %(volume_id)s: %(exception)s"),
                      {'volume_id': volume_id, 'exception': e})
            raise

    if metadata:
        msg = ("Using volume encryption metadata '%(metadata)s' for "
               "connection: %(connection_info)s" %
               {'metadata': metadata, 'connection_info': connection_info})
        LOG.debug(strutils.mask_password(msg))

    return metadata
    def find_multipath_device(self, device):
        """Discover multipath devices for a mpath device.

           This uses the slow multipath -l command to find a
           multipath device description, then screen scrapes
           the output to discover the multipath device name
           and it's devices.

        """

        mdev = None
        devices = []
        out = None
        try:
            (out, _err) = self._execute('multipath', '-l', device,
                                        run_as_root=True,
                                        root_helper=self._root_helper)
        except putils.ProcessExecutionError as exc:
            LOG.warning(_LW("multipath call failed exit %(code)s"),
                        {'code': exc.exit_code})
            return None

        if out:
            lines = out.strip()
            lines = lines.split("\n")
            lines = [line for line in lines
                     if not re.match(MULTIPATH_ERROR_REGEX, line)]
            if lines:

                mdev_name = lines[0].split(" ")[0]

                if mdev_name in MULTIPATH_DEVICE_ACTIONS:
                    mdev_name = lines[0].split(" ")[1]

                mdev = '/dev/mapper/%s' % mdev_name

                # Confirm that the device is present.
                try:
                    os.stat(mdev)
                except OSError:
                    LOG.warning(_LW("Couldn't find multipath device %s"),
                                mdev)
                    return None

                wwid_search = MULTIPATH_WWID_REGEX.search(lines[0])
                if wwid_search is not None:
                    mdev_id = wwid_search.group('wwid')
                else:
                    mdev_id = mdev_name

                LOG.debug("Found multipath device = %(mdev)s",
                          {'mdev': mdev})
                device_lines = lines[3:]
                for dev_line in device_lines:
                    if dev_line.find("policy") != -1:
                        continue

                    dev_line = dev_line.lstrip(' |-`')
                    dev_info = dev_line.split()
                    address = dev_info[0].split(":")

                    dev = {'device': '/dev/%s' % dev_info[1],
                           'host': address[0], 'channel': address[1],
                           'id': address[2], 'lun': address[3]
                           }

                    devices.append(dev)

        if mdev is not None:
            info = {"device": mdev,
                    "id": mdev_id,
                    "name": mdev_name,
                    "devices": devices}
            return info
        return None
Пример #32
0
    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(_LI("Multipath discovery for iSCSI enabled"))
            # Multipath installed, discovering other targets if available
            try:
                ips_iqns = 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 = set([ip for ip, iqn in ips_iqns])
                match_portals = set([ip for ip, iqn in ips_iqns
                                     if iqn == main_iqn])
                if len(all_portals) == len(match_portals):
                    ips_iqns = zip(all_portals, [main_iqn] * len(all_portals))

            for ip, iqn in ips_iqns:
                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()
            host_devices = self._get_device_path(connection_properties)
        else:
            LOG.info(_LI("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(_LW(
                            '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
Пример #33
0
    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(_LI("Multipath discovery for iSCSI enabled"))
            # Multipath installed, discovering other targets if available
            try:
                ips_iqns = 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 = set([ip for ip, iqn in ips_iqns])
                match_portals = set(
                    [ip for ip, iqn in ips_iqns if iqn == main_iqn])
                if len(all_portals) == len(match_portals):
                    ips_iqns = zip(all_portals, [main_iqn] * len(all_portals))

            for ip, iqn in ips_iqns:
                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()
            host_devices = self._get_device_path(connection_properties)
        else:
            LOG.info(_LI("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(
                            _LW('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
Пример #34
0
    def _connect_to_iscsi_portal(self, connection_properties):
        # NOTE(vish): If we are on the same host as nova volume, the
        #             discovery makes the target so we don't need to
        #             run --op new. Therefore, we check to see if the
        #             target exists, and if we get 255 (Not Found), then
        #             we run --op new. This will also happen if another
        #             volume is using the same target.
        LOG.info(_LI("Trying to connect to iSCSI portal %(portal)s"),
                 {"portal": connection_properties['target_portal']})
        try:
            self._run_iscsiadm(connection_properties, ())
        except putils.ProcessExecutionError as exc:
            # iscsiadm returns 21 for "No records found" after version 2.0-871
            if exc.exit_code in [21, 255]:
                self._run_iscsiadm(connection_properties,
                                   ('--interface', self._get_transport(),
                                    '--op', 'new'))
            else:
                raise

        if connection_properties.get('auth_method'):
            self._iscsiadm_update(connection_properties,
                                  "node.session.auth.authmethod",
                                  connection_properties['auth_method'])
            self._iscsiadm_update(connection_properties,
                                  "node.session.auth.username",
                                  connection_properties['auth_username'])
            self._iscsiadm_update(connection_properties,
                                  "node.session.auth.password",
                                  connection_properties['auth_password'])

        # Duplicate logins crash iscsiadm after load,
        # so we scan active sessions to see if the node is logged in.
        out = self._run_iscsiadm_bare(["-m", "session"],
                                      run_as_root=True,
                                      check_exit_code=[0, 1, 21])[0] or ""

        portals = [{'portal': p.split(" ")[2], 'iqn': p.split(" ")[3]}
                   for p in out.splitlines() if p.startswith("tcp:")]

        stripped_portal = connection_properties['target_portal'].split(",")[0]
        if len(portals) == 0 or len([s for s in portals
                                     if stripped_portal ==
                                     s['portal'].split(",")[0]
                                     and
                                     s['iqn'] ==
                                     connection_properties['target_iqn']]
                                    ) == 0:
            try:
                self._run_iscsiadm(connection_properties,
                                   ("--login",),
                                   check_exit_code=[0, 255])
            except putils.ProcessExecutionError as err:
                # exit_code=15 means the session already exists, so it should
                # be regarded as successful login.
                if err.exit_code not in [15]:
                    LOG.warning(_LW('Failed to login iSCSI target %(iqn)s '
                                    'on portal %(portal)s (exit code '
                                    '%(err)s).'),
                                {'iqn': connection_properties['target_iqn'],
                                 'portal': connection_properties[
                                     'target_portal'],
                                 'err': err.exit_code})
                    return False

            self._iscsiadm_update(connection_properties,
                                  "node.startup",
                                  "automatic")
        return True
Пример #35
0
    def disconnect_volume(self, connection_properties, device_info):
        """Disconnect the ScaleIO volume.

        :param connection_properties: The dictionary that describes all
                                      of the target volume attributes.
        :type connection_properties: dict
        :param device_info: historical difference, but same as connection_props
        :type device_info: dict
        """
        self.get_config(connection_properties)
        self.volume_id = self.volume_id or self._get_volume_id()
        LOG.info(
            _LI("ScaleIO disconnect volume in ScaleIO brick volume driver."))

        LOG.debug(
            _("ScaleIO Volume name: %(volume_name)s, SDC IP: %(sdc_ip)s, "
              "REST Server IP: %(server_ip)s"), {
                  'volume_name': self.volume_name,
                  'sdc_ip': self.local_sdc_ip,
                  'server_ip': self.server_ip
              })

        LOG.info(_LI("ScaleIO sdc query guid command: %(cmd)s"),
                 {'cmd': self.GET_GUID_CMD})

        try:
            (out, err) = self._execute(*self.GET_GUID_CMD,
                                       run_as_root=True,
                                       root_helper=self._root_helper)
            LOG.info(
                _LI("Unmap volume %(cmd)s: stdout=%(out)s stderr=%(err)s"), {
                    'cmd': self.GET_GUID_CMD,
                    'out': out,
                    'err': err
                })

        except putils.ProcessExecutionError as e:
            msg = _("Error querying sdc guid: %(err)s") % {'err': e.stderr}
            LOG.error(msg)
            raise exception.BrickException(message=msg)

        guid = out
        LOG.info(_LI("Current sdc guid: %(guid)s"), {'guid': guid})

        params = {'guid': guid}
        headers = {'content-type': 'application/json'}
        request = ("https://%(server_ip)s:%(server_port)s/api/instances/"
                   "Volume::%(volume_id)s/action/removeMappedSdc" % {
                       'server_ip': self.server_ip,
                       'server_port': self.server_port,
                       'volume_id': self.volume_id
                   })

        LOG.info(_LI("Unmap volume request: %(request)s"),
                 {'request': request})
        r = requests.post(request,
                          data=json.dumps(params),
                          headers=headers,
                          auth=(self.server_username, self.server_token),
                          verify=False)

        r = self._check_response(r, request, False, params)
        if r.status_code != self.OK_STATUS_CODE:
            response = r.json()
            error_code = response['errorCode']
            if error_code == self.VOLUME_NOT_MAPPED_ERROR:
                LOG.warning(
                    _LW("Ignoring error unmapping volume %(volume_id)s: "
                        "volume not mapped."), {'volume_id': self.volume_name})
            else:
                msg = (_("Error unmapping volume %(volume_id)s: %(err)s") % {
                    'volume_id': self.volume_name,
                    'err': response['message']
                })
                LOG.error(msg)
                raise exception.BrickException(message=msg)
 def flush(self):
     try:
         self._rbd_volume.image.flush()
     except AttributeError:
         LOG.warning(_LW("flush() not supported in this version of librbd"))
Пример #37
0
    def _connect_to_iscsi_portal(self, connection_properties):
        # NOTE(vish): If we are on the same host as nova volume, the
        #             discovery makes the target so we don't need to
        #             run --op new. Therefore, we check to see if the
        #             target exists, and if we get 255 (Not Found), then
        #             we run --op new. This will also happen if another
        #             volume is using the same target.
        LOG.info(_LI("Trying to connect to iSCSI portal %(portal)s"),
                 {"portal": connection_properties['target_portal']})
        try:
            self._run_iscsiadm(connection_properties, ())
        except putils.ProcessExecutionError as exc:
            # iscsiadm returns 21 for "No records found" after version 2.0-871
            if exc.exit_code in [21, 255]:
                self._run_iscsiadm(
                    connection_properties,
                    ('--interface', self._get_transport(), '--op', 'new'))
            else:
                raise

        if connection_properties.get('auth_method'):
            self._iscsiadm_update(connection_properties,
                                  "node.session.auth.authmethod",
                                  connection_properties['auth_method'])
            self._iscsiadm_update(connection_properties,
                                  "node.session.auth.username",
                                  connection_properties['auth_username'])
            self._iscsiadm_update(connection_properties,
                                  "node.session.auth.password",
                                  connection_properties['auth_password'])

        # Duplicate logins crash iscsiadm after load,
        # so we scan active sessions to see if the node is logged in.
        out = self._run_iscsiadm_bare(["-m", "session"],
                                      run_as_root=True,
                                      check_exit_code=[0, 1, 21])[0] or ""

        portals = [{
            'portal': p.split(" ")[2],
            'iqn': p.split(" ")[3]
        } for p in out.splitlines() if p.startswith("tcp:")]

        stripped_portal = connection_properties['target_portal'].split(",")[0]
        if len(portals) == 0 or len([
                s for s in portals
                if stripped_portal == s['portal'].split(",")[0]
                and s['iqn'] == connection_properties['target_iqn']
        ]) == 0:
            try:
                self._run_iscsiadm(connection_properties, ("--login", ),
                                   check_exit_code=[0, 255])
            except putils.ProcessExecutionError as err:
                # exit_code=15 means the session already exists, so it should
                # be regarded as successful login.
                if err.exit_code not in [15]:
                    LOG.warning(
                        _LW('Failed to login iSCSI target %(iqn)s '
                            'on portal %(portal)s (exit code '
                            '%(err)s).'),
                        {
                            'iqn': connection_properties['target_iqn'],
                            'portal': connection_properties['target_portal'],
                            'err': err.exit_code
                        })
                    return False

            self._iscsiadm_update(connection_properties, "node.startup",
                                  "automatic")
        return True
Пример #38
0
    def find_multipath_device(self, device):
        """Find a multipath device associated with a LUN device name.

        device can be either a /dev/sdX entry or a multipath id.
        """

        mdev = None
        devices = []
        out = None
        try:
            (out, _err) = self._execute('multipath', '-l', device,
                                        run_as_root=True,
                                        root_helper=self._root_helper)
        except putils.ProcessExecutionError as exc:
            LOG.warning(_LW("multipath call failed exit %(code)s"),
                        {'code': exc.exit_code})
            return None

        if out:
            lines = out.strip()
            lines = lines.split("\n")
            lines = [line for line in lines
                     if not re.match(MULTIPATH_ERROR_REGEX, line)]
            if lines:

                # Use the device name, be it the WWID, mpathN or custom alias
                # of a device to build the device path. This should be the
                # first item on the first line of output from `multipath -l
                # ${path}` or `multipath -l ${wwid}`..
                mdev_name = lines[0].split(" ")[0]
                mdev = '/dev/mapper/%s' % mdev_name

                # Find the WWID for the LUN if we are using mpathN or aliases.
                wwid_search = MULTIPATH_WWID_REGEX.search(lines[0])
                if wwid_search is not None:
                    mdev_id = wwid_search.group('wwid')
                else:
                    mdev_id = mdev_name

                # Confirm that the device is present.
                try:
                    os.stat(mdev)
                except OSError:
                    LOG.warn(_LW("Couldn't find multipath device %s"), mdev)
                    return None

                LOG.debug("Found multipath device = %(mdev)s",
                          {'mdev': mdev})
                device_lines = lines[3:]
                for dev_line in device_lines:
                    if dev_line.find("policy") != -1:
                        continue

                    dev_line = dev_line.lstrip(' |-`')
                    dev_info = dev_line.split()
                    address = dev_info[0].split(":")

                    dev = {'device': '/dev/%s' % dev_info[1],
                           'host': address[0], 'channel': address[1],
                           'id': address[2], 'lun': address[3]
                           }

                    devices.append(dev)

        if mdev is not None:
            info = {"device": mdev,
                    "id": mdev_id,
                    "name": mdev_name,
                    "devices": devices}
            return info
        return None
Пример #39
0
 def flush(self):
     try:
         self._rbd_volume.image.flush()
     except AttributeError:
         LOG.warning(_LW("flush() not supported in this version of librbd"))
Пример #40
0
    def disconnect_volume(self, connection_properties, device_info):
        """Disconnect the ScaleIO volume.

        :param connection_properties: The dictionary that describes all
                                      of the target volume attributes.
        :type connection_properties: dict
        :param device_info: historical difference, but same as connection_props
        :type device_info: dict
        """
        self.get_config(connection_properties)
        self.volume_id = self.volume_id or self._get_volume_id()
        LOG.info(_LI(
            "ScaleIO disconnect volume in ScaleIO brick volume driver."
        ))

        LOG.debug(
            _("ScaleIO Volume name: %(volume_name)s, SDC IP: %(sdc_ip)s, "
              "REST Server IP: %(server_ip)s"),
            {'volume_name': self.volume_name, 'sdc_ip': self.local_sdc_ip,
             'server_ip': self.server_ip}
        )

        LOG.info(_LI("ScaleIO sdc query guid command: %(cmd)s"),
                 {'cmd': self.GET_GUID_CMD})

        try:
            (out, err) = self._execute(*self.GET_GUID_CMD, run_as_root=True,
                                       root_helper=self._root_helper)
            LOG.info(
                _LI("Unmap volume %(cmd)s: stdout=%(out)s stderr=%(err)s"),
                {'cmd': self.GET_GUID_CMD, 'out': out, 'err': err}
            )

        except putils.ProcessExecutionError as e:
            msg = _("Error querying sdc guid: %(err)s") % {'err': e.stderr}
            LOG.error(msg)
            raise exception.BrickException(message=msg)

        guid = out
        LOG.info(_LI("Current sdc guid: %(guid)s"), {'guid': guid})

        params = {'guid': guid}
        headers = {'content-type': 'application/json'}
        request = (
            "https://%(server_ip)s:%(server_port)s/api/instances/"
            "Volume::%(volume_id)s/action/removeMappedSdc" %
            {'server_ip': self.server_ip, 'server_port': self.server_port,
             'volume_id': self.volume_id}
        )

        LOG.info(_LI("Unmap volume request: %(request)s"),
                 {'request': request})
        r = requests.post(
            request,
            data=json.dumps(params),
            headers=headers,
            auth=(self.server_username, self.server_token),
            verify=False
        )

        r = self._check_response(r, request, False, params)
        if r.status_code != self.OK_STATUS_CODE:
            response = r.json()
            error_code = response['errorCode']
            if error_code == self.VOLUME_NOT_MAPPED_ERROR:
                LOG.warning(_LW(
                    "Ignoring error unmapping volume %(volume_id)s: "
                    "volume not mapped."), {'volume_id': self.volume_name}
                )
            else:
                msg = (_("Error unmapping volume %(volume_id)s: %(err)s") %
                       {'volume_id': self.volume_name,
                        'err': response['message']})
                LOG.error(msg)
                raise exception.BrickException(message=msg)
Пример #41
0
    def connect_volume(self, connection_properties):
        """Attach the volume to instance_name.

        :param connection_properties: The dictionary that describes all
                                      of the target volume attributes.
        :type connection_properties: dict
        :returns: dict

        connection_properties for Fibre Channel must include:
        target_wwn - World Wide Name
        target_lun - LUN id of the volume
        """
        LOG.debug("execute = %s", self._execute)
        device_info = {'type': 'block'}

        hbas = self._linuxfc.get_fc_hbas_info()
        host_devices = self._get_possible_volume_paths(
            connection_properties, hbas)

        if len(host_devices) == 0:
            # this is empty because we don't have any FC HBAs
            LOG.warning(
                _LW("We are unable to locate any Fibre Channel devices"))
            raise exception.NoFibreChannelHostsFound()

        # The /dev/disk/by-path/... node is not always present immediately
        # We only need to find the first device.  Once we see the first device
        # multipath will have any others.
        def _wait_for_device_discovery(host_devices):
            tries = self.tries
            for device in host_devices:
                LOG.debug("Looking for Fibre Channel dev %(device)s",
                          {'device': device})
                if os.path.exists(device):
                    self.host_device = device
                    # get the /dev/sdX device.  This is used
                    # to find the multipath device.
                    self.device_name = os.path.realpath(device)
                    raise loopingcall.LoopingCallDone()

            if self.tries >= self.device_scan_attempts:
                LOG.error(_LE("Fibre Channel volume device not found."))
                raise exception.NoFibreChannelVolumeDeviceFound()

            LOG.info(_LI("Fibre Channel volume device not yet found. "
                         "Will rescan & retry.  Try number: %(tries)s."),
                     {'tries': tries})

            self._linuxfc.rescan_hosts(hbas,
                                       connection_properties['target_lun'])
            self.tries = self.tries + 1

        self.host_device = None
        self.device_name = None
        self.tries = 0
        timer = loopingcall.FixedIntervalLoopingCall(
            _wait_for_device_discovery, host_devices)
        timer.start(interval=2).wait()

        tries = self.tries
        if self.host_device is not None and self.device_name is not None:
            LOG.debug("Found Fibre Channel volume %(name)s "
                      "(after %(tries)s rescans)",
                      {'name': self.device_name, 'tries': tries})

        # find out the WWN of the device
        device_wwn = self._linuxscsi.get_scsi_wwn(self.host_device)
        LOG.debug("Device WWN = '%(wwn)s'", {'wwn': device_wwn})
        device_info['scsi_wwn'] = device_wwn

        # see if the new drive is part of a multipath
        # device.  If so, we'll use the multipath device.
        if self.use_multipath:
            (device_path, multipath_id) = (super(
                FibreChannelConnector, self)._discover_mpath_device(
                device_wwn, connection_properties, self.device_name))
            if multipath_id:
                # only set the multipath_id if we found one
                device_info['multipath_id'] = multipath_id

        else:
            device_path = self.host_device

        device_info['path'] = device_path
        LOG.debug("connect_volume returning %s", device_info)
        return device_info
Пример #42
0
    def connect_volume(self, connection_properties):
        """Connect the volume.

        :param connection_properties: The dictionary that describes all
                                      of the target volume attributes.
        :type connection_properties: dict
        :returns: dict
        """
        device_info = self.get_config(connection_properties)
        LOG.debug(
            _(
                "scaleIO Volume name: %(volume_name)s, SDC IP: %(sdc_ip)s, "
                "REST Server IP: %(server_ip)s, "
                "REST Server username: %(username)s, "
                "iops limit:%(iops_limit)s, "
                "bandwidth limit: %(bandwidth_limit)s."
            ), {
                'volume_name': self.volume_name,
                'volume_id': self.volume_id,
                'sdc_ip': self.local_sdc_ip,
                'server_ip': self.server_ip,
                'username': self.server_username,
                'iops_limit': self.iops_limit,
                'bandwidth_limit': self.bandwidth_limit
            }
        )

        LOG.info(_LI("ScaleIO sdc query guid command: %(cmd)s"),
                 {'cmd': self.GET_GUID_CMD})

        try:
            (out, err) = self._execute(*self.GET_GUID_CMD, run_as_root=True,
                                       root_helper=self._root_helper)

            LOG.info(_LI("Map volume %(cmd)s: stdout=%(out)s "
                         "stderr=%(err)s"),
                     {'cmd': self.GET_GUID_CMD, 'out': out, 'err': err})

        except putils.ProcessExecutionError as e:
            msg = (_("Error querying sdc guid: %(err)s") % {'err': e.stderr})
            LOG.error(msg)
            raise exception.BrickException(message=msg)

        guid = out
        LOG.info(_LI("Current sdc guid: %(guid)s"), {'guid': guid})
        params = {'guid': guid, 'allowMultipleMappings': 'TRUE'}
        self.volume_id = self.volume_id or self._get_volume_id()

        headers = {'content-type': 'application/json'}
        request = (
            "https://%(server_ip)s:%(server_port)s/api/instances/"
            "Volume::%(volume_id)s/action/addMappedSdc" %
            {'server_ip': self.server_ip, 'server_port': self.server_port,
             'volume_id': self.volume_id}
        )

        LOG.info(_LI("map volume request: %(request)s"), {'request': request})
        r = requests.post(
            request,
            data=json.dumps(params),
            headers=headers,
            auth=(self.server_username, self.server_token),
            verify=False
        )

        r = self._check_response(r, request, False, params)
        if r.status_code != self.OK_STATUS_CODE:
            response = r.json()
            error_code = response['errorCode']
            if error_code == self.VOLUME_ALREADY_MAPPED_ERROR:
                LOG.warning(_LW(
                    "Ignoring error mapping volume %(volume_name)s: "
                    "volume already mapped."),
                    {'volume_name': self.volume_name}
                )
            else:
                msg = (
                    _("Error mapping volume %(volume_name)s: %(err)s") %
                    {'volume_name': self.volume_name,
                     'err': response['message']}
                )

                LOG.error(msg)
                raise exception.BrickException(message=msg)

        self.volume_path = self._find_volume_path()
        device_info['path'] = self.volume_path

        # Set QoS settings after map was performed
        if self.iops_limit is not None or self.bandwidth_limit is not None:
            params = {'guid': guid}
            if self.bandwidth_limit is not None:
                params['bandwidthLimitInKbps'] = self.bandwidth_limit
            if self.iops_limit is not None:
                params['iopsLimit'] = self.iops_limit

            request = (
                "https://%(server_ip)s:%(server_port)s/api/instances/"
                "Volume::%(volume_id)s/action/setMappedSdcLimits" %
                {'server_ip': self.server_ip, 'server_port': self.server_port,
                 'volume_id': self.volume_id}
            )

            LOG.info(_LI("Set client limit request: %(request)s"),
                     {'request': request})

            r = requests.post(
                request,
                data=json.dumps(params),
                headers=headers,
                auth=(self.server_username, self.server_token),
                verify=False
            )
            r = self._check_response(r, request, False, params)
            if r.status_code != self.OK_STATUS_CODE:
                response = r.json()
                LOG.info(_LI("Set client limit response: %(response)s"),
                         {'response': response})
                msg = (
                    _("Error setting client limits for volume "
                      "%(volume_name)s: %(err)s") %
                    {'volume_name': self.volume_name,
                     'err': response['message']}
                )

                LOG.error(msg)

        return device_info
Пример #43
0
    def connect_volume(self, connection_properties):
        """Connect the volume.

        :param connection_properties: The dictionary that describes all
                                      of the target volume attributes.
        :type connection_properties: dict
        :returns: dict
        """
        device_info = self.get_config(connection_properties)
        LOG.debug(
            _("scaleIO Volume name: %(volume_name)s, SDC IP: %(sdc_ip)s, "
              "REST Server IP: %(server_ip)s, "
              "REST Server username: %(username)s, "
              "iops limit:%(iops_limit)s, "
              "bandwidth limit: %(bandwidth_limit)s."), {
                  'volume_name': self.volume_name,
                  'volume_id': self.volume_id,
                  'sdc_ip': self.local_sdc_ip,
                  'server_ip': self.server_ip,
                  'username': self.server_username,
                  'iops_limit': self.iops_limit,
                  'bandwidth_limit': self.bandwidth_limit
              })

        LOG.info(_LI("ScaleIO sdc query guid command: %(cmd)s"),
                 {'cmd': self.GET_GUID_CMD})

        try:
            (out, err) = self._execute(*self.GET_GUID_CMD,
                                       run_as_root=True,
                                       root_helper=self._root_helper)

            LOG.info(
                _LI("Map volume %(cmd)s: stdout=%(out)s "
                    "stderr=%(err)s"), {
                        'cmd': self.GET_GUID_CMD,
                        'out': out,
                        'err': err
                    })

        except putils.ProcessExecutionError as e:
            msg = (_("Error querying sdc guid: %(err)s") % {'err': e.stderr})
            LOG.error(msg)
            raise exception.BrickException(message=msg)

        guid = out
        LOG.info(_LI("Current sdc guid: %(guid)s"), {'guid': guid})
        params = {'guid': guid, 'allowMultipleMappings': 'TRUE'}
        self.volume_id = self.volume_id or self._get_volume_id()

        headers = {'content-type': 'application/json'}
        request = ("https://%(server_ip)s:%(server_port)s/api/instances/"
                   "Volume::%(volume_id)s/action/addMappedSdc" % {
                       'server_ip': self.server_ip,
                       'server_port': self.server_port,
                       'volume_id': self.volume_id
                   })

        LOG.info(_LI("map volume request: %(request)s"), {'request': request})
        r = requests.post(request,
                          data=json.dumps(params),
                          headers=headers,
                          auth=(self.server_username, self.server_token),
                          verify=False)

        r = self._check_response(r, request, False, params)
        if r.status_code != self.OK_STATUS_CODE:
            response = r.json()
            error_code = response['errorCode']
            if error_code == self.VOLUME_ALREADY_MAPPED_ERROR:
                LOG.warning(
                    _LW("Ignoring error mapping volume %(volume_name)s: "
                        "volume already mapped."),
                    {'volume_name': self.volume_name})
            else:
                msg = (_("Error mapping volume %(volume_name)s: %(err)s") % {
                    'volume_name': self.volume_name,
                    'err': response['message']
                })

                LOG.error(msg)
                raise exception.BrickException(message=msg)

        self.volume_path = self._find_volume_path()
        device_info['path'] = self.volume_path

        # Set QoS settings after map was performed
        if self.iops_limit is not None or self.bandwidth_limit is not None:
            params = {'guid': guid}
            if self.bandwidth_limit is not None:
                params['bandwidthLimitInKbps'] = self.bandwidth_limit
            if self.iops_limit is not None:
                params['iopsLimit'] = self.iops_limit

            request = ("https://%(server_ip)s:%(server_port)s/api/instances/"
                       "Volume::%(volume_id)s/action/setMappedSdcLimits" % {
                           'server_ip': self.server_ip,
                           'server_port': self.server_port,
                           'volume_id': self.volume_id
                       })

            LOG.info(_LI("Set client limit request: %(request)s"),
                     {'request': request})

            r = requests.post(request,
                              data=json.dumps(params),
                              headers=headers,
                              auth=(self.server_username, self.server_token),
                              verify=False)
            r = self._check_response(r, request, False, params)
            if r.status_code != self.OK_STATUS_CODE:
                response = r.json()
                LOG.info(_LI("Set client limit response: %(response)s"),
                         {'response': response})
                msg = (_("Error setting client limits for volume "
                         "%(volume_name)s: %(err)s") % {
                             'volume_name': self.volume_name,
                             'err': response['message']
                         })

                LOG.error(msg)

        return device_info