

from __future__ import absolute_import

import collections
import itertools
import logging
import operator

from vdsm.common.compat import subprocess

LSBLK = "/usr/bin/lsblk"
LVM = "/usr/sbin/lvm"

log = logging.getLogger("lvmfilter")


MountInfo = collections.namedtuple("MountInfo", "lv,mountpoint,devices")
FilterItem = collections.namedtuple("FilterItem", "action,path")
Advice = collections.namedtuple("Advice", "action,filter")

OVIRT_VG_TAG = "RHAT_storage_domain"


UNNEEDED = "unneeded"

CONFIGURE = "configure"

RECOMMEND = "recommend"


class InvalidFilter(Exception):
    msg = "Invalid LVM filter regex {self.regex!r}: {self.reason}"

    def __init__(self, regex, reason):
        self.regex = regex
        self.reason = reason

    def __str__(self):
        return self.msg.format(self=self)


def find_lvm_mounts():
    log.debug("Looking up mounted logical volumes")

    out = _run([
        LSBLK,
        "--raw",
        "--noheadings",
        "--paths",
        "--inverse",
        "--include", "253",
        "--nodeps",
        "--output", "type,name,mountpoint",
    ])

    rows = [line.rstrip("\n").split(" ") for line in out.splitlines()]

    mounts = []
    for devtype, name, mountpoint in rows:
        if devtype != "lvm" or mountpoint == "":
            continue
        vg_name, tags = vg_info(name)
        if OVIRT_VG_TAG in tags:
            log.debug("Skipping oVirt logical volume %r", name)
            continue
        devices = vg_devices(vg_name)
        mounts.append(MountInfo(name, mountpoint, devices))

    return sorted(mounts)


def build_filter(mounts):
    log.debug("Building filter for %s", mounts)

    devices = set()
    for mnt in mounts:
        for dev in mnt.devices:
            devices.add(dev)

    items = []

    for device in sorted(devices):
        items.append("a|^{}$|".format(device))

    items.append("r|.*|")

    return items


def analyze(current_filter, wanted_filter):

    if not current_filter:
        return Advice(CONFIGURE, wanted_filter)

    if current_filter == wanted_filter:
        return Advice(UNNEEDED, None)

    wanted_items = [parse_item(r) for r in wanted_filter]

    current_items = [parse_item(r) for r in current_filter]

    if current_items == wanted_items:
        return Advice(UNNEEDED, None)

    wanted_items = normalize_items(wanted_items)
    current_items = normalize_items(current_items)

    if current_items == wanted_items:
        return Advice(UNNEEDED, None)

    return Advice(RECOMMEND, wanted_filter)


def normalize_items(items):
    res = []
    for k, g in itertools.groupby(items, operator.attrgetter("action")):
        res.extend(sorted(g))
    return res


def parse_item(regex):
    action = regex[0]
    if action not in ("a", "r"):
        raise InvalidFilter(
            regex,
            "regex must be preceded by 'a' to accept the path, or by 'r' "
            "to reject the path")

    path = regex[1:]
    if path[0] != path[-1]:
        raise InvalidFilter(
            regex,
            "regex must be delimited by a vertical bar '|' (or any "
            "character)")

    path = path[1:-1]
    if not path:
        raise InvalidFilter(regex, "Empty path")

    return FilterItem(action, path)


def format_option(items):
    quoted = ['"' + it + '"' for it in items]
    return "filter = [ {} ]".format(", ".join(quoted))


def vg_info(lv_path):
    log.debug("Looking up information for logical volume %r", lv_path)
    out = _run([
        LVM,
        "lvs",
        "--noheadings",
        "--readonly",
        "--config", 'devices {filter=["a|.*|"]}',
        "--options", "vg_name,vg_tags",
        lv_path
    ])
    out = out.lstrip().rstrip("\n")
    vg_name, vg_tags = out.split(" ", 1)
    vg_tags = vg_tags.split(",")
    return vg_name, vg_tags


def vg_devices(vg_name):
    log.debug("Looking up volume group %r devices", vg_name)
    out = _run([
        LVM,
        "vgs",
        "--noheadings",
        "--readonly",
        "--config", 'devices {filter=["a|.*|"]}',
        "--options", "pv_name",
        vg_name
    ])
    return sorted(line.strip() for line in out.splitlines())


def _run(args):
    log.debug("Running %s", args)
    out = subprocess.check_output(args)
    log.debug("Completed successfuly, out=%r", out)
    return out.decode("utf-8")
