Пример #1
0
    def _get_client_id(self):
        request = ("https://%(server_ip)s:%(server_port)s/"
                   "api/types/Client/instances/getByIp::%(sdc_ip)s/" % {
                       'server_ip': self.server_ip,
                       'server_port': self.server_port,
                       'sdc_ip': self.local_sdc_ip
                   })

        LOG.info(_LI("ScaleIO get client id by ip request: %(request)s"),
                 {'request': request})

        r = requests.get(request,
                         auth=(self.server_username, self.server_token),
                         verify=False)

        r = self._check_response(r, request)
        sdc_id = r.json()
        if not sdc_id:
            msg = (_("Client with ip %(sdc_ip)s was not found.") % {
                'sdc_ip': self.local_sdc_ip
            })
            raise exception.BrickException(message=msg)

        if r.status_code != 200 and "errorCode" in sdc_id:
            msg = (_("Error getting sdc id from ip %(sdc_ip)s: %(err)s") % {
                'sdc_ip': self.local_sdc_ip,
                'err': sdc_id['message']
            })

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

        LOG.info(_LI("ScaleIO sdc id is %(sdc_id)s."), {'sdc_id': sdc_id})
        return sdc_id
Пример #2
0
    def extend_volume(self, volume_path):
        """Signal the SCSI subsystem to test for volume resize.

        This function tries to signal the local system's kernel
        that an already attached volume might have been resized.
        """
        LOG.debug("extend volume %s", volume_path)

        device = self.get_device_info(volume_path)
        LOG.debug("Volume device info = %s", device)
        device_id = ("%(host)s:%(channel)s:%(id)s:%(lun)s" % {
            'host': device['host'],
            'channel': device['channel'],
            'id': device['id'],
            'lun': device['lun']
        })

        scsi_path = ("/sys/bus/scsi/drivers/sd/%(device_id)s" % {
            'device_id': device_id
        })

        size = self.get_device_size(volume_path)
        LOG.debug("Starting size: %s", size)

        # now issue the device rescan
        rescan_path = "%(scsi_path)s/rescan" % {'scsi_path': scsi_path}
        self.echo_scsi_command(rescan_path, "1")
        new_size = self.get_device_size(volume_path)
        LOG.debug("volume size after scsi device rescan %s", new_size)

        scsi_wwn = self.get_scsi_wwn(volume_path)
        mpath_device = self.find_multipath_device_path(scsi_wwn)
        if mpath_device:
            # Force a reconfigure so that resize works
            self.multipath_reconfigure()

            size = self.get_device_size(mpath_device)
            LOG.info(_LI("mpath(%(device)s) current size %(size)s"), {
                'device': mpath_device,
                'size': size
            })
            result = self.multipath_resize_map(scsi_wwn)
            if 'fail' in result:
                msg = (_LI("Multipathd failed to update the size mapping of "
                           "multipath device %(scsi_wwn)s volume %(volume)s") %
                       {
                           'scsi_wwn': scsi_wwn,
                           'volume': volume_path
                       })
                LOG.error(msg)
                return None

            new_size = self.get_device_size(mpath_device)
            LOG.info(_LI("mpath(%(device)s) new size %(size)s"), {
                'device': mpath_device,
                'size': new_size
            })
            return new_size
        else:
            return new_size
