Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
Size: Mime:
# vim: tabstop=4 shiftwidth=4 softtabstop=4

# Copyright (c) 2013 TrilioData, Inc.
# All Rights Reserved.

"""Utilities and helper functions."""

import argparse
import datetime
import glob
import json
import os
import psutil
import random
import shlex
import shutil
import signal
import stat
import subprocess
import time
import tempfile
import logging

from distutils.sysconfig import EXEC_PREFIX
from retrying import retry
from workloadmgr.exception import ProcessExecutionError
from workloadmgr import exception
from workloadmgr import flags
from workloadmgr.openstack.common.gettextutils import _
from workloadmgr.utils import sanitize_message
from workloadmgr.virt.qemuimages import qemu_img_info
import configparser

LOG = logging.getLogger()
LOG.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s | %(levelname)s | %(message)s',
                              '%m-%d-%Y %H:%M:%S')

ISO_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S"
PERFECT_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%f"
FLAGS = flags.FLAGS
TEMPDIR_PREFIX = "/tmp/TrilioVault-FileSearch"
MAX_WAIT_FOR_OVERLAY_FILE = 3
MAX_WAIT_FOR_LSBLK = 3
PROCESS_TIMEOUT = 300
rootwrap_config = '/etc/triliovault-wlm/rootwrap.conf'
config_file = '/etc/triliovault-wlm/triliovault-wlm.conf'

def permissions_to_unix_name(f, st):
    is_dir = 'd' if stat.S_ISDIR(st.st_mode) else \
             'l' if stat.S_ISLNK(st.st_mode) else \
             'p' if stat.S_ISFIFO(st.st_mode) else \
             'b' if stat.S_ISBLK(st.st_mode) else \
             'c' if stat.S_ISCHR(st.st_mode) else \
             's' if stat.S_ISSOCK(st.st_mode) else \
             '-'
    dic = {'7': 'rwx', '6': 'rw-', '5': 'r-x', '4': 'r--', '0': '---'}
    perm = str(oct(st.st_mode)[-3:])
    finfo = is_dir + ''.join(dic.get(x, x) for x in perm)
    finfo += " % 2d" % st.st_nlink
    finfo += " %d %d" % (st.st_uid, st.st_gid)
    finfo += " % 11d " % st.st_size
    timestamp = datetime.datetime.fromtimestamp(st.st_ctime)
    finfo += timestamp.strftime('%b %d %H:%M')
    finfo += " %s" % f
    return finfo


def _subprocess_setup():
    # Python installs a SIGPIPE handler by default. This is usually not what
    # non-Python subprocesses expect.
    signal.signal(signal.SIGPIPE, signal.SIG_DFL)


