Пример #1
0
 def test_remove_connection_multipath_complete(self, remove_mock, wait_mock,
                                               find_dm_mock,
                                               get_dm_name_mock,
                                               flush_mp_mock,
                                               remove_link_mock,
                                               do_raise, force):
     if do_raise:
         flush_mp_mock.side_effect = Exception
     devices_names = ('sda', 'sdb')
     exc = exception.ExceptionChainer()
     mp_name = self.linuxscsi.remove_connection(devices_names,
                                                is_multipath=True,
                                                force=mock.sentinel.Force,
                                                exc=exc)
     find_dm_mock.assert_called_once_with(devices_names)
     get_dm_name_mock.assert_called_once_with(find_dm_mock.return_value)
     flush_mp_mock.assert_called_once_with(get_dm_name_mock.return_value)
     self.assertEqual(get_dm_name_mock.return_value if do_raise else None,
                      mp_name)
     remove_mock.assert_has_calls([
         mock.call('/dev/sda', mock.sentinel.Force, exc),
         mock.call('/dev/sdb', mock.sentinel.Force, exc)])
     wait_mock.assert_called_once_with(devices_names)
     self.assertEqual(do_raise, bool(exc))
     remove_link_mock.assert_called_once_with(devices_names)
def unlink_root(*links, **kwargs):
    no_errors = kwargs.get('no_errors', False)
    raise_at_end = kwargs.get('raise_at_end', False)
    exc = exception.ExceptionChainer()
    catch_exception = no_errors or raise_at_end

    error_msg = 'Some unlinks failed for %s'
    if os.getuid() == 0:
        for link in links:
            with exc.context(catch_exception, error_msg, links):
                try:
                    os.unlink(link)
                except OSError as exc:
                    # Ignore file doesn't exist errors
                    if exc.errno != errno.ENOENT:
                        raise
    else:
        with exc.context(catch_exception, error_msg, links):
            # Ignore file doesn't exist errors
            putils.execute('rm',
                           *links,
                           run_as_root=True,
                           check_exit_code=(0, errno.ENOENT),
                           root_helper=ROOT_HELPER)

    if not no_errors and raise_at_end and exc:
        raise exc
Пример #3
0
    def _cleanup_connection(self, connection_properties, ips_iqns_luns=None,
                            force=False, ignore_errors=False):
        """Cleans up connection flushing and removing devices and multipath.

        :param connection_properties: The dictionary that describes all
                                      of the target volume attributes.
        :type connection_properties: dict that must include:
                                     target_portal(s) - IP and optional port
                                     target_iqn(s) - iSCSI Qualified Name
                                     target_lun(s) - LUN id of the volume
        :param ips_iqns_luns: Use this list of tuples instead of information
                              from the connection_properties.
        :param force: Whether to forcefully disconnect even if flush fails.
        :type force: bool
        :param ignore_errors: When force is True, this will decide whether to
                              ignore errors or raise an exception once finished
                              the operation.  Default is False.
        :type ignore_errors: bool
        """
        exc = exception.ExceptionChainer()
        try:
            devices_map = self._get_connection_devices(connection_properties,
                                                       ips_iqns_luns)
        except exception.TargetPortalsNotFound as exc:
            # When discovery sendtargets failed on connect there is no
            # information in the discoverydb, so there's nothing to clean.
            LOG.debug('Skipping cleanup %s', exc)
            return

        # Remove devices and multipath from this connection
        remove_devices = set()
        for remove, __ in devices_map.values():
            remove_devices.update(remove)
        multipath_name = self._linuxscsi.remove_connection(remove_devices,
                                                           self.use_multipath,
                                                           force, exc)

        # Disconnect sessions and remove nodes that are left without devices
        disconnect = [conn for conn, (__, keep) in devices_map.items()
                      if not keep]
        self._disconnect_connection(connection_properties, disconnect, force,
                                    exc)

        # If flushing the multipath failed before, try now after we have
        # removed the devices and we may have even logged off (only reaches
        # here with multipath_name if force=True).
        if multipath_name:
            LOG.debug('Flushing again multipath %s now that we removed the '
                      'devices.', multipath_name)
            self._linuxscsi.flush_multipath_device(multipath_name)

        if exc:
            LOG.warning('There were errors removing %s, leftovers may remain '
                        'in the system', remove_devices)
            if not ignore_errors:
                raise exc