Пример #3
0
    def extend_volume(self, volume_path):
        """Signal the SCSI subsystem to test for volume resize.

        This function tries to signal the local system's kernel
        that an already attached volume might have been resized.
        """
        LOG.debug("extend volume %s", volume_path)

        device = self.get_device_info(volume_path)
        LOG.debug("Volume device info = %s", device)
        device_id = ("%(host)s:%(channel)s:%(id)s:%(lun)s" %
                     {'host': device['host'],
                      'channel': device['channel'],
                      'id': device['id'],
                      'lun': device['lun']})

        scsi_path = ("/sys/bus/scsi/drivers/sd/%(device_id)s" %
                     {'device_id': device_id})

        size = self.get_device_size(volume_path)
        LOG.debug("Starting size: %s", size)

        # now issue the device rescan
        rescan_path = "%(scsi_path)s/rescan" % {'scsi_path': scsi_path}
        self.echo_scsi_command(rescan_path, "1")
        new_size = self.get_device_size(volume_path)
        LOG.debug("volume size after scsi device rescan %s", new_size)

        scsi_wwn = self.get_scsi_wwn(volume_path)
        mpath_device = self.find_multipath_device_path(scsi_wwn)
        if mpath_device:
            # Force a reconfigure so that resize works
            self.multipath_reconfigure()

            size = self.get_device_size(mpath_device)
            LOG.info(_LI("mpath(%(device)s) current size %(size)s"),
                     {'device': mpath_device, 'size': size})
            result = self.multipath_resize_map(scsi_wwn)
            if 'fail' in result:
                msg = (_LI("Multipathd failed to update the size mapping of "
                           "multipath device %(scsi_wwn)s volume %(volume)s") %
                       {'scsi_wwn': scsi_wwn, 'volume': volume_path})
                LOG.error(msg)
                return None

            new_size = self.get_device_size(mpath_device)
            LOG.info(_LI("mpath(%(device)s) new size %(size)s"),
                     {'device': mpath_device, 'size': new_size})
            return new_size
        else:
            return new_size
Пример #4
0
    def _get_volume_id(self):
        volname_encoded = urllib.parse.quote(self.volume_name, '')
        volname_double_encoded = urllib.parse.quote(volname_encoded, '')
        LOG.debug(_(
            "Volume name after double encoding is %(volume_name)s."),
            {'volume_name': volname_double_encoded}
        )

        request = (
            "https://%(server_ip)s:%(server_port)s/api/types/Volume/instances"
            "/getByName::%(encoded_volume_name)s" %
            {
                'server_ip': self.server_ip,
                'server_port': self.server_port,
                'encoded_volume_name': volname_double_encoded
            }
        )

        LOG.info(
            _LI("ScaleIO get volume id by name request: %(request)s"),
            {'request': request}
        )

        r = requests.get(request,
                         auth=(self.server_username, self.server_token),
                         verify=False)

        r = self._check_response(r, request)

        volume_id = r.json()
        if not volume_id:
            msg = (_("Volume with name %(volume_name)s wasn't found.") %
                   {'volume_name': self.volume_name})

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

        if r.status_code != self.OK_STATUS_CODE and "errorCode" in volume_id:
            msg = (
                _("Error getting volume id from name %(volume_name)s: "
                  "%(err)s") %
                {'volume_name': self.volume_name, 'err': volume_id['message']}
            )

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

        LOG.info(_LI("ScaleIO volume id is %(volume_id)s."),
                 {'volume_id': volume_id})
        return volume_id
Пример #5
0
    def _find_volume_path(self):
        LOG.info(_LI(
            "Looking for volume %(volume_id)s, maximum tries: %(tries)s"),
            {'volume_id': self.volume_id, 'tries': self.device_scan_attempts}
        )

        # look for the volume in /dev/disk/by-id directory
        by_id_path = self.get_search_path()

        disk_filename = self._wait_for_volume_path(by_id_path)
        full_disk_name = ("%(path)s/%(filename)s" %
                          {'path': by_id_path, 'filename': disk_filename})
        LOG.info(_LI("Full disk name is %(full_path)s"),
                 {'full_path': full_disk_name})
        return full_disk_name
Пример #6
0
    def _wait_for_volume_path(self, path):
        if not os.path.isdir(path):
            msg = (_("ScaleIO volume %(volume_id)s not found at "
                     "expected path.") % {
                         'volume_id': self.volume_id
                     })

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

        disk_filename = None
        filenames = os.listdir(path)
        LOG.info(_LI("Files found in %(path)s path: %(files)s "), {
            'path': path,
            'files': filenames
        })

        for filename in filenames:
            if (filename.startswith("emc-vol")
                    and filename.endswith(self.volume_id)):
                disk_filename = filename
                break

        if not disk_filename:
            msg = (_("ScaleIO volume %(volume_id)s not found.") % {
                'volume_id': self.volume_id
            })
            LOG.debug(msg)
            raise exception.BrickException(message=msg)

        return disk_filename