def execute(*cmd, **kwargs):
    """Helper method to execute command with optional retry.

    If you add a run_as_root=True command, don't forget to add the
    corresponding filter to etc/workloadmgr/rootwrap.d !

    :param cmd:                Passed to subprocess.Popen.
    :param process_input:      Send to opened process.
    :param check_exit_code:    Single bool, int, or list of allowed exit
                               codes.  Defaults to [0].  Raise
                               exception.ProcessExecutionError unless
                               program exits with one of these code.
    :param delay_on_retry:     True | False. Defaults to True. If set to
                               True, wait a short amount of time
                               before retrying.
    :param attempts:           How many times to retry cmd.
    :param run_as_root:        True | False. Defaults to False. If set to True,
                               the command is prefixed by the command specified
                               in the root_helper FLAG.

    :raises exception.Error: on receiving unknown arguments
    :raises exception.ProcessExecutionError:

    :returns: a tuple, (stdout, stderr) from the spawned process, or None if
             the command fails.
    """
    process_input = kwargs.pop('process_input', None)
    check_exit_code = kwargs.pop('check_exit_code', [0])
    ignore_exit_code = False
    if isinstance(check_exit_code, bool):
        ignore_exit_code = not check_exit_code
        check_exit_code = [0]
    elif isinstance(check_exit_code, int):
        check_exit_code = [check_exit_code]
    delay_on_retry = kwargs.pop('delay_on_retry', True)
    attempts = kwargs.pop('attempts', 1)
    run_as_root = kwargs.pop('run_as_root', False)

    if len(kwargs):
        raise exception.Error(_('Got unknown keyword args '
                                'to utils.execute: %r') % kwargs)
    if run_as_root:
        if FLAGS.rootwrap_config is not None or FLAGS.root_helper != 'sudo':
            LOG.deprecated(
                _('The root_helper option (which lets you specify '
                  'a root wrapper different from workloadmgr-rootwrap, '
                  'and defaults to using sudo) is now deprecated. '
                  'You should use the rootwrap_config option '
                  'instead.'))

        if (FLAGS.rootwrap_config is not None):
            cmd = ['sudo', '%s/bin/workloadmgr-rootwrap' % (EXEC_PREFIX),
                   FLAGS.rootwrap_config] + list(cmd)
        elif (rootwrap_config is not None):
            cmd = ['sudo', '%s/bin/workloadmgr-rootwrap' % (EXEC_PREFIX),
                   rootwrap_config] + list(cmd)
        else:
            cmd = shlex.split(FLAGS.root_helper) + list(cmd)
    cmd = list(map(str, cmd))
    sanitize_cmd = sanitize_message(cmd)
    while attempts > 0:
        attempts -= 1
        try:
            LOG.debug(_('Running cmd (subprocess): %s'), sanitize_cmd)
            _PIPE = subprocess.PIPE  # pylint: disable=E1101
            obj = subprocess.Popen(cmd,
                                   stdin=_PIPE,
                                   stdout=_PIPE,
                                   stderr=_PIPE,
                                   close_fds=True,
                                   preexec_fn=_subprocess_setup)
            result = None
            if process_input is not None:
                result = obj.communicate(bytes(process_input, 'utf-8'))
            else:
                try:
                    result = obj.communicate(timeout=PROCESS_TIMEOUT)
                except subprocess.TimeoutExpired:
                    if not obj.poll():
                        return ("Successfully started the process", None)
            obj.stdin.close()  # pylint: disable=E1101

            _returncode = obj.returncode  # pylint: disable=E1101
            if _returncode:
                LOG.debug(_('Result was %s') % _returncode)
                if not ignore_exit_code and _returncode not in check_exit_code:
                    (stdout, stderr) = result
                    if _returncode in [137, -9]: # this exit_code denotes timeout error
                        LOG.error(_('Timeout occured in running cmd (subprocess): %s'), sanitize_cmd)
                    raise exception.ProcessExecutionError(
                        exit_code=_returncode,
                        stdout=stdout,
                        stderr=stderr,
                        cmd=sanitize_cmd)
            return (str(result[0], encoding='utf-8'),
                    str(result[1], encoding='utf-8'))
        except exception.ProcessExecutionError:
            if not attempts:
                raise
            else:
                LOG.debug(_('%r failed. Retrying.'), sanitize_cmd)
                if delay_on_retry:
                    time.sleep(random.SystemRandom().randint(20, 200) / 100.0)
        finally:
            # NOTE(termie): this appears to be necessary to let the subprocess
            #               call clean something up in between calls, without
            #               it two execute calls in a row hangs the second one
            obj.kill()
            time.sleep(0)


def list_devices():
    devices = []
    for dev in os.listdir('/dev'):
        if dev.startswith('nbd'):
            # see if it's a partition
            if len(dev.split('p')) == 1:
                devices.append(dev)

    return devices


def find_available():
    devices = []
    busy = []
    for x in os.popen("cat /proc/partitions |         \
                      grep nbd |                      \
                      awk '{print $4}'").readlines():
        busy.append(x.strip())

    for d in list_devices():
        if d not in busy:
            devices.append(d)

    return devices