Пример #4
0
def unlink_root(*links, **kwargs):
    no_errors = kwargs.get('no_errors', False)
    raise_at_end = kwargs.get('raise_at_end', False)
    exc = exception.ExceptionChainer()
    catch_exception = no_errors or raise_at_end
    for link in links:
        with exc.context(catch_exception, 'Unlink failed for %s', link):
            putils.execute('unlink', link, run_as_root=True,
                           root_helper=ROOT_HELPER)
    if not no_errors and raise_at_end and exc:
        raise exc
Пример #5
0
 def test_remove_connection_singlepath(self, remove_mock, wait_mock,
                                       remove_link_mock):
     devices_names = ('sda', 'sdb')
     exc = exception.ExceptionChainer()
     self.linuxscsi.remove_connection(devices_names, is_multipath=False,
                                      force=mock.sentinel.Force,
                                      exc=exc)
     remove_mock.assert_has_calls(
         [mock.call('/dev/sda', mock.sentinel.Force, exc),
          mock.call('/dev/sdb', mock.sentinel.Force, exc)])
     wait_mock.assert_called_once_with(devices_names)
     remove_link_mock.assert_called_once_with(devices_names)
Пример #6
0
    def test_remove_scsi_device_force(self, exists_mock, flush_mock,
                                      echo_mock):
        """With force we'll always call delete even if flush fails."""
        exc = exception.ExceptionChainer()
        flush_mock.side_effect = Exception()
        echo_mock.side_effect = Exception()
        device = '/dev/sdc'

        self.linuxscsi.remove_scsi_device(device, force=True, exc=exc)
        # The context manager has caught the exceptions
        self.assertTrue(exc)
        flush_mock.assert_called_once_with(device)
        echo_mock.assert_called_once_with('/sys/block/sdc/device/delete', '1')
Пример #7
0
    def remove_connection(self, devices_names, force=False, exc=None,
                          path_used=None, was_multipath=False):
        """Remove LUNs and multipath associated with devices names.

        :param devices_names: Iterable with real device names ('sda', 'sdb')
        :param force: Whether to forcefully disconnect even if flush fails.
        :param exc: ExceptionChainer where to add exceptions if forcing
        :param path_used: What path was used by Nova/Cinder for I/O
        :param was_multipath: If the path used for I/O was a multipath
        :returns: Multipath device map name if found and not flushed
        """
        if not devices_names:
            return
        exc = exception.ExceptionChainer() if exc is None else exc

        multipath_dm = self.find_sysfs_multipath_dm(devices_names)
        LOG.debug('Removing %(type)s devices %(devices)s',
                  {'type': 'multipathed' if multipath_dm else 'single pathed',
                   'devices': ', '.join(devices_names)})
        multipath_name = multipath_dm and self.get_dm_name(multipath_dm)
        if multipath_name:
            with exc.context(force, 'Flushing %s failed', multipath_name):
                self.flush_multipath_device(multipath_name)
                multipath_name = None
            multipath_running = True
        else:
            multipath_running = self.is_multipath_running(
                enforce_multipath=False, root_helper=self._root_helper)

        for device_name in devices_names:
            dev_path = '/dev/' + device_name
            if multipath_running:
                # Recent multipathd doesn't remove path devices in time when
                # it receives mutiple udev events in a short span, so here we
                # tell multipathd to remove the path device immediately.
                # Even if this step fails, later removing an iscsi device
                # triggers a udev event and multipathd can remove the path
                # device based on the udev event
                self.multipath_del_path(dev_path)
            flush = self.requires_flush(dev_path, path_used, was_multipath)
            self.remove_scsi_device(dev_path, force, exc, flush)

        # Wait until the symlinks are removed
        with exc.context(force, 'Some devices remain from %s', devices_names):
            try:
                self.wait_for_volumes_removal(devices_names)
            finally:
                # Since we use /dev/disk/by-id/scsi- links to get the wwn we
                # must ensure they are always removed.
                self._remove_scsi_symlinks(devices_names)
        return multipath_name
