#!/usr/bin/python


import sys

from libsan.host import dm, linux, lvm, snapper
from libsan.host.cmdline import run
from libsan.host.loopdev import create_loopdev, delete_loopdev

import stqe.host.tc

#
# Bugs related:
#   BZ1365555 - Revert workaround for issues with snapper and btrfs subvolume labels
#

TestObj = None

loop_dev = None

vg_name = "vgtest"
pool_name = "test_pool"
thin1_name = "thin1"
brtfs_name = "brtfs_lv"
mnt_point_dev = "/mnt/snapper_test"
cfg_name = "bugtest"


def create_test_lv(filesystem):
    if not filesystem:
        return None

    # On brtfs we should not use thinp
    if filesystem != "btrfs":
        if run(f"lvcreate --thinpool {pool_name} -L 800M {vg_name}") != 0:
            print(f"FAIL: Could not create {pool_name} - {filesystem}")
            return None
        if run(f"lvcreate -V 1G -T {vg_name}/{pool_name} -n {thin1_name}") != 0:
            print(f"FAIL: Could not create {thin1_name} - {filesystem}")
            return None

        return thin1_name
    else:
        if run(f"lvcreate -L 500M  -n {brtfs_name} {vg_name}") != 0:
            print(f"FAIL: Could not create {brtfs_name} - {filesystem}")
            return None

        return brtfs_name