def connect(image_file, read_only=True, secret=None, sec_id=None):
    image_file = os.path.realpath(image_file)

    if not os.path.exists(image_file):
        return False
    devices = find_available()

    if len(devices) == 0:
        return False

    dev = devices[0]

    if read_only:
        read_only = '-r'
    else:
        read_only = ''

    if secret:
        cmd = "qemu-nbd %s -c /dev/%s "                          \
              "--object secret,id=%s,data=%s "                   \
              "--image-opts driver=qcow2,encrypt.format=luks,"   \
              "encrypt.key-secret=%s,file.filename=%s"           \
              % (read_only, dev, sec_id,
                 secret, sec_id, image_file)
    else:
        cmd = "qemu-nbd %s -c /dev/%s %s" \
              % (read_only, dev, image_file)
    cmd = cmd.split()
    execute(*cmd, run_as_root=True)

    try:
        # on kernel 5.4.0, partprobe fails to inform kernel about the changes
        cmd = "partprobe /dev/%s" % dev
        cmd = cmd.split()
        execute(*cmd, run_as_root=True)
    except Exception as ex:
        LOG.exception(ex)

    return dev


def disconnect(dev=None):
    umount(dev)

    if dev is None:
        for dev in list_devices():
            disconnect(dev)
    else:
        cmd = 'qemu-nbd -d /dev/%s' % dev
        cmd = cmd.split()
        execute(*cmd, run_as_root=True)


def mount(dev, path=None, fstype=None):
    full_dev_path = '/dev/%s' % (dev)

    if path is None:
        import tempfile
        path = tempfile.mkdtemp()
    cmd = 'timeout -s 9 %s mount -r %s %s' % (PROCESS_TIMEOUT, full_dev_path, path)
    cmd = cmd.split()
    if fstype == 'xfs':
        cmd.extend('-o nouuid'.split())
    try:
        execute(*cmd, run_as_root=True)
    except Exception as ex:
        LOG.exception(ex)
        # retring with '-o nouuid' paramter as sometimes issue in finding out the xfs
        cmd.extend('-o nouuid'.split())
        execute(*cmd, run_as_root=True)

    return path


def find_mount(dev=None):
    if dev is None:
        mounts = []
        for dev in list_devices():
            m = find_mount(dev)
            if m is not None and m not in mounts:
                mounts.append(m)

        return mounts

    else:
        mount = None

        sys_mount = os.popen('mount | \
                             grep %s' % dev).readline().strip().split(' ')
        if len(sys_mount) > 1:
            mount = {
                'dev': sys_mount[0],
                'mount': sys_mount[2],
                'type': sys_mount[3]
            }

        return mount


def umount(dev=None):
    m = find_mount(dev)

    if dev is None:
        for x in m:
            cmd = 'umount %s' % x['mount']
            cmd = cmd.split()
            execute(*cmd, run_as_root=True)
    elif m is not None:
        cmd = 'umount %s' % m['mount']
        cmd = cmd.split()
        execute(*cmd, run_as_root=True)


def create_overlay_file(backup_image, tmpdirname, secret=None, sec_id=None):
    relpath = os.path.relpath(backup_image, os.path.dirname(backup_image))
    overlay_path = os.path.join(tmpdirname, relpath)
    image_info_obj = qemu_img_info(backup_image)
    if secret:
        cmd = ["qemu-img", "create", "-f", "qcow2", "-F", image_info_obj.file_format,
               "--object", "secret,id=%s,data=%s" % (sec_id, secret),
               "-b", 'json:{ "encrypt.key-secret": "%s",        \
                "driver": "qcow2", "file": { "driver": "file",  \
                "filename": "%s" }}' % (sec_id, backup_image),
               "-o", "encrypt.format=luks,encrypt.key-secret=%s"
               % sec_id, overlay_path]
    else:
        cmd = ["qemu-img", "create", "-f",
               "qcow2", "-F", image_info_obj.file_format, "-b", backup_image, overlay_path]

    (out, err) = execute(*cmd, run_as_root=False)

    if err:
        raise Exception(err)

    found_overlay_path = False
    if os.path.exists(overlay_path):
        found_overlay_path = True

    if not found_overlay_path:
        raise Exception('Error: overlay file: {} not created even '\
            'after waiting for: {} seconds'.format(overlay_path, PROCESS_TIMEOUT))

    cmd = ["qemu-img", "info", "--output=json", overlay_path]
    err = None
    image_info = None
    start_time = datetime.datetime.now()
    while not image_info:
        try:
            (out, err) = execute(*cmd, run_as_root=False)
            if (datetime.datetime.now() - start_time).seconds / 60 > MAX_WAIT_FOR_OVERLAY_FILE:
                break
            image_info = json.loads(out)
        except Exception:
            pass

    if not image_info:
        raise Exception("Error: {} command".format(err))

    return overlay_path