Пример #7
0
    def _wait_for_volume_path(self, path):
        if not os.path.isdir(path):
            msg = (
                _("ScaleIO volume %(volume_id)s not found at "
                  "expected path.") % {'volume_id': self.volume_id}
                )

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

        disk_filename = None
        filenames = os.listdir(path)
        LOG.info(_LI(
            "Files found in %(path)s path: %(files)s "),
            {'path': path, 'files': filenames}
        )

        for filename in filenames:
            if (filename.startswith("emc-vol") and
                    filename.endswith(self.volume_id)):
                disk_filename = filename
                break

        if not disk_filename:
            msg = (_("ScaleIO volume %(volume_id)s not found.") %
                   {'volume_id': self.volume_id})
            LOG.debug(msg)
            raise exception.BrickException(message=msg)

        return disk_filename
Пример #8
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)
            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)
Пример #9
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 processutils.ProcessExecutionError as e:
            if e.exit_code == 2:
                # NOTE(lyarwood): Workaround bug#1633518 by attempting to use
                # a mangled passphrase to open the device..
                LOG.info(
                    _LI("Unable to open %s with the current passphrase, "
                        "attempting to use a mangled passphrase to open "
                        "the volume."), self.dev_path)
                self._open_volume(self._get_mangled_passphrase(key), **kwargs)

        # 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)
Пример #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 iSCSI volume.
        """
        LOG.info(_LI("Extend volume for %s"), connection_properties)

        volume_paths = self.get_volume_paths(connection_properties)
        LOG.info(_LI("Found paths for volume %s"), volume_paths)
        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_volume_id(self):
        volname_encoded = urllib.parse.quote(self.volume_name, '')
        volname_double_encoded = urllib.parse.quote(volname_encoded, '')
        LOG.debug(_("Volume name after double encoding is %(volume_name)s."),
                  {'volume_name': volname_double_encoded})

        request = (
            "https://%(server_ip)s:%(server_port)s/api/types/Volume/instances"
            "/getByName::%(encoded_volume_name)s" % {
                'server_ip': self.server_ip,
                'server_port': self.server_port,
                'encoded_volume_name': volname_double_encoded
            })

        LOG.info(_LI("ScaleIO get volume id by name request: %(request)s"),
                 {'request': request})

        r = requests.get(request,
                         auth=(self.server_username, self.server_token),
                         verify=False)

        r = self._check_response(r, request)

        volume_id = r.json()
        if not volume_id:
            msg = (_("Volume with name %(volume_name)s wasn't found.") % {
                'volume_name': self.volume_name
            })

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

        if r.status_code != self.OK_STATUS_CODE and "errorCode" in volume_id:
            msg = (_("Error getting volume id from name %(volume_name)s: "
                     "%(err)s") % {
                         'volume_name': self.volume_name,
                         'err': volume_id['message']
                     })

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

        LOG.info(_LI("ScaleIO volume id is %(volume_id)s."),
                 {'volume_id': volume_id})
        return volume_id
Пример #12
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 iSCSI volume.
        """
        LOG.info(_LI("Extend volume for %s"), connection_properties)

        volume_paths = self.get_volume_paths(connection_properties)
        LOG.info(_LI("Found paths for volume %s"), volume_paths)
        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()
Пример #13
0
    def _find_volume_path(self):
        LOG.info(
            _LI("Looking for volume %(volume_id)s, maximum tries: %(tries)s"),
            {
                'volume_id': self.volume_id,
                'tries': self.device_scan_attempts
            })

        # look for the volume in /dev/disk/by-id directory
        by_id_path = self.get_search_path()

        disk_filename = self._wait_for_volume_path(by_id_path)
        full_disk_name = ("%(path)s/%(filename)s" % {
            'path': by_id_path,
            'filename': disk_filename
        })
        LOG.info(_LI("Full disk name is %(full_path)s"),
                 {'full_path': full_disk_name})
        return full_disk_name
