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
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
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
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)
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')
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
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
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")
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
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
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))
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
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)
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
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