def _deactive_logical_volumes(dev):
    cmd = ["lsblk", "-O", "-J", "/dev/%s" % dev]
    devices = None
    err = None
    start_time = datetime.datetime.now()
    while not err and not devices:
        try:
            (out, err) = execute(*cmd, run_as_root=False)
            if err:
                raise Exception(err)
            if (datetime.datetime.now() - start_time).seconds / 60 > MAX_WAIT_FOR_LSBLK:
                break
            devices = json.loads(out).get('blockdevices', [])
        except json.decoder.JSONDecodeError:
            pass

    if not devices:
        raise Exception("Error in running: {} command".format(sanitize_message(cmd)))

    for device in devices:
        for part in device.get('children', []):
            for lv in part.get('children', []):
                if lv['type'] == 'lvm':
                    cmd = ["lvchange", "-a", "n", "/dev/%s/%s" % (lv['name'].split('-')[0], lv['name'].split('-')[1])]
                    execute(*cmd, run_as_root=True)

'''
This method is used to activate the logical volumes
params:
    vg_path: valid full vg path 
'''
def _active_logical_volumes(vg_path):
    cmd = ["lvchange", "-a", "y", vg_path]
    execute(*cmd, run_as_root=True, attempts=3, delay_on_retry=True)
'''
It performs the scans to identify the newly added
PV, VG and LVs.
Then gets the list of LVs and relative informations
(vg path, vg name and associated device path)
And returns the sets of these informations.
'''
def lvtodev():
    # Scan for any logical volumes
    # --cache is used to clear the stale entries
    cmd = ["pvscan", "--cache"]
    (out, err) = execute(*cmd, run_as_root=True)

    cmd = ["pvscan"]
    (out, err) = execute(*cmd, run_as_root=True)

    cmd = ["vgscan"]
    (out, err) = execute(*cmd, run_as_root=True)

    cmd = ["lvscan"]
    (out, err) = execute(*cmd, run_as_root=True)

    cmd = ["lvs", "--noheading", "-o", "path,vg_name,devices"]
    try:
        (out, err) = execute(*cmd, run_as_root=True)
    except Exception as ex:
        raise
    devices = set()
    vgs = set()
    vgs_path = set()

    for line in out.split("\n"):
        if len(line.split()) >= 3:
            vg_path = line.split()[0]
            vg = line.split()[1].split("(")[0]
            dev = line.split()[2].split("(")[0]
            vgs_path.add(vg_path)
            devices.add(dev)
            vgs.add(vg)

    return vgs_path,devices,vgs

'''
Cloning is required for the duplicate VGs,
which will appear whenver multiple snapshots
are provided with same lvs.
`vgimportclone` takes care of updating the metadata of
the devices(e.g UUIDs) so that same lvs can be loaded.
params:
    dev_path: full device path whose VGs are to be cloned
'''
def clone_vgs(dev_path):
    cmd = ["vgimportclone", dev_path]
    try:
        (out, err) = execute(*cmd, run_as_root=True, check_exit_code=5)
    except Exception as ex:
        raise