Пример #14
0
    def connect_volume(self, connection_properties):
        volume_connected = False
        for (initiator_name, target_portal, target_iqn,
             target_lun) in self._get_all_paths(connection_properties):
            try:
                msg = _LI("Attempting to establish an iSCSI session to "
                          "target %(target_iqn)s on portal %(target_portal)s "
                          "accessing LUN %(target_lun)s using initiator "
                          "%(initiator_name)s.")
                LOG.info(
                    msg,
                    dict(target_portal=target_portal,
                         target_iqn=target_iqn,
                         target_lun=target_lun,
                         initiator_name=initiator_name))
                self._iscsi_utils.login_storage_target(
                    target_lun=target_lun,
                    target_iqn=target_iqn,
                    target_portal=target_portal,
                    auth_username=connection_properties.get('auth_username'),
                    auth_password=connection_properties.get('auth_password'),
                    mpio_enabled=self.use_multipath,
                    initiator_name=initiator_name,
                    ensure_lun_available=False)
                self._iscsi_utils.ensure_lun_available(
                    target_iqn=target_iqn,
                    target_lun=target_lun,
                    rescan_attempts=self.device_scan_attempts,
                    retry_interval=self.device_scan_interval)

                if not volume_connected:
                    (device_number, device_path) = (
                        self._iscsi_utils.get_device_number_and_path(
                            target_iqn, target_lun))
                    volume_connected = True

                if not self.use_multipath:
                    break
            except os_win_exc.OSWinException:
                LOG.exception(_LE("Could not establish the iSCSI session."))

        if not volume_connected:
            raise exception.BrickException(
                _("Could not connect volume %s.") % connection_properties)

        scsi_wwn = self._get_scsi_wwn(device_number)

        device_info = {
            'type': 'block',
            'path': device_path,
            'number': device_number,
            'scsi_wwn': scsi_wwn
        }
        return device_info
Пример #15
0
        def _wait_for_discovery(aoe_path):
            if os.path.exists(aoe_path):
                raise loopingcall.LoopingCallDone

            if waiting_status['tries'] >= self.device_scan_attempts:
                raise exception.VolumeDeviceNotFound(device=aoe_path)

            LOG.info(_LI("AoE volume not yet found at: %(path)s. "
                         "Try number: %(tries)s"),
                     {'path': aoe_device, 'tries': waiting_status['tries']})

            self._aoe_discover()
            waiting_status['tries'] += 1
Пример #16
0
    def mount(self, share, flags=None):
        """Mount the Scality ScaleOut FS.

        The `share` argument is ignored because you can't mount several
        SOFS at the same type on a single server. But we want to keep the
        same method signature for class inheritance purpose.
        """
        if self._mount_base in self._read_mounts():
            LOG.info(_LI('Already mounted: %s'), self._mount_base)
            return
        self._execute('mkdir', '-p', self._mount_base, check_exit_code=0)
        super(ScalityRemoteFsClient, self)._do_mount(
            'sofs', '/etc/sfused.conf', self._mount_base)
Пример #17
0
    def mount(self, share, flags=None):
        """Mount the Scality ScaleOut FS.

        The `share` argument is ignored because you can't mount several
        SOFS at the same type on a single server. But we want to keep the
        same method signature for class inheritance purpose.
        """
        if self._mount_base in self._read_mounts():
            LOG.info(_LI('Already mounted: %s'), self._mount_base)
            return
        self._execute('mkdir', '-p', self._mount_base, check_exit_code=0)
        super(ScalityRemoteFsClient, self)._do_mount(
            'sofs', '/etc/sfused.conf', self._mount_base)
Пример #18
0
    def disconnect_volume(self, connection_properties, device_info):
        """Detach the volume from instance."""
        disco_id = connection_properties['disco_id']
        disco_dev = '/dev/dms%s' % (disco_id)
        LOG.debug("detaching %s", disco_dev)

        if os.path.exists(disco_dev):
            ret = self._send_disco_vol_cmd(self.server_ip, self.server_port, 2,
                                           disco_id)
            if ret is not None:
                msg = _("Detach volume failed")
                raise exception.BrickException(message=msg)
        else:
            LOG.info(_LI("Volume already detached from host"))
