Exemplo n.º 1
0
def get_dmsetup_uuid(device):
    """
    get the dm uuid for a specified dmsetup device
    """
    blockdev = block.sysfs_to_devpath(device)
    (out, _) = util.subp(['dmsetup', 'info', blockdev, '-C', '-o', 'uuid',
                          '--noheadings'], capture=True)
    return out.strip()
Exemplo n.º 2
0
def identify_partition(device):
    """
    determine if specified device is a partition
    """
    path = os.path.join(device, 'partition')
    if os.path.exists(path):
        return True

    blockdev = block.sysfs_to_devpath(device)
    if multipath.is_mpath_partition(blockdev):
        return True

    return False
Exemplo n.º 3
0
def shutdown_mdadm(device):
    """
    Shutdown specified mdadm device.
    """

    blockdev = block.sysfs_to_devpath(device)

    LOG.info('Wiping superblock on raid device: %s', device)
    _wipe_superblock(blockdev, exclusive=False)

    md_devs = (mdadm.md_get_devices_list(blockdev) +
               mdadm.md_get_spares_list(blockdev))
    mdadm.set_sync_action(blockdev, action="idle")
    mdadm.set_sync_action(blockdev, action="frozen")
    for mddev in md_devs:
        try:
            mdadm.fail_device(blockdev, mddev)
            mdadm.remove_device(blockdev, mddev)
        except util.ProcessExecutionError as e:
            LOG.debug('Non-fatal error clearing raid array: %s', e.stderr)
            pass

    LOG.debug('using mdadm.mdadm_stop on dev: %s', blockdev)
    mdadm.mdadm_stop(blockdev)

    for mddev in md_devs:
        mdadm.zero_device(mddev)

    # mdadm stop operation is asynchronous so we must wait for the kernel to
    # release resources. For more details see  LP: #1682456
    try:
        for wait in MDADM_RELEASE_RETRIES:
            if mdadm.md_present(block.path_to_kname(blockdev)):
                time.sleep(wait)
            else:
                LOG.debug('%s has been removed', blockdev)
                break

        if mdadm.md_present(block.path_to_kname(blockdev)):
            raise OSError('Timeout exceeded for removal of %s', blockdev)

    except OSError:
        LOG.critical('Failed to stop mdadm device %s', device)
        if os.path.exists('/proc/mdstat'):
            LOG.critical("/proc/mdstat:\n%s", util.load_file('/proc/mdstat'))
        raise
Exemplo n.º 4
0
def shutdown_bcache(device):
    """
    Shut down bcache for specified bcache device

    1. wipe the bcache device contents
    2. extract the cacheset uuid (if cached)
    3. extract the backing device
    4. stop cacheset (if present)
    5. stop the bcacheN device
    6. wait for removal of sysfs path to bcacheN, bcacheN/bcache and
       backing/bcache to go away
    """
    if not device.startswith('/sys/class/block'):
        raise ValueError(
            'Invalid Device (%s): '
            'Device path must start with /sys/class/block/', device)

    # 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.
    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

    LOG.info('Wiping superblock on bcache device: %s', device)
    _wipe_superblock(block.sysfs_to_devpath(device), exclusive=False)

    # collect required information before stopping bcache device
    # UUID from /sys/fs/cache/UUID
    cset_uuid = bcache.get_attached_cacheset(device)
    # /sys/class/block/vdX which is a backing dev of device (bcacheN)
    backing_sysfs = bcache.get_backing_device(block.path_to_kname(device))
    # /sys/class/block/bcacheN/bache
    bcache_sysfs = bcache.sysfs_path(device, strict=False)

    # stop cacheset if one is presennt
    if cset_uuid:
        LOG.info('%s was attached to cacheset %s, stopping cacheset', device,
                 cset_uuid)
        bcache.stop_cacheset(cset_uuid)

        # let kernel settle before the next remove
        udev.udevadm_settle()
        LOG.info('bcache cacheset stopped: %s', cset_uuid)

    # test and log whether the device paths are still present
    to_check = [bcache_sysfs, backing_sysfs]
    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_sysfs, device)
        LOG.debug('bcache backing device checked: %s', backing_sysfs)
    else:
        LOG.info('stopping bcache backing device at: %s', bcache_sysfs)
        bcache.stop_device(bcache_sysfs)
    return