Пример #8
0
    def remove_connection(self,
                          devices_names,
                          is_multipath,
                          force=False,
                          exc=None,
                          path_used=None,
                          was_multipath=False):
        """Remove LUNs and multipath associated with devices names.

        :param devices_names: Iterable with real device names ('sda', 'sdb')
        :param is_multipath: Whether this is a multipath connection or not
        :param force: Whether to forcefully disconnect even if flush fails.
        :param exc: ExceptionChainer where to add exceptions if forcing
        :param path_used: What path was used by Nova/Cinder for I/O
        :param was_multipath: If the path used for I/O was a multipath
        :returns: Multipath device map name if found and not flushed
        """
        if not devices_names:
            return
        multipath_name = None
        exc = exception.ExceptionChainer() if exc is None else exc
        LOG.debug(
            'Removing %(type)s devices %(devices)s', {
                'type': 'multipathed' if is_multipath else 'single pathed',
                'devices': ', '.join(devices_names)
            })

        if is_multipath:
            multipath_dm = self.find_sysfs_multipath_dm(devices_names)
            multipath_name = multipath_dm and self.get_dm_name(multipath_dm)
            if multipath_name:
                with exc.context(force, 'Flushing %s failed', multipath_name):
                    self.flush_multipath_device(multipath_name)
                    multipath_name = None

        for device_name in devices_names:
            dev_path = '/dev/' + device_name
            flush = self.requires_flush(dev_path, path_used, was_multipath)
            self.remove_scsi_device(dev_path, force, exc, flush)

        # Wait until the symlinks are removed
        with exc.context(force, 'Some devices remain from %s', devices_names):
            try:
                self.wait_for_volumes_removal(devices_names)
            finally:
                # Since we use /dev/disk/by-id/scsi- links to get the wwn we
                # must ensure they are always removed.
                self._remove_scsi_symlinks(devices_names)
        return multipath_name
Пример #9
0
    def remove_scsi_device(self, device, force=False, exc=None,
                           flush=True):
        """Removes a scsi device based upon /dev/sdX name."""
        path = "/sys/block/%s/device/delete" % device.replace("/dev/", "")
        if os.path.exists(path):
            exc = exception.ExceptionChainer() if exc is None else exc
            if flush:
                # flush any outstanding IO first
                with exc.context(force, 'Flushing %s failed', device):
                    self.flush_device_io(device)

            LOG.debug("Remove SCSI device %(device)s with %(path)s",
                      {'device': device, 'path': path})
            with exc.context(force, 'Removing %s failed', device):
                self.echo_scsi_command(path, "1")
Пример #10
0
    def detach(self, force=False, ignore_errors=False, exc=None):
        if not exc:
            exc = brick_exception.ExceptionChainer()
        with exc.context(force, 'Disconnect failed'):
            self.connector.disconnect_volume(self.conn_info['data'],
                                             self.device,
                                             force=force,
                                             ignore_errors=ignore_errors)
        if not exc or ignore_errors:
            if self._volume:
                self.volume.local_attach = None
            self.device = None
            self.save()
            self._connector = None

        if exc and not ignore_errors:
            raise exc
Пример #11
0
    def detach(self, force=False, ignore_errors=False):
        if not self.local_attach:
            raise exception.NotLocal(self.id)
        exc = brick_exception.ExceptionChainer()

        conn = self.local_attach
        try:
            conn.detach(force, ignore_errors, exc)
        except Exception:
            if not force:
                raise

        with exc.context(force, 'Unable to disconnect'):
            conn.disconnect(force)

        if exc and not ignore_errors:
            raise exc
Пример #12
0
 def test_remove_connection_multipath_fail(self, remove_mock, wait_mock,
                                           find_dm_mock, get_dm_name_mock,
                                           flush_mp_mock, remove_link_mock):
     flush_mp_mock.side_effect = exception.ExceptionChainer
     devices_names = ('sda', 'sdb')
     exc = exception.ExceptionChainer()
     self.assertRaises(exception.ExceptionChainer,
                       self.linuxscsi.remove_connection,
                       devices_names, is_multipath=True,
                       force=False, exc=exc)
     find_dm_mock.assert_called_once_with(devices_names)
     get_dm_name_mock.assert_called_once_with(find_dm_mock.return_value)
     flush_mp_mock.assert_called_once_with(get_dm_name_mock.return_value)
     remove_mock.assert_not_called()
     wait_mock.assert_not_called()
     remove_link_mock.assert_not_called()
     self.assertTrue(bool(exc))