'''
This method collectively initiates the processes of
scanning the devices and cloning the discovered VGs.
It keeps on this operation until all the devices are scanned 
and their VGs are cloned.
Wait 2^x * 1000 milliseconds between each retry, up to 5 seconds
'''
@retry(wait_exponential_multiplier=1000, wait_exponential_max=5000, stop_max_attempt_number=3)
def rename_logical_vgs():
    processed_devices = set()
    lvm_devices = set()
    lvm_vgs_path, lvm_devices, lvm_vgs = lvtodev()
    while lvm_devices-processed_devices:
        for dev in lvm_devices:
            cmd = "lsblk -O -J %s" % dev
            cmd = cmd.split()
            (out, err) = execute(*cmd, run_as_root=True)
            if err:
                raise Exception(err)
            try:
                dev_output = json.loads(out).get('blockdevices', [])[0]
                if dev_output and dev_output.get('children', []):
                    _dev = dev_output.get('pkname')
                    if _dev:
                        _deactive_logical_volumes(_dev)
            except Exception as ex:
                LOG.exception(ex)

        for dev in lvm_devices:
            clone_vgs(dev)
            processed_devices.add(dev)

        lvm_vgs_path, lvm_devices, lvm_vgs = lvtodev()

    for vg_path in lvm_vgs_path:
        _active_logical_volumes(vg_path)


def clean_up(pid=None, devices=[]):
    # clear_up walks through the list of subdirectories
    # in TrilioVault-FileSearch
    # Identifies the process id from the directory name
    # if the process is not running, cleanup resources for that process.
    # if the process is the input pid, cleanup the resources that belong
    # to that process

    if not (os.path.exists(TEMPDIR_PREFIX) and os.path.isdir(TEMPDIR_PREFIX)):
        return False
    for root in os.listdir(TEMPDIR_PREFIX):
        proc_id = int(root.split("-")[0])
        try:
            os.kill(proc_id, 0)
        except OSError:
            pass
        else:
            if proc_id != pid:
                continue

        try:
            drives = []
            # Umount any mounted parititons or LVs that are mounted
            # to temp directory
            for croot in os.listdir(os.path.join(TEMPDIR_PREFIX, root)):
                with open("/proc/mounts", "r") as f:
                    for line in f:
                        if croot in line:
                            drives.append(line.split()[0])
                            cmd = "umount -f -l %s" % line.split()[0]
                            cmd = cmd.split()
                            execute(*cmd, run_as_root=True)

            # Identify nbd devices based on the overlay files that are in the
            # temporary directory
            # deactivate any logical volumes
            # detach ndb devices
            if devices:
                for dev in devices:
                    try:
                        _deactive_logical_volumes(dev)
                        cmd = "qemu-nbd -d /dev/%s" % dev
                        cmd = cmd.split()
                        execute(*cmd, run_as_root=True)
                    except (psutil.NoSuchProcess, psutil.AccessDenied,
                            psutil.ZombieProcess):
                        pass
            else:
                for proc in psutil.process_iter():
                    try:
                        # Get process name & pid from process object.
                        if len(proc.cmdline()) > 3 and                          \
                           'qemu-nbd' == proc.cmdline()[0] and                  \
                           '-c' == proc.cmdline()[1] and                        \
                           [i for i in proc.cmdline() if root in i]:
                            dev = proc.cmdline()[2].split("/")[2]
                            _deactive_logical_volumes(dev)
                            cmd = "qemu-nbd -d /dev/%s" % dev
                            cmd = cmd.split()
                            execute(*cmd, run_as_root=True)
                    except (psutil.NoSuchProcess, psutil.AccessDenied,
                            psutil.ZombieProcess):
                        pass
            # Remove temp directory and its contents
            if proc_id != pid:
                shutil.rmtree(os.path.join(TEMPDIR_PREFIX, root))
        except Exception as ex:
            LOG.exception(ex)
            pass