def start_test(filesystem, snapper_debug_mode):
    global TestObj

    print(80 * "#")
    print(f"INFO: Starting test on using snapper on {filesystem} debug mode:{snapper_debug_mode}")
    print(80 * "#")

    if snapper_debug_mode:
        # Wait 60 seconds so snapperd terminates
        linux.sleep(60)
        if not snapper.snapper_enable_debug_mode():
            TestObj.tfail("Could not enable snapper debug on FS(%s)" % filesystem)
            return False

    # create loop device of 900M
    global loop_dev
    loop_dev = create_loopdev(size=900)
    if not loop_dev:
        TestObj.tfail("Could not create loop device")
        return False

    if not lvm.vg_create(vg_name, loop_dev):
        TestObj.tfail('Could not create VG "%s"' % vg_name)
        return False

    print("INFO: Creating LVs")
    test_lv = create_test_lv(filesystem)
    if not test_lv:
        TestObj.tfail(f"Could not LV for FS({filesystem}) with snapper_debug_mode={snapper_debug_mode}")
        return False

    dm.dm_show_table()

    test_device = f"/dev/mapper/{vg_name}-{test_lv}"
    print(f"INFO:Going to create FS({filesystem}) on {test_device}")
    fs_option = ""
    if filesystem == "btrfs":
        # use mixed option as device is not big. more details see:
        # https://bugzilla.redhat.com/show_bug.cgi?id=1374158#c19
        fs_option = "--mixed"
    if run(f"mkfs.{filesystem} {fs_option} {test_device}") != 0:
        TestObj.tfail(
            f"Could not create FS({filesystem}) on {test_device} with snapper_debug_mode={snapper_debug_mode}",
        )
        return False

    if not linux.mkdir(mnt_point_dev):
        TestObj.tfail(
            "Could not create directory %s - FS(%s) with snapper_debug_mode=%s"
            % (mnt_point_dev, filesystem, snapper_debug_mode),
        )
        return False

    if not linux.mount(test_device, mnt_point_dev):
        TestObj.tfail(
            f"Could not mount {mnt_point_dev} - FS({filesystem}) with snapper_debug_mode={snapper_debug_mode}",
        )
        return False

    lvm.lv_show()

    print("INFO: Going create snapper config %s" % cfg_name)
    snap_type = "lvm(%s)" % filesystem
    if filesystem == "btrfs":
        snap_type = "btrfs"
    if not snapper.snapper_create_config(cfg_name, snap_type, mnt_point_dev):
        TestObj.tfail(
            f"Could not create snapper config - FS({filesystem}) with snapper_debug_mode={snapper_debug_mode}",
        )
        return False

    run("ps -ef | grep snapper")

    if not snapper.snapper_list_configs():
        TestObj.tfail(
            "Could not list config FS(%s) on %s with snapper_debug_mode=%s"
            % (filesystem, test_device, snapper_debug_mode),
        )
        return False

    # list current snapshots
    # do not expect any snaphost to be listed
    if not snapper.snapper_list(cfg_name):
        TestObj.tfail(
            "Could not list snapshots FS(%s) on %s with snapper_debug_mode=%s"
            % (filesystem, test_device, snapper_debug_mode),
        )
        return False

    print("INFO: Creating single snapshot")
    pre_num = snapper.snapper_create(cfg_name, "single")
    if not pre_num:
        TestObj.tfail(
            "Could not create SINGLE snapshot FS(%s) on %s with snapper_debug_mode=%s"
            % (filesystem, test_device, snapper_debug_mode),
        )
        return False

    if not linux.mkdir("%s/dir_0" % mnt_point_dev):
        TestObj.tfail(
            "Could not create directory on %s - FS(%s) with snapper_debug_mode=%s"
            % (mnt_point_dev, filesystem, snapper_debug_mode),
        )
        return False
    if run("touch %s/dir_0/file_0" % mnt_point_dev) != 0:
        TestObj.tfail(
            "Could not ecreate file FS(%s) on %s with snapper_debug_mode=%s"
            % (filesystem, test_device, snapper_debug_mode),
        )
        return False

    print("INFO: Creating pre snapshot")
    pre_num = snapper.snapper_create(cfg_name, "pre")
    if not pre_num:
        TestObj.tfail(
            "Could not create PRE snapshot FS(%s) on %s with snapper_debug_mode=%s"
            % (filesystem, test_device, snapper_debug_mode),
        )
        return False

    if not snapper.snapper_list(cfg_name):
        TestObj.tfail(
            "Could not list snapshots FS(%s) on %s with snapper_debug_mode=%s"
            % (filesystem, test_device, snapper_debug_mode),
        )
        return False

    print("INFO: Creating data on %s" % mnt_point_dev)
    if not linux.mkdir("%s/dir_1" % mnt_point_dev):
        TestObj.tfail(
            "Could not create directory on %s - FS(%s) with snapper_debug_mode=%s"
            % (mnt_point_dev, filesystem, snapper_debug_mode),
        )
        return False
    if run("touch %s/dir_1/file_1" % mnt_point_dev) != 0:
        TestObj.tfail(
            "Could not create file FS(%s) on %s with snapper_debug_mode=%s"
            % (filesystem, test_device, snapper_debug_mode),
        )
        return False
    if run('echo "going to create snapshot" >> %s/dir_0/file_0' % mnt_point_dev) != 0:
        TestObj.tfail(
            "Could not edit file FS(%s) on %s with snapper_debug_mode=%s"
            % (filesystem, test_device, snapper_debug_mode),
        )
        return False

    print("INFO: Creating post snapshot")
    post_num = snapper.snapper_create(cfg_name, "post", pre_num=pre_num)
    if not post_num:
        TestObj.tfail(
            "Could not create POST snapshot FS(%s) on %s with snapper_debug_mode=%s"
            % (filesystem, test_device, snapper_debug_mode),
        )
        return False

    if not snapper.snapper_list(cfg_name):
        TestObj.tfail(
            "Could not list snapshots FS(%s) on %s with snapper_debug_mode=%s"
            % (filesystem, test_device, snapper_debug_mode),
        )
        return False

    if not snapper.snapper_status(cfg_name, pre_num, post_num):
        TestObj.tfail(
            "Could not get status for snapshots FS(%s) on %s with snapper_debug_mode=%s"
            % (filesystem, test_device, snapper_debug_mode),
        )
        return False

    if not snapper.snapper_diff(cfg_name, pre_num, post_num):
        TestObj.tfail(
            "Could not get diff for snapshots FS(%s) on %s with snapper_debug_mode=%s"
            % (filesystem, test_device, snapper_debug_mode),
        )
        return False

    print("INFO: Deleting post snapshot")
    if not snapper.snapper_delete(cfg_name, post_num):
        TestObj.tfail(
            "Could not delete POST snapshot FS(%s) on %s with snapper_debug_mode=%s"
            % (filesystem, test_device, snapper_debug_mode),
        )
        return False

    if not snapper.snapper_list(cfg_name):
        TestObj.tfail(
            "Could not list snapshots FS(%s) on %s with snapper_debug_mode=%s"
            % (filesystem, test_device, snapper_debug_mode),
        )
        return False

    print("INFO: Deleting pre snapshot")
    if not snapper.snapper_delete(cfg_name, pre_num):
        TestObj.tfail(
            "Could not delete PRE snapshot FS(%s) on %s with snapper_debug_mode=%s"
            % (filesystem, test_device, snapper_debug_mode),
        )
        return False

    print("INFO: Deleting config")
    if not snapper.snapper_delete_config(cfg_name):
        TestObj.tfail(
            "Could not delete config FS(%s) on %s with snapper_debug_mode=%s"
            % (filesystem, test_device, snapper_debug_mode),
        )
        return False

    # Wait 60 seconds so snapperd terminates
    linux.sleep(60)
    if snapper_debug_mode and not snapper.snapper_disable_debug_mode():
        TestObj.tfail("Could not disable snapper debug on FS(%s)" % filesystem)
        return False

    print(80 * "#")
    TestObj.tpass(f"PASS: Test on FS {filesystem} and snapper_debug_mode={snapper_debug_mode}")
    print(80 * "#")

    return True