Пример #19
0
    def __init__(self, root_helper, driver=None,
                 device_scan_attempts=initiator.DEVICE_SCAN_ATTEMPTS_DEFAULT,
                 *args, **kwargs):
        """Init DISCO connector."""
        super(DISCOConnector, self).__init__(
            root_helper,
            driver=driver,
            device_scan_attempts=device_scan_attempts,
            *args, **kwargs
        )
        LOG.info(_LI("Init DISCO connector"))

        self.server_port = None
        self.server_ip = None
Пример #20
0
    def mount(self, share, flags=None):
        """Mount given share."""
        mount_path = self.get_mount_point(share)

        if mount_path in self._read_mounts():
            LOG.info(_LI('Already mounted: %s'), mount_path)
            return

        self._execute('mkdir', '-p', mount_path, check_exit_code=0)
        if self._mount_type == 'nfs':
            self._mount_nfs(share, mount_path, flags)
        else:
            self._do_mount(self._mount_type, share, mount_path,
                           self._mount_options, flags)
Пример #21
0
    def _get_client_id(self):
        request = (
            "https://%(server_ip)s:%(server_port)s/"
            "api/types/Client/instances/getByIp::%(sdc_ip)s/" %
            {
                'server_ip': self.server_ip,
                'server_port': self.server_port,
                'sdc_ip': self.local_sdc_ip
            }
        )

        LOG.info(_LI("ScaleIO get client id by ip request: %(request)s"),
                 {'request': request})

        r = requests.get(
            request,
            auth=(self.server_username, self.server_token),
            verify=False
        )

        r = self._check_response(r, request)
        sdc_id = r.json()
        if not sdc_id:
            msg = (_("Client with ip %(sdc_ip)s was not found.") %
                   {'sdc_ip': self.local_sdc_ip})
            raise exception.BrickException(message=msg)

        if r.status_code != 200 and "errorCode" in sdc_id:
            msg = (_("Error getting sdc id from ip %(sdc_ip)s: %(err)s") %
                   {'sdc_ip': self.local_sdc_ip, 'err': sdc_id['message']})

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

        LOG.info(_LI("ScaleIO sdc id is %(sdc_id)s."),
                 {'sdc_id': sdc_id})
        return sdc_id
Пример #22
0
    def connect_volume(self, connection_properties):
        volume_connected = False
        for (initiator_name,
             target_portal,
             target_iqn,
             target_lun) in self._get_all_paths(connection_properties):
            try:
                msg = _LI("Attempting to establish an iSCSI session to "
                          "target %(target_iqn)s on portal %(target_portal)s "
                          "accessing LUN %(target_lun)s using initiator "
                          "%(initiator_name)s.")
                LOG.info(msg, dict(target_portal=target_portal,
                                   target_iqn=target_iqn,
                                   target_lun=target_lun,
                                   initiator_name=initiator_name))
                self._iscsi_utils.login_storage_target(
                    target_lun=target_lun,
                    target_iqn=target_iqn,
                    target_portal=target_portal,
                    auth_username=connection_properties.get('auth_username'),
                    auth_password=connection_properties.get('auth_password'),
                    mpio_enabled=self.use_multipath,
                    initiator_name=initiator_name,
                    rescan_attempts=self.device_scan_attempts)

                if not volume_connected:
                    (device_number,
                     device_path) = (
                        self._iscsi_utils.get_device_number_and_path(
                            target_iqn, target_lun))
                    volume_connected = True

                if not self.use_multipath:
                    break
            except os_win_exc.OSWinException:
                LOG.exception(_LE("Could not establish the iSCSI session."))

        if not volume_connected:
            raise exception.BrickException(
                _("Could not connect volume %s.") % connection_properties)

        scsi_wwn = self._get_scsi_wwn(device_number)

        device_info = {'type': 'block',
                       'path': device_path,
                       'number': device_number,
                       'scsi_wwn': scsi_wwn}
        return device_info