def search_pattern(mnt_path, pattern):
    pattern_split = os.path.split(pattern)
    search_mnt_path = os.path.join(mnt_path, pattern_split[0].lstrip('/'))
    s_pattern = pattern_split[1]
    file_stat_fmt = "%n %f %i %d %u %g %s %X %Y %Z %B %b %h"
    stat_output = {}

    search_cmd = ['find', search_mnt_path, '-name',s_pattern, '-maxdepth', 1,
            '-exec', 'stat', '-c', '{}'.format(file_stat_fmt), '{}', '+']
    try:
        (search_out, search_err) = execute(*search_cmd, run_as_root=True)
    except Exception as ex:
        (search_out, search_err) = ex.stdout.decode(), ex.stderr.decode()
        LOG.error(search_err)

    try:
        for fs in search_out.split('\n'):
            if not fs:
                continue
            f_stat = fs.split(' ')
            s_file_path = '/'+os.path.relpath(f_stat[0],mnt_path)
            stat_output[s_file_path] = {'mode': int(f_stat[1],16),
                    'ino': int(f_stat[2]),
                    'dev': int(f_stat[3]),
                    'uid': int(f_stat[4]),
                    'gid': int(f_stat[5]),
                    'size': int(f_stat[6]),
                    'atime': int(f_stat[7]),
                    'mtime': int(f_stat[8]),
                    'ctime': int(f_stat[9]),
                    'blksize': int(f_stat[10]),
                    'blocks': int(f_stat[11]),
                    'nlink': int(f_stat[12]),}
        dev_file_output[part['name']+":"+snap_dev].append(s_file_path)
        dev_file_output[s_file_path] = stat_output[s_file_path]
    except Exception as ex:
        pass

    return stat_output