Exemplo n.º 5
0
def wipe_superblock(device):
    """
    Wrapper for block.wipe_volume compatible with shutdown function interface
    """
    blockdev = block.sysfs_to_devpath(device)
    # when operating on a disk that used to have a dos part table with an
    # extended partition, attempting to wipe the extended partition will fail
    try:
        if not block.is_online(blockdev):
            LOG.debug("Device is not online (size=0), so skipping:"
                      " '%s'", blockdev)
            return

        if block.is_extended_partition(blockdev):
            LOG.info(
                "extended partitions do not need wiping, so skipping:"
                " '%s'", blockdev)
            return
    except OSError as e:
        if util.is_file_not_found_exc(e):
            LOG.debug('Device to wipe disappeared: %s', e)
            LOG.debug('/proc/partitions says: %s',
                      util.load_file('/proc/partitions'))

            (parent, partnum) = block.get_blockdev_for_partition(blockdev)
            out, _e = util.subp(['sfdisk', '-d', parent],
                                capture=True,
                                combine_capture=True)
            LOG.debug('Disk partition info:\n%s', out)
            return
        else:
            raise e

    # gather any partitions
    partitions = block.get_sysfs_partitions(device)

    # release zfs member by exporting the pool
    if zfs.zfs_supported() and block.is_zfs_member(blockdev):
        poolname = zfs.device_to_poolname(blockdev)
        # only export pools that have been imported
        if poolname in zfs.zpool_list():
            try:
                zfs.zpool_export(poolname)
            except util.ProcessExecutionError as e:
                LOG.warning('Failed to export zpool "%s": %s', poolname, e)

    if is_swap_device(blockdev):
        shutdown_swap(blockdev)

    # some volumes will be claimed by the bcache layer but do not surface
    # an actual /dev/bcacheN device which owns the parts (backing, cache)
    # The result is that some volumes cannot be wiped while bcache claims
    # the device.  Resolve this by stopping bcache layer on those volumes
    # if present.
    for bcache_path in ['bcache', 'bcache/set']:
        stop_path = os.path.join(device, bcache_path)
        if os.path.exists(stop_path):
            LOG.debug('Attempting to release bcache layer from device: %s:%s',
                      device, stop_path)
            if stop_path.endswith('set'):
                rp = os.path.realpath(stop_path)
                bcache.stop_cacheset(rp)
            else:
                bcache._stop_device(stop_path)

    _wipe_superblock(blockdev)

    # if we had partitions, make sure they've been removed
    if partitions:
        LOG.debug('%s had partitions, issuing partition reread', device)
        retries = [.5, .5, 1, 2, 5, 7]
        for attempt, wait in enumerate(retries):
            try:
                # only rereadpt on wiped device
                block.rescan_block_devices(devices=[blockdev])
                # may raise IOError, OSError due to wiped partition table
                curparts = block.get_sysfs_partitions(device)
                if len(curparts) == 0:
                    return
            except (IOError, OSError):
                if attempt + 1 >= len(retries):
                    raise

            LOG.debug(
                "%s partitions still present, rereading pt"
                " (%s/%s).  sleeping %ss before retry", device, attempt + 1,
                len(retries), wait)
            time.sleep(wait)

    # multipath partitions are separate block devices (disks)
    if multipath.is_mpath_partition(blockdev):
        multipath.remove_partition(blockdev)
    # multipath devices must be hidden to utilize a single member (path)
    elif multipath.is_mpath_device(blockdev):
        mp_id = multipath.find_mpath_id(blockdev)
        if mp_id:
            multipath.remove_map(mp_id)
        else:
            raise RuntimeError('Failed to find multipath id for %s' % blockdev)
Exemplo n.º 6
0
def shutdown_mdadm(device):
    """
    Shutdown specified mdadm device.
    """

    blockdev = block.sysfs_to_devpath(device)

    LOG.info('Discovering raid devices and spares for %s', device)
    md_devs = (mdadm.md_get_devices_list(blockdev) +
               mdadm.md_get_spares_list(blockdev))
    mdadm.set_sync_action(blockdev, action="idle")
    mdadm.set_sync_action(blockdev, action="frozen")

    LOG.info('Wiping superblock on raid device: %s', device)
    try:
        _wipe_superblock(blockdev, exclusive=False)
    except ValueError as e:
        # if the array is not functional, writes to the device may fail
        # and _wipe_superblock will raise ValueError for short writes
        # which happens on inactive raid volumes.  In that case we
        # shouldn't give up yet as we still want to disassemble
        # array and wipe members.  Other errors such as IOError or OSError
        # are unwelcome and will stop deployment.
        LOG.debug(
            'Non-fatal error writing to array device %s, '
            'proceeding with shutdown: %s', blockdev, e)

    LOG.info('Removing raid array members: %s', md_devs)
    for mddev in md_devs:
        try:
            mdadm.fail_device(blockdev, mddev)
            mdadm.remove_device(blockdev, mddev)
        except util.ProcessExecutionError as e:
            LOG.debug('Non-fatal error clearing raid array: %s', e.stderr)
            pass

    LOG.debug('using mdadm.mdadm_stop on dev: %s', blockdev)
    mdadm.mdadm_stop(blockdev)

    LOG.debug('Wiping mdadm member devices: %s' % md_devs)
    for mddev in md_devs:
        mdadm.zero_device(mddev, force=True)

    # mdadm stop operation is asynchronous so we must wait for the kernel to
    # release resources. For more details see  LP: #1682456
    try:
        for wait in MDADM_RELEASE_RETRIES:
            if mdadm.md_present(block.path_to_kname(blockdev)):
                time.sleep(wait)
            else:
                LOG.debug('%s has been removed', blockdev)
                break

        if mdadm.md_present(block.path_to_kname(blockdev)):
            raise OSError('Timeout exceeded for removal of %s', blockdev)

    except OSError:
        LOG.critical('Failed to stop mdadm device %s', device)
        if os.path.exists('/proc/mdstat'):
            LOG.critical("/proc/mdstat:\n%s", util.load_file('/proc/mdstat'))
        raise
Exemplo n.º 7
0
def shutdown_crypt(device):
    """
    Shutdown specified cryptsetup device
    """
    blockdev = block.sysfs_to_devpath(device)
    util.subp(['cryptsetup', 'remove', blockdev], capture=True)
Exemplo n.º 8
0
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