Пример #23
0
    def mount(self, share, flags=None):
        """Mount given share."""
        mount_path = self.get_mount_point(share)

        if mount_path in self._read_mounts():
            LOG.info(_LI('Already mounted: %s'), mount_path)
            return

        self._execute('mkdir', '-p', mount_path, check_exit_code=0)
        if self._mount_type == 'nfs':
            self._mount_nfs(share, mount_path, flags)
        elif self._mount_type == 'vzstorage':
            self._mount_vzstorage(share, mount_path, flags)
        else:
            self._do_mount(self._mount_type, share, mount_path,
                           self._mount_options, flags)
Пример #24
0
    def _mount_disco_volume(self, path, volume_id):
        """Send request to mount volume on physical host."""
        LOG.debug("Enter in mount disco volume %(port)s "
                  "and %(ip)s.", {
                      'port': self.server_port,
                      'ip': self.server_ip
                  })

        if not os.path.exists(path):
            ret = self._send_disco_vol_cmd(self.server_ip, self.server_port, 1,
                                           volume_id)
            if ret is not None:
                msg = _("Attach volume failed")
                raise exception.BrickException(message=msg)
        else:
            LOG.info(_LI("Volume already attached to host"))
Пример #25
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
Пример #26
0
    def mount(self, share, flags=None):
        share_norm_path = self._get_share_norm_path(share)
        use_local_path = (self._local_path_for_loopback
                          and self._smbutils.is_local_share(share_norm_path))

        if use_local_path:
            LOG.info(_LI("Skipping mounting local share %(share_path)s."),
                     dict(share_path=share_norm_path))
        else:
            mount_options = " ".join([self._mount_options or '', flags or ''])
            username, password = self._parse_credentials(mount_options)

            if not self._smbutils.check_smb_mapping(share_norm_path):
                self._smbutils.mount_smb_share(share_norm_path,
                                               username=username,
                                               password=password)

        if self._mount_base:
            self._create_mount_point(share, use_local_path)
    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
Пример #28
0
    def get_lv_info(root_helper, vg_name=None, lv_name=None):
        """Retrieve info about LVs (all, in a VG, or a single LV).

        :param root_helper: root_helper to use for execute
        :param vg_name: optional, gathers info for only the specified VG
        :param lv_name: optional, gathers info for only the specified LV
        :returns: List of Dictionaries with LV info

        """

        cmd = LVM.LVM_CMD_PREFIX + [
            'lvs', '--noheadings', '--unit=g', '-o', 'vg_name,name,size',
            '--nosuffix'
        ]
        if lv_name is not None and vg_name is not None:
            cmd.append("%s/%s" % (vg_name, lv_name))
        elif vg_name is not None:
            cmd.append(vg_name)

        try:
            (out, _err) = priv_rootwrap.execute(*cmd,
                                                root_helper=root_helper,
                                                run_as_root=True)
        except putils.ProcessExecutionError as err:
            with excutils.save_and_reraise_exception(reraise=True) as ctx:
                if "not found" in err.stderr or "Failed to find" in err.stderr:
                    ctx.reraise = False
                    LOG.info(
                        _LI("Logical Volume not found when querying "
                            "LVM info. (vg_name=%(vg)s, lv_name=%(lv)s"), {
                                'vg': vg_name,
                                'lv': lv_name
                            })
                    out = None

        lv_list = []
        if out is not None:
            volumes = out.split()
            iterator = moves.zip(*[iter(volumes)] * 3)  # pylint: disable=E1101
            for vg, name, size in iterator:
                lv_list.append({"vg": vg, "name": name, "size": size})

        return lv_list
Пример #29
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)
Пример #30
0
    def _check_response(self,
                        response,
                        request,
                        is_get_request=True,
                        params=None):
        if response.status_code == 401 or response.status_code == 403:
            LOG.info(
                _LI("Token is invalid, "
                    "going to re-login to get a new one"))

            login_request = (
                "https://%(server_ip)s:%(server_port)s/api/login" % {
                    'server_ip': self.server_ip,
                    'server_port': self.server_port
                })

            r = requests.get(login_request,
                             auth=(self.server_username, self.server_password),
                             verify=False)

            token = r.json()
            # repeat request with valid token
            LOG.debug(
                _("Going to perform request %(request)s again "
                  "with valid token"), {'request': request})

            if is_get_request:
                res = requests.get(request,
                                   auth=(self.server_username, token),
                                   verify=False)
            else:
                headers = {'content-type': 'application/json'}
                res = requests.post(request,
                                    data=json.dumps(params),
                                    headers=headers,
                                    auth=(self.server_username, token),
                                    verify=False)

            self.server_token = token
            return res

        return response
