def remove_partition(devpath, retries=10): LOG.debug('multipath: removing multipath partition: %s', devpath) for _ in range(0, retries): util.subp(['dmsetup', 'remove', devpath], rcs=[0, 1]) udev.udevadm_settle() if not os.path.exists(devpath): return util.wait_for_removal(devpath)
def remove_partition(devpath, retries=10): """ Remove a multipath partition mapping. """ LOG.debug('multipath: removing multipath partition: %s', devpath) for _ in range(0, retries): util.subp(['dmsetup', 'remove', '--force', '--retry', devpath]) udev.udevadm_settle() if not os.path.exists(devpath): return util.wait_for_removal(devpath)
def remove_map(map_id, retries=10): LOG.debug('multipath: removing multipath map: %s', map_id) devpath = '/dev/mapper/%s' % map_id for _ in range(0, retries): util.subp(['multipath', '-f', map_id], rcs=[0, 1]) udev.udevadm_settle() if not os.path.exists(devpath): return util.wait_for_removal(devpath)
def test_wait_for_removal_timesout(self, mock_os, mock_time): path = "/file/to/remove" mock_os.path.exists.return_value = True with self.assertRaises(OSError): util.wait_for_removal(path) self.assertEqual(5, len(mock_os.path.exists.call_args_list)) self.assertEqual(4, len(mock_time.sleep.call_args_list)) mock_os.path.exists.assert_has_calls(5 * [mock.call(path)]) mock_time.sleep.assert_has_calls([ mock.call(1), mock.call(3), mock.call(5), mock.call(7), ])
def _stop_device(device): """ write to sysfs 'stop' and wait for path to be removed The caller needs to ensure that supplied path to the device is a 'bcache' sysfs path on a device. This may be one of the following scenarios: Cacheset: /sys/fs/bcache/<uuid>/ Bcache device: /sys/class/block/bcache0/bcache Backing device /sys/class/block/vdb/bcache Cached device /sys/class/block/nvme0n1p1/bcache/set To support all of these, we append 'stop' to the path and write '1' and then wait for the 'stop' path to be removed. """ bcache_stop = os.path.join(device, 'stop') if not os.path.exists(bcache_stop): LOG.debug('bcache._stop_device: already removed %s', bcache_stop) return LOG.debug('bcache._stop_device: device=%s stop_path=%s', device, bcache_stop) try: util.write_file(bcache_stop, '1', mode=None) except (IOError, OSError) as e: # Note: if we get any exceptions in the above exception classes # it is a result of attempting to write "1" into the sysfs path # The range of errors changes depending on when we race with # the kernel asynchronously removing the sysfs path. Therefore # we log the exception errno we got, but do not re-raise as # the calling process is watching whether the same sysfs path # is being removed; if it fails to go away then we'll have # a log of the exceptions to debug. LOG.debug('Error writing to bcache stop file %s, device removed: %s', bcache_stop, e) finally: util.wait_for_removal(bcache_stop, retries=BCACHE_RETRIES)
def test_wait_for_removal(self, mock_os, mock_time): path = "/file/to/remove" mock_os.path.exists.side_effect = iter([ True, # File is not yet removed False, # File has been removed ]) util.wait_for_removal(path) self.assertEqual(2, len(mock_os.path.exists.call_args_list)) self.assertEqual(1, len(mock_time.sleep.call_args_list)) mock_os.path.exists.assert_has_calls([ mock.call(path), mock.call(path), ]) mock_time.sleep.assert_has_calls([ mock.call(1), ])
def shutdown_bcache(device): """ Shut down bcache for specified bcache device 1. Stop the cacheset that `device` is connected to 2. Stop the 'device' """ if not device.startswith('/sys/class/block'): raise ValueError( 'Invalid Device (%s): ' 'Device path must start with /sys/class/block/', device) LOG.info('Wiping superblock on bcache device: %s', device) _wipe_superblock(block.sysfs_to_devpath(device), exclusive=False) # bcache device removal should be fast but in an extreme # case, might require the cache device to flush large # amounts of data to a backing device. The strategy here # is to wait for approximately 30 seconds but to check # frequently since curtin cannot proceed until devices # cleared. removal_retries = [0.2] * 150 # 30 seconds total bcache_shutdown_message = ('shutdown_bcache running on {} has determined ' 'that the device has already been shut down ' 'during handling of another bcache dev. ' 'skipping'.format(device)) if not os.path.exists(device): LOG.info(bcache_shutdown_message) return # get slaves [vdb1, vdc], allow for slaves to not have bcache dir slave_paths = [ get_bcache_sys_path(k, strict=False) for k in os.listdir(os.path.join(device, 'slaves')) ] # stop cacheset if it exists bcache_cache_sysfs = get_bcache_using_dev(device, strict=False) if not os.path.exists(bcache_cache_sysfs): LOG.info('bcache cacheset already removed: %s', os.path.basename(bcache_cache_sysfs)) else: LOG.info('stopping bcache cacheset at: %s', bcache_cache_sysfs) maybe_stop_bcache_device(bcache_cache_sysfs) try: util.wait_for_removal(bcache_cache_sysfs, retries=removal_retries) except OSError: LOG.info('Failed to stop bcache cacheset %s', bcache_cache_sysfs) raise # let kernel settle before the next remove udev.udevadm_settle() # after stopping cache set, we may need to stop the device # both the dev and sysfs entry should be gone. # we know the bcacheN device is really gone when we've removed: # /sys/class/block/{bcacheN} # /sys/class/block/slaveN1/bcache # /sys/class/block/slaveN2/bcache bcache_block_sysfs = get_bcache_sys_path(device, strict=False) to_check = [device] + slave_paths found_devs = [os.path.exists(p) for p in to_check] LOG.debug('os.path.exists on blockdevs:\n%s', list(zip(to_check, found_devs))) if not any(found_devs): LOG.info('bcache backing device already removed: %s (%s)', bcache_block_sysfs, device) LOG.debug('bcache slave paths checked: %s', slave_paths) return else: LOG.info('stopping bcache backing device at: %s', bcache_block_sysfs) maybe_stop_bcache_device(bcache_block_sysfs) try: # wait for them all to go away for dev in [device, bcache_block_sysfs] + slave_paths: util.wait_for_removal(dev, retries=removal_retries) except OSError: LOG.info('Failed to stop bcache backing device %s', bcache_block_sysfs) raise return
def test_wait_for_removal_missing_path(self): with self.assertRaises(ValueError): util.wait_for_removal(None)