def main(pattern, backup_images, secret=None, sec_id=None):
    os.makedirs(TEMPDIR_PREFIX, exist_ok=True)
    clean_up()
    json_output = {}
    raw_output = {}
    dev_output = {}
    lvs = {}

    # Make sure nbd driver is loaded with sufficient number of nbd devices
    try:
        cmd = "modprobe nbd nbds_max=128".split()
        execute(*cmd, run_as_root=True)
    except Exception as ex:
        LOG.exception(ex)

    with tempfile.TemporaryDirectory(prefix="%06d-" % os.getpid(),
                                     dir=TEMPDIR_PREFIX) as tmpdirname:
        try:
            # Mount all qcow2 images as ndb devices.
            # it creates an overlay file in the temp directory
            # The temp directory is created for this process
            # all overlay files are mounted using qemu-nbd
            devices = {}
            for img in backup_images:
                snap_list = [i.split('_')[1] for i in img.split('/') if 'snapshot_' in i]
                if snap_list:
                    snap_id = snap_list.pop()
                    if not os.path.exists(img):
                        LOG.debug("Creating nbd device for %s failed as the image does not exist:" % img)
                        raw_output[snap_id] = 'Snapshot VM deleted'
                        continue
                    raw_output[snap_id] = []
                    LOG.debug("Creating nbd device for %s:" % img)
                    try:
                        # create overlay file
                        overlay_path = create_overlay_file(img, tmpdirname,
                                                           secret=secret,
                                                           sec_id=sec_id)
                    except Exception as ex:
                        LOG.exception(ex)
                        LOG.error('Failed to create overlay file for: {}'.format(img))
                        raw_output[snap_id] = 'Failed to create overlay file for: {}'.format(img)
                        continue

                    try:
                        # connect device
                        dev = None
                        dev = connect(overlay_path, read_only="",
                                      secret=secret, sec_id=sec_id)
                        devices[img] = dev
                    except Exception as ex:
                        LOG.exception(ex)
                        LOG.error('Failed to connect: {} overlay file for: {}'.format(overlay_path, img))
                        raw_output[snap_id] = 'Failed to connect: {} overlay file for: {}'.format(overlay_path, img)
                        continue

            # Scan for any logical volumes
            cmd = ["pvscan", "--cache"]
            (out, err) = execute(*cmd, run_as_root=True)

            cmd = ["pvscan"]
            (out, err) = execute(*cmd, run_as_root=True)

            cmd = ["vgscan"]
            (out, err) = execute(*cmd, run_as_root=True)

            cmd = ["lvscan"]
            (out, err) = execute(*cmd, run_as_root=True)

            # for each device, get the partitions and logical volumes
            for img, dev in devices.items():
                cmd = "lsblk -O -J /dev/%s" % dev
                cmd = cmd.split()
                (out, err) = execute(*cmd, run_as_root=True)
                if err:
                    raise Exception(err)
                try:
                    dev_output[img] = json.loads(out).get('blockdevices', [])[0]
                except Exception as ex:
                    LOG.exception(ex)

            # for each device, search files on the disk and on each partition
            for img, device in dev_output.items():
                error_message = ""
                snap_id = [i.split('_')[1] for i in img.split('/') if 'snapshot_' in i].pop()
                snap_dev = [i.split('vm_res_id_').pop() for i in img.split('/') if 'vm_res_id_' in i].pop()

                LOG.debug("Searching in %s:" % device['name'])
                json_output[img] = {}
                json_output[img][device['name']] = []
                try:
                    relpath = os.path.relpath(img, os.path.dirname(img))
                    mnt_path = os.path.join(tmpdirname, relpath+"-mnt")
                    os.mkdir(mnt_path)
                    mount(device['name'], path=mnt_path, fstype=device['fstype'])
                    dev_file_output = {}
                    dev_file_output[device['name']+":"+snap_dev] = []

                    stat_output = {}
                    stat_output = search_pattern(mnt_path, pattern)
                    for k,v in stat_output.items():
                        dev_file_output[device['name']+":"+snap_dev].append(k)
                        dev_file_output[k] = v

                    '''
                    TODO: file stats in unix format
                    search_cmd_ls = ['find', search_mnt_path,'-type', 'f', '-name', s_pattern, '-exec', 'ls', '-l', '{}', '+']
                    (search_out_ls, search_err_ls) = execute(*search_cmd_ls, run_as_root=True)
                    '''

                    raw_output[snap_id].append(dev_file_output)
                    umount(dev)
                except Exception as ex:
                    LOG.debug(ex)
                    error_message = str(ex)

                for part in device.get('children', []):
                    json_output[img][part['name']] = []
                    dev_file_output = {}
                    dev_file_output[part['name']+":"+snap_dev] = []
                    try:
                        relpath = os.path.relpath(img, os.path.dirname(img))
                        mnt_path = os.path.join(tmpdirname,
                                                relpath+part['name']+"-mnt")
                        os.mkdir(mnt_path)
                        mount(part['name'], path=mnt_path, fstype=part['fstype'])

                        stat_output = {}
                        stat_output = search_pattern(mnt_path, pattern)
                        for k,v in stat_output.items():
                            dev_file_output[part['name']+":"+snap_dev].append(k)
                            dev_file_output[k] = v

                        '''
                        TODO: file stats in unix format
                        search_cmd_ls = ['find', search_mnt_path,'-type', 'f', '-name', s_pattern, '-exec', 'ls', '-l', '{}', '+']
                        (search_out_ls, search_err_ls) = execute(*search_cmd_ls, run_as_root=True)

                        '''

                        raw_output[snap_id].append(dev_file_output)
                        os.chdir(cwd)
                        umount(dev)
                    except Exception as ex:
                        LOG.debug(ex)
                        error_message = str(ex)
                if not raw_output[snap_id]:
                    raw_output[snap_id].append("Error in searching files on: {}. Error: {}".format(img, error_message))

            # take care of scanning the LVs and duplicate VGs
            rename_logical_vgs()

            # read the new mapped devices after the VG clonning
            for img, dev in devices.items():
                cmd = "lsblk -O -J /dev/%s" % dev
                cmd = cmd.split()
                (out, err) = execute(*cmd, run_as_root=True)
                if err:
                    raise Exception(err)
                try:
                    dev_output[img] = json.loads(out).get('blockdevices', [])[0]
                except Exception as ex:
                    LOG.exception(ex)

            for img, device in dev_output.items():
                snap_id = [i.split('_')[1] for i in img.split('/') if 'snapshot_' in i].pop()
                snap_dev = [i.split('vm_res_id_').pop() for i in img.split('/') if 'vm_res_id_' in i].pop()
                for part in device.get('children', []):
                    for lv in part.get('children', []):
                        lvs[lv['name']] = lv
                        lv['snap_id'] = snap_id
                        lv['snap_dev'] = snap_dev

            # Search for files on each LV
            json_output['lvs'] = {}

            for name, lv in lvs.items():
                json_output['lvs'][lv['name']] = []
                try:
                    relpath = os.path.relpath(img, os.path.dirname(img))
                    mnt_path = os.path.join(tmpdirname,
                                            relpath+lv['name']+"-mnt")
                    os.mkdir(mnt_path)
                    lvm_dev = "mapper/%s" % lv['name']
                    mount(lvm_dev, path=mnt_path)
                    dev_file_output = {}
                    dev_file_output[lv['name']+":"+lv['snap_dev']] = []

                    stat_output = {}
                    stat_output = search_pattern(mnt_path, pattern)
                    for k,v in stat_output.items():
                        dev_file_output[lv['name']+":"+lv['snap_dev']].append(k)
                        dev_file_output[k] = v

                    '''
                    TODO: file stats in unix format
                    search_cmd_ls = ['find', search_mnt_path,'-type', 'f', '-name', s_pattern, '-exec', 'ls', '-l', '{}', '+']
                    (search_out_ls, search_err_ls) = execute(*search_cmd_ls, run_as_root=True)
                    '''

                    raw_output[lv['snap_id']].append(dev_file_output)
                    umount(lvm_dev)
                except Exception as ex:
                    LOG.debug(ex)
        finally:
            clean_up(pid=os.getpid(), devices=list(devices.values()))
    raw_output = [{k : v} for k,v in raw_output.items()]
    return json_output, raw_output


