Source code for autotest.client.lv_utils

"""
Utility for taking shapshots from existing logical volumes
or creates such.

:author: Plamen Dimitrov
:copyright: Intra2net AG 2012
@license: GPL v2

:param vg_name: Name of the volume group.
:param lv_name: Name of the logical volume.
:param lv_size: Size of the logical volume as string in the form "#G"
        (for example 30G).
:param lv_snapshot_name: Name of the snapshot with origin the logical
        volume.
:param lv_snapshot_size: Size of the snapshot with origin the logical
        volume also as "#G".
:param ramdisk_vg_size: Size of the ramdisk virtual group.
:param ramdisk_basedir: Base directory for the ramdisk sparse file.
:param ramdisk_sparse_filename: Name of the ramdisk sparse file.

Sample ramdisk params:
    ramdisk_vg_size = "40000"
    ramdisk_basedir = "/tmp"
    ramdisk_sparse_filename = "virtual_hdd"

Sample general params:
    vg_name='autotest_vg',
    lv_name='autotest_lv',
    lv_size='1G',
    lv_snapshot_name='autotest_sn',
    lv_snapshot_size='1G'
The ramdisk volume group size is in MB.
"""

import logging
import re
import os
import shutil
import time

from autotest.client import utils
from autotest.client.shared import error


@error.context_aware
def vg_ramdisk(vg_name, ramdisk_vg_size,
               ramdisk_basedir, ramdisk_sparse_filename):
    """
    Create vg on top of ram memory to speed up lv performance.
    """
    error.context("Creating virtual group on top of ram memory",
                  logging.info)
    vg_size = ramdisk_vg_size
    vg_ramdisk_dir = os.path.join(ramdisk_basedir, vg_name)
    ramdisk_filename = os.path.join(vg_ramdisk_dir,
                                    ramdisk_sparse_filename)

    vg_ramdisk_cleanup(ramdisk_filename,
                       vg_ramdisk_dir, vg_name, "")
    result = ""
    if not os.path.exists(vg_ramdisk_dir):
        os.mkdir(vg_ramdisk_dir)
    try:
        logging.info("Mounting tmpfs")
        result = utils.run("mount -t tmpfs tmpfs " + vg_ramdisk_dir)

        logging.info("Converting and copying /dev/zero")
        cmd = ("dd if=/dev/zero of=" + ramdisk_filename +
               " bs=1M count=1 seek=" + vg_size)
        result = utils.run(cmd, verbose=True)

        logging.info("Finding free loop device")
        result = utils.run("losetup --find", verbose=True)
    except error.CmdError, ex:
        logging.error(ex)
        vg_ramdisk_cleanup(ramdisk_filename,
                           vg_ramdisk_dir, vg_name, "")
        raise ex

    loop_device = result.stdout.rstrip()

    try:
        logging.info("Creating loop device")
        result = utils.run("losetup " + loop_device + " " + ramdisk_filename)
        logging.info("Creating physical volume %s", loop_device)
        result = utils.run("pvcreate " + loop_device)
        logging.info("Creating volume group %s", vg_name)
        result = utils.run("vgcreate " + vg_name + " " + loop_device)
    except error.CmdError, ex:
        logging.error(ex)
        vg_ramdisk_cleanup(ramdisk_filename, vg_ramdisk_dir, vg_name,
                           loop_device)
        raise ex

    logging.info(result.stdout.rstrip())