Пример #31
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
Пример #32
0
    def _check_response(self, response, request, is_get_request=True,
                        params=None):
        if response.status_code == 401 or response.status_code == 403:
            LOG.info(_LI("Token is invalid, "
                         "going to re-login to get a new one"))

            login_request = (
                "https://%(server_ip)s:%(server_port)s/api/login" %
                {'server_ip': self.server_ip, 'server_port': self.server_port}
            )

            r = requests.get(
                login_request,
                auth=(self.server_username, self.server_password),
                verify=False
            )

            token = r.json()
            # repeat request with valid token
            LOG.debug(_("Going to perform request %(request)s again "
                        "with valid token"), {'request': request})

            if is_get_request:
                res = requests.get(request,
                                   auth=(self.server_username, token),
                                   verify=False)
            else:
                headers = {'content-type': 'application/json'}
                res = requests.post(
                    request,
                    data=json.dumps(params),
                    headers=headers,
                    auth=(self.server_username, token),
                    verify=False
                )

            self.server_token = token
            return res

        return response
Пример #33
0
    def get_lv_info(root_helper, vg_name=None, lv_name=None):
        """Retrieve info about LVs (all, in a VG, or a single LV).

        :param root_helper: root_helper to use for execute
        :param vg_name: optional, gathers info for only the specified VG
        :param lv_name: optional, gathers info for only the specified LV
        :returns: List of Dictionaries with LV info

        """

        cmd = LVM.LVM_CMD_PREFIX + ['lvs', '--noheadings', '--unit=g',
                                    '-o', 'vg_name,name,size', '--nosuffix']
        if lv_name is not None and vg_name is not None:
            cmd.append("%s/%s" % (vg_name, lv_name))
        elif vg_name is not None:
            cmd.append(vg_name)

        try:
            (out, _err) = putils.execute(*cmd,
                                         root_helper=root_helper,
                                         run_as_root=True)
        except putils.ProcessExecutionError as err:
            with excutils.save_and_reraise_exception(reraise=True) as ctx:
                if "not found" in err.stderr or "Failed to find" in err.stderr:
                    ctx.reraise = False
                    LOG.info(_LI("Logical Volume not found when querying "
                                 "LVM info. (vg_name=%(vg)s, lv_name=%(lv)s"),
                             {'vg': vg_name, 'lv': lv_name})
                    out = None

        lv_list = []
        if out is not None:
            volumes = out.split()
            iterator = moves.zip(*[iter(volumes)] * 3)  # pylint: disable=E1101
            for vg, name, size in iterator:
                lv_list.append({"vg": vg, "name": name, "size": size})

        return lv_list
Пример #34
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
Пример #35
0
        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
Пример #36
0
    def mount(self, share, flags=None):
        share = share.replace('/', '\\')
        use_local_path = (self._local_path_for_loopback and
                          self._smbutils.is_local_share(share))

        if use_local_path:
            LOG.info(_LI("Skipping mounting local share %(share_path)s."),
                     dict(share_path=share))
        else:
            mount_options = " ".join(
                [self._mount_options or '', flags or ''])
            username, password = self._parse_credentials(mount_options)

            if not self._smbutils.check_smb_mapping(
                    share):
                self._smbutils.mount_smb_share(share,
                                               username=username,
                                               password=password)

        if self._mount_base:
            share_name = self.get_share_name(share)
            symlink_dest = (share if not use_local_path
                            else self.get_local_share_path(share_name))
            self._create_mount_point(symlink_dest)
Пример #37
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)
            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)