def _clean_up():
    global vg_name, loop_dev

    snapper.snapper_delete_config(cfg_name)

    linux.umount(mnt_point_dev)

    # make sure any failed device is removed
    if not lvm.vg_remove(vg_name, force=True):
        TestObj.tfail('Could not delete VG "%s"' % vg_name)

    if loop_dev:
        if not lvm.pv_remove(loop_dev):
            TestObj.tfail('Could not delete PV "%s"' % loop_dev)
        delete_loopdev(loop_dev)


def execute_test():
    global TestObj

    os_version = linux.dist_ver()
    os_name = linux.dist_name()

    if os_name == "RHEL" and os_version < 7:
        TestObj.tskip(f"Test is not supported on {os_name}-{os_version}")
        return

    linux.install_package("snapper")

    filesystems = ["ext4", "xfs"]
    # btrfs is depreciated in RHEL 7.4, but should remain accessible in RHEL 7 series
    # also btrfs was purposely removed from Pegas Kernel (RHEL ALT)
    ret, kernel = run(cmd="uname -r", return_output=True)
    if ret:
        print("WARN: Could not get kernele version, running without btrfs.")
    else:
        version = kernel.split("-")[0].split(".")
        # FIXME: add check for RHEL < 8
        if int(version[0]) <= 4 and int(version[1]) <= 11:
            filesystems.append("btrfs")
    # test using snapperd with and without debug
    dbg_modes = [False, True]

    for fs in filesystems:
        for dbg_mode in dbg_modes:
            _clean_up()

            if not start_test(fs, dbg_mode):
                log_name = f"snapper_{fs}_{dbg_mode}.log"
                run("mv /var/log/snapper.log %s" % log_name)
                TestObj.log_submit(log_name)

    _clean_up()
    return


def main():
    global TestObj

    TestObj = stqe.host.tc.TestClass()

    linux.install_package("snapper")
    linux.install_package("lvm2")

    execute_test()

    if not TestObj.tend():
        print("FAIL: test failed")
        sys.exit(1)

    print("PASS: Test pass")
    sys.exit(0)


main()