# Driver Code.
if __name__ == '__main__':
    parser = argparse.ArgumentParser(
                prog='filesearch',
                description='Searches for files in backup images.')
    parser.add_argument('--pattern', required=True,
                        help='path pattern that matches full path of the '
                             'file relative to the partition')
    parser.add_argument('--secret', default=None,
                        help='secret key used for encrypted qcow2 images. '
                             'Required for encrypted qcow2 images')
    parser.add_argument('--sec_id', default=None,
                        help='secret id for encrypted qcow2 images. '
                             'Required for encrypted qcow2 images')
    parser.add_argument('--json', action='store_true',
                        default=False, help='Output is printed in json')
    parser.add_argument('--raw', action='store_true',
                        default=False, help='Output is printed in json of raw stat')
    parser.add_argument('backup-image', nargs='+',
                        help='List of backup images. All backup images '
                             'are either encrypted or plain images. '
                             'If qcow2 are encrypted they must have '
                             'same secret and sec_id')
    parser.add_argument('--rootwrap_config', default='/etc/triliovault-wlm/rootwrap.conf',
                        help='rootwrap config file path '
                             'Required if non-root user runs the script')
    parser.add_argument('--log_dir', default='/tmp/triliovault',
                        help='log file directory path')

    parser.add_argument('--process_timeout', default=300,
                        help='Timeout for waiting for a python subprocess to execute')

    args = parser.parse_args()
    if args.rootwrap_config:
        rootwrap_config = args.rootwrap_config

    PROCESS_TIMEOUT = int(args.process_timeout)
    LOG_DIR = args.log_dir

    os.makedirs(LOG_DIR, exist_ok=True)
    file_handler = logging.handlers.RotatingFileHandler(os.path.join(
        LOG_DIR, 'workloadmgr-filesearch.log'), mode = 'a', maxBytes = 5000000,
        backupCount = 5)
    file_handler.setLevel(logging.DEBUG)
    file_handler.setFormatter(formatter)
    LOG.addHandler(file_handler)


    if args.secret and not args.sec_id:
        print("--sec_id must be specified with --secret option")
        exit(-1)
    json_output, raw_output = main(args.pattern, args.__dict__['backup-image'],
                  secret=args.secret, sec_id=args.sec_id)
    LOG.info('raw_output: {}'.format(raw_output))
    LOG.info('json_output: {}'.format(json_output))
    if args.raw:
        print(json.dumps(raw_output))
    elif args.json:
        print(json.dumps(json_output))
    else:
        for img, parts in json_output.items():
            print("Files in %s:" % img)
            for dev, files in parts.items():
                print("\t Files in partition %s:" % dev)
                for f in files:
                    print("\t\t%s" % f)