Пример #38
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
Пример #39
0
    def connect_volume(self, connection_properties):
        """Attach the volume to instance_name.

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

        connection_properties for iSCSI must include:
        target_portal(s) - ip and optional port
        target_iqn(s) - iSCSI Qualified Name
        target_lun(s) - LUN id of the volume
        Note that plural keys may be used when use_multipath=True
        """

        device_info = {'type': 'block'}

        # At this point the host_devices may be an empty list
        host_devices, target_props = self._get_potential_volume_paths(
            connection_properties)

        # The /dev/disk/by-path/... node is not always present immediately
        # TODO(justinsb): This retry-with-delay is a pattern, move to utils?
        tries = 0
        # Loop until at least 1 path becomes available
        while all(map(lambda x: not os.path.exists(x), host_devices)):
            if tries >= self.device_scan_attempts:
                raise exception.VolumeDeviceNotFound(device=host_devices)

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

            # The rescan isn't documented as being necessary(?), but it helps
            if self.use_multipath:
                self._rescan_iscsi()
                # We need to refresh the paths as the devices may be empty
                host_devices, target_props = (
                    self._get_potential_volume_paths(connection_properties))
            else:
                if (tries):
                    host_devices = self._get_device_path(target_props)
                self._run_iscsiadm(target_props, ("--rescan", ))

            tries = tries + 1
            if all(map(lambda x: not os.path.exists(x), host_devices)):
                time.sleep(tries**2)
            else:
                break

        if tries != 0:
            LOG.debug(
                "Found iSCSI node %(host_devices)s "
                "(after %(tries)s rescans)", {
                    'host_devices': host_devices,
                    'tries': tries
                })

        # Choose an accessible host device
        host_device = next(dev for dev in host_devices if os.path.exists(dev))

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

        if self.use_multipath:
            (host_device, multipath_id) = (super(ISCSIConnector,
                                                 self)._discover_mpath_device(
                                                     device_wwn,
                                                     connection_properties,
                                                     host_device))
            if multipath_id:
                device_info['multipath_id'] = multipath_id

        device_info['path'] = host_device

        LOG.debug("connect_volume returning %s", device_info)
        return device_info
Пример #40
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
Пример #41
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)
Пример #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
Пример #44
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)
Пример #45
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
Пример #46
0
    def connect_volume(self, connection_properties):
        """Attach the volume to instance_name.

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

        connection_properties for iSCSI must include:
        target_portal(s) - ip and optional port
        target_iqn(s) - iSCSI Qualified Name
        target_lun(s) - LUN id of the volume
        Note that plural keys may be used when use_multipath=True
        """

        device_info = {'type': 'block'}

        # At this point the host_devices may be an empty list
        host_devices, target_props = self._get_potential_volume_paths(
            connection_properties)

        # The /dev/disk/by-path/... node is not always present immediately
        # TODO(justinsb): This retry-with-delay is a pattern, move to utils?
        tries = 0
        # Loop until at least 1 path becomes available
        while all(map(lambda x: not os.path.exists(x), host_devices)):
            if tries >= self.device_scan_attempts:
                raise exception.VolumeDeviceNotFound(device=host_devices)

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

            # The rescan isn't documented as being necessary(?), but it helps
            if self.use_multipath:
                self._rescan_iscsi()
                # We need to refresh the paths as the devices may be empty
                host_devices, target_props = (
                    self._get_potential_volume_paths(connection_properties))
            else:
                if (tries):
                    host_devices = self._get_device_path(target_props)
                self._run_iscsiadm(target_props, ("--rescan",))

            tries = tries + 1
            if all(map(lambda x: not os.path.exists(x), host_devices)):
                time.sleep(tries ** 2)
            else:
                break

        if tries != 0:
            LOG.debug("Found iSCSI node %(host_devices)s "
                      "(after %(tries)s rescans)",
                      {'host_devices': host_devices, 'tries': tries})

        # Choose an accessible host device
        host_device = next(dev for dev in host_devices if os.path.exists(dev))

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

        if self.use_multipath:
            (host_device, multipath_id) = (super(
                ISCSIConnector, self)._discover_mpath_device(
                device_wwn, connection_properties, host_device))
            if multipath_id:
                device_info['multipath_id'] = multipath_id

        device_info['path'] = host_device

        LOG.debug("connect_volume returning %s", device_info)
        return device_info
Пример #47
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