[docs]def vg_ramdisk_cleanup(ramdisk_filename, vg_ramdisk_dir, vg_name, loop_device): """ Inline cleanup function in case of test error. """ result = utils.run("vgremove " + vg_name, ignore_status=True) if result.exit_status == 0: logging.info(result.stdout.rstrip()) else: logging.debug("%s -> %s", result.command, result.stderr) result = utils.run("pvremove " + loop_device, ignore_status=True) if result.exit_status == 0: logging.info(result.stdout.rstrip()) else: logging.debug("%s -> %s", result.command, result.stderr) for _ in range(10): time.sleep(0.1) result = utils.run("losetup -d " + loop_device, ignore_status=True) if "resource busy" not in result.stderr: if result.exit_status != 0: logging.debug("%s -> %s", result.command, result.stderr) else: logging.info("Loop device %s deleted", loop_device) break if os.path.exists(ramdisk_filename): os.unlink(ramdisk_filename) logging.info("Ramdisk filename %s deleted", ramdisk_filename) utils.run("umount " + vg_ramdisk_dir, ignore_status=True) if result.exit_status == 0: if loop_device != "": logging.info("Loop device %s unmounted", loop_device) else: logging.debug("%s -> %s", result.command, result.stderr) if os.path.exists(vg_ramdisk_dir): try: shutil.rmtree(vg_ramdisk_dir) logging.info("Ramdisk directory %s deleted", vg_ramdisk_dir) except OSError: pass
[docs]def vg_check(vg_name): """ Check whether provided volume group exists. """ cmd = "vgdisplay " + vg_name try: utils.run(cmd) logging.debug("Provided volume group exists: " + vg_name) return True except error.CmdError: return False
[docs]def vg_list(): """ List available volume groups. """ cmd = "vgs --all" vgroups = {} result = utils.run(cmd) lines = result.stdout.strip().splitlines() if len(lines) > 1: columns = lines[0].split() lines = lines[1:] else: return vgroups for line in lines: details = line.split() details_dict = {} index = 0 for column in columns: if re.search("VG", column): vg_name = details[index] else: details_dict[column] = details[index] index += 1 vgroups[vg_name] = details_dict return vgroups
@error.context_aware def vg_create(vg_name, pv_list): """ Create a volume group by using the block special devices """ error.context( "Creating volume group '%s' by using '%s'" % (vg_name, pv_list), logging.info) if vg_check(vg_name): raise error.TestError("Volume group '%s' already exist" % vg_name) cmd = "vgcreate %s %s" % (vg_name, pv_list) result = utils.run(cmd) logging.info(result.stdout.rstrip()) @error.context_aware def vg_remove(vg_name): """ Remove a volume group. """ error.context("Removing volume '%s'" % vg_name, logging.info) if not vg_check(vg_name): raise error.TestError("Volume group '%s' could not be found" % vg_name) cmd = "vgremove -f %s" % vg_name result = utils.run(cmd) logging.info(result.stdout.rstrip())
[docs]def lv_check(vg_name, lv_name): """ Check whether provided logical volume exists. """ cmd = "lvdisplay" result = utils.run(cmd, ignore_status=True) # unstable approach but currently works lvpattern = r"LV Path\s+/dev/" + vg_name + r"/" + lv_name + "\s+" match = re.search(lvpattern, result.stdout.rstrip()) if match: logging.debug("Provided logical volume exists: /dev/" + vg_name + "/" + lv_name) return True else: return False
@error.context_aware def lv_remove(vg_name, lv_name): """ Remove a logical volume. """ error.context("Removing volume /dev/%s/%s" % (vg_name, lv_name), logging.info) if not vg_check(vg_name): raise error.TestError("Volume group could not be found") if not lv_check(vg_name, lv_name): raise error.TestError("Logical volume could not be found") cmd = "lvremove -f " + vg_name + "/" + lv_name result = utils.run(cmd) logging.info(result.stdout.rstrip()) @error.context_aware def lv_create(vg_name, lv_name, lv_size, force_flag=True): """ Create a logical volume in a volume group. The volume group must already exist. """ error.context("Creating original lv to take a snapshot from", logging.info) if not vg_check(vg_name): raise error.TestError("Volume group could not be found") if lv_check(vg_name, lv_name) and not force_flag: raise error.TestError("Logical volume already exists") elif lv_check(vg_name, lv_name) and force_flag: lv_remove(vg_name, lv_name) cmd = ("lvcreate --size " + lv_size + " --name " + lv_name + " " + vg_name) result = utils.run(cmd) logging.info(result.stdout.rstrip()) @error.context_aware def lv_take_snapshot(vg_name, lv_name, lv_snapshot_name, lv_snapshot_size): """ Take a snapshot of the original logical volume. """ error.context("Taking snapshot from original logical volume", logging.info) if not vg_check(vg_name): raise error.TestError("Volume group could not be found") if lv_check(vg_name, lv_snapshot_name): raise error.TestError("Snapshot already exists") if not lv_check(vg_name, lv_name): raise error.TestError("Snapshot's origin could not be found") cmd = ("lvcreate --size " + lv_snapshot_size + " --snapshot " + " --name " + lv_snapshot_name + " /dev/" + vg_name + "/" + lv_name) try: result = utils.run(cmd) except error.CmdError, ex: if ('Logical volume "%s" already exists in volume group "%s"' % (lv_snapshot_name, vg_name) in ex.result_obj.stderr and re.search(re.escape(lv_snapshot_name + " [active]"), utils.run("lvdisplay").stdout)): # the above conditions detect if merge of snapshot was postponed logging.warning(("Logical volume %s is still active! " + "Attempting to deactivate..."), lv_name) lv_reactivate(vg_name, lv_name) result = utils.run(cmd) else: raise ex logging.info(result.stdout.rstrip()) @error.context_aware def lv_revert(vg_name, lv_name, lv_snapshot_name): """ Revert the origin to a snapshot. """ error.context("Reverting original logical volume to snapshot", logging.info) try: if not vg_check(vg_name): raise error.TestError("Volume group could not be found") if not lv_check(vg_name, lv_snapshot_name): raise error.TestError("Snapshot could not be found") if (not lv_check(vg_name, lv_snapshot_name) and not lv_check(vg_name, lv_name)): raise error.TestError("Snapshot and its origin could not be found") if (lv_check(vg_name, lv_snapshot_name) and not lv_check(vg_name, lv_name)): raise error.TestError("Snapshot origin could not be found") cmd = ("lvconvert --merge /dev/%s/%s" % (vg_name, lv_snapshot_name)) result = utils.run(cmd) if ("Merging of snapshot %s will start next activation." % lv_snapshot_name) in result.stdout: raise error.TestError("The logical volume %s is still active" % lv_name) result = result.stdout.rstrip() except error.TestError, ex: # detect if merge of snapshot was postponed # and attempt to reactivate the volume. if (('Snapshot could not be found' in ex and re.search(re.escape(lv_snapshot_name + " [active]"), utils.run("lvdisplay").stdout)) or ("The logical volume %s is still active" % lv_name) in ex): logging.warning(("Logical volume %s is still active! " + "Attempting to deactivate..."), lv_name) lv_reactivate(vg_name, lv_name) result = "Continuing after reactivation" elif 'Snapshot could not be found' in ex: logging.error(ex) result = "Could not revert to snapshot" else: raise ex logging.info(result) @error.context_aware def lv_revert_with_snapshot(vg_name, lv_name, lv_snapshot_name, lv_snapshot_size): """ Perform logical volume merge with snapshot and take a new snapshot. """ error.context("Reverting to snapshot and taking a new one", logging.info) lv_revert(vg_name, lv_name, lv_snapshot_name) lv_take_snapshot(vg_name, lv_name, lv_snapshot_name, lv_snapshot_size) @error.context_aware def lv_reactivate(vg_name, lv_name, timeout=10): """ In case of unclean shutdowns some of the lvs is still active and merging is postponed. Use this function to attempt to deactivate and reactivate all of them to cause the merge to happen. """ try: utils.run("lvchange -an /dev/%s/%s" % (vg_name, lv_name)) time.sleep(timeout) utils.run("lvchange -ay /dev/%s/%s" % (vg_name, lv_name)) time.sleep(timeout) except error.CmdError: logging.error(("Failed to reactivate %s - please, " + "nuke the process that uses it first."), lv_name) raise error.TestError("The logical volume %s is still active" % lv_name)