Пример #13
0
def unlink_root(*links, **kwargs):
    no_errors = kwargs.get('no_errors', False)
    raise_at_end = kwargs.get('raise_at_end', False)
    exc = exception.ExceptionChainer()
    catch_exception = no_errors or raise_at_end

    error_msg = 'Some unlinks failed for %s'
    if os.getuid() == 0:
        for link in links:
            with exc.context(catch_exception, error_msg, links):
                os.unlink(link)
    else:
        with exc.context(catch_exception, error_msg, links):
            putils.execute('rm', *links, run_as_root=True,
                           root_helper=ROOT_HELPER)

    if not no_errors and raise_at_end and exc:
        raise exc
Пример #14
0
    def test_remove_connection_singlepath_used(self, remove_mock, wait_mock,
                                               remove_link_mock):
        devices_names = ('sda', 'sdb')
        exc = exception.ExceptionChainer()

        # realpath was mocked on test setup
        with mock.patch('os.path.realpath', side_effect=self.realpath):
            self.linuxscsi.remove_connection(devices_names,
                                             is_multipath=True,
                                             force=mock.sentinel.Force,
                                             exc=exc,
                                             path_used='/dev/sdb',
                                             was_multipath=False)
        remove_mock.assert_has_calls([
            mock.call('/dev/sda', mock.sentinel.Force, exc, False),
            mock.call('/dev/sdb', mock.sentinel.Force, exc, True)
        ])
        wait_mock.assert_called_once_with(devices_names)
        remove_link_mock.assert_called_once_with(devices_names)
Пример #15
0
def unlink_root(*links, **kwargs):
    """Unlink system links with sys admin privileges.

    By default it will raise an exception if a link does not exist and stop
    unlinking remaining links.

    This behavior can be modified passing optional parameters `no_errors` and
    `raise_at_end`.

    :param no_errors: Don't raise an exception on error
    "param raise_at_end: Don't raise an exception on first error, try to
                         unlink all links and then raise a ChainedException
                         with all the errors that where found.
    """
    no_errors = kwargs.get('no_errors', False)
    raise_at_end = kwargs.get('raise_at_end', False)
    exc = exception.ExceptionChainer()
    catch_exception = no_errors or raise_at_end
    for link in links:
        with exc.context(catch_exception, 'Unlink failed for %s', link):
            os.unlink(link)
    if not no_errors and raise_at_end and exc:
        raise exc
Пример #16
0
    def disconnect_volume(self,
                          connection_properties,
                          device_info,
                          force=False,
                          ignore_errors=False):
        """Detach and flush the volume.

        :param connection_properties: The dictionary that describes all
                                      of the target volume attributes.
               connection_properties must include:
               device_path - path to the volume to be connected
        :type connection_properties: dict

        :param device_info: historical difference, but same as connection_props
        :type device_info: dict

        """
        if connection_properties.get('vol_uuid'):  # compatibility
            return self._disconnect_volume_replicated(
                connection_properties,
                device_info,
                force=force,
                ignore_errors=ignore_errors)

        conn_nqn = connection_properties['nqn']
        if device_info and device_info.get('path'):
            device_path = device_info.get('path')
        else:
            device_path = connection_properties['device_path'] or ''
        current_nvme_devices = self._get_nvme_devices()
        if device_path not in current_nvme_devices:
            LOG.warning(
                "Trying to disconnect device %(device_path)s with "
                "subnqn %(conn_nqn)s that is not connected.", {
                    'device_path': device_path,
                    'conn_nqn': conn_nqn
                })
            return

        exc = exception.ExceptionChainer()
        with exc.context(force, 'Flushing %s failed', device_path):
            self._linuxscsi.flush_device_io(device_path)

        LOG.debug(
            "Trying to disconnect from device %(device_path)s with "
            "subnqn %(conn_nqn)s", {
                'device_path': device_path,
                'conn_nqn': conn_nqn
            })
        cmd = ['nvme', 'disconnect', '-n', conn_nqn]
        with exc.context(
                force, "Failed to disconnect from NVMe nqn "
                "%(conn_nqn)s with device_path %(device_path)s", {
                    'conn_nqn': conn_nqn,
                    'device_path': device_path
                }):
            self._execute(*cmd,
                          root_helper=self._root_helper,
                          run_as_root=True)

        if exc:
            LOG.warning(
                'There were errors removing %s, leftovers may remain '
                'in the system', device_path)
            if not ignore_errors:
                raise exc