#!/usr/bin/python3
#
# Copyright (C) Citrix Systems Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation; version 2.1 only.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
#
# A plugin for synchronizing slaves when something changes on the Master

import sys
import os
import time
import errno
sys.path.append("/opt/xensource/sm/")
import util
import lock
from lvmcache import LVMCache
import scsiutil


def multi(session, args):
    """Perform several actions in one call (to save on round trips)"""
    util.SMlog("on-slave.multi: %s" % args)
    vgName = args["vgName"]
    lvmCache = LVMCache(vgName)
    i = 1
    while True:
        action = args.get("action%d" % i)
        if not action:
            break
        util.SMlog("on-slave.action %d: %s" % (i, action))
        if action == "activate":
            try:
                lvmCache.activate(args["ns%d" % i], args["uuid%d" % i],
                                  args["lvName%d" % i], False)
            except util.CommandException:
                util.SMlog("on-slave.activate failed")
                raise
        elif action == "deactivate":
            try:
                lvmCache.deactivate(args["ns%d" % i], args["uuid%d" % i],
                        args["lvName%d" % i], False)
            except util.SMException:
                util.SMlog("on-slave.deactivate failed")
                raise
        elif action == "deactivateNoRefcount":
            try:
                lvmCache.deactivateNoRefcount(args["lvName%d" % i])
            except util.SMException:
                util.SMlog("on-slave.deactivateNoRefcount failed")
                raise
        elif action == "refresh":
            try:
                lvmCache.activateNoRefcount(args["lvName%d" % i], True)
            except util.CommandException:
                util.SMlog("on-slave.refresh failed")
                raise
        elif action == "cleanupLockAndRefcount":
            from refcounter import RefCounter
            lock.Lock.cleanup(args["uuid%d" % i], args["ns%d" % i])
            RefCounter.reset(args["uuid%d" % i], args["ns%d" % i])
        else:
            raise util.SMException("unrecognized action: %s" % action)
        i += 1
    return str(True)


def _is_open(session, args):
    """Check if VDI <args["vdiUuid"]> is open by a tapdisk on this host"""
    import SRCommand
    import SR
    import CephFSSR
    import EXTSR
    import LargeBlockSR
    import GlusterFSSR
    import LVMSR
    import MooseFSSR
    import NFSSR
    import XFSSR
    import ZFSSR
    import blktap2

    util.SMlog("on-slave.is_open: %s" % args)
    vdiUuid = args["vdiUuid"]
    srRef = args["srRef"]
    srRec = session.xenapi.SR.get_record(srRef)
    srType = srRec["type"]

    # FIXME: ugly hacks to create a VDI object without a real SRCommand to
    # avoid having to refactor the core files
    if srType.startswith("lvm"):
        srType = "lvm"
    cmd = SRCommand.SRCommand(None)
    cmd.driver_info = {"capabilities": None}
    cmd.dconf = {
        "server": None,
        "device": "/HACK",
        # Hack for custom XCP-ng drivers.
        "masterhost": None,  # MooseFS
        "rootpath": None,    # MooseFS
        "serverpath": None,  # CephFS
        "location": "/HACK"  # ZFS
    }
    cmd.params = {"command": None}

    sr_uuid = srRec["uuid"]

    # Another ugly piece of code to load a real Linstor SR, otherwise
    # we can't fetch the VDI path.
    if srType == 'linstor':
        host_ref = util.get_this_host_ref(session)
        sr_ref = session.xenapi.SR.get_by_uuid(sr_uuid)

        pbd = util.find_my_pbd(session, host_ref, sr_ref)
        if pbd is None:
            raise util.SMException('Failed to find Linstor PBD')

        cmd.dconf = session.xenapi.PBD.get_device_config(pbd)

    driver = SR.driver(srType)
    sr = driver(cmd, sr_uuid)

    # session_ref param is required to have a valid session when SR object is created.
    # It's not the case here, so attach the current session object to make LinstorSR happy.
    if srType == 'linstor':
        sr.session = session

    vdi = sr.vdi(vdiUuid)
    tapdisk = blktap2.Tapdisk.find_by_path(vdi.path)
    util.SMlog("Tapdisk for %s: %s" % (vdi.path, tapdisk))
    if tapdisk:
        return "True"
    return "False"


def is_open(session, args):
    try:
        return _is_open(session, args)
    except:
        util.logException("is_open")
        raise

def refresh_lun_size_by_SCSIid(session, args):
    """Refresh the size of LUNs backing the SCSIid on the local node."""
    util.SMlog("on-slave.refresh_lun_size_by_SCSIid(,%s)" % args)
    if scsiutil.refresh_lun_size_by_SCSIid(args['SCSIid']):
        util.SMlog("on-slave.refresh_lun_size_by_SCSIid with %s succeeded"
                   % args)
        return "True"
    else:
        util.SMlog("on-slave.refresh_lun_size_by_SCSIid with %s failed" % args)
        return "False"

def commit_tapdisk(session, args):
    path: str = args["path"]
    vdi_type = args["vdi_type"]
    #TODO: naming should reflect that it does more than coalesceing, like setting volume RW

    def set_RW(path):
        try:
            util.pread2(["lvchange", "-p", "rw", path])
        except:
            pass
    #TODO: need to make children RW. Or we let the relink happen with a refresh on master and hope it doesn't corrupt the disk
    if path.startswith("/dev/"):
        set_RW(path)

    from cowutil import getCowUtil
    cowutil = getCowUtil(vdi_type)
    try:
        parent = cowutil.getParentNoCheck(path)
        if parent.startswith("/dev/"):
            set_RW(parent)
        return str(cowutil.coalesceOnline(path))
    except:
        util.logException("Couldn't coalesce online")
        raise

def commit_cancel(session, args):
    path = args["path"]
    vdi_type = args["vdi_type"]
    from cowutil import getCowUtil
    cowutil = getCowUtil(vdi_type)
    try:
        cowutil.cancelCoalesceOnline(path)
    except:
        return "False"
    return "True"

def cancel_coalesce_master(session, args):
    sr_uuid = args["sr_uuid"]
    vdi_uuid = args["vdi_uuid"]

    # from ipc import IPCFlag
    # flag = IPCFlag(sr_uuid)

    # runningStr = "gc_running_{}".format(vdi_uuid)
    # abortStr = "abort_{}".format(vdi_uuid)

    # if not flag.test(runningStr):
    #     return "True"

    # if not flag.test(abortStr):
    #     flag.set(abortStr)

    # while flag.test(abortStr) or flag.test(runningStr):
    #     time.sleep(1)

    # return "True"

    util.SMlog("Running cancel_coalesce_master plugin: {}".format(vdi_uuid))
    path = "/run/nonpersistent/sm/{}/gc_running_{}".format(sr_uuid, vdi_uuid)

    try:
        with open(path, "r+") as f:
            f.truncate(0)
            f.flush()
            os.fsync(f.fileno())
    except IOError as e:
        if e.errno == errno.ENOENT:
            return "True"
        raise

    while os.path.exists(path):
        time.sleep(1)

    return "True"

def is_openers(session, args):
    path = args["path"]
    openers_pid= util.get_openers_pid(path)
    return str(bool(openers_pid))

if __name__ == "__main__":
    import XenAPIPlugin
    XenAPIPlugin.dispatch({
        "multi": multi,
        "is_open": is_open,
        "refresh_lun_size_by_SCSIid": refresh_lun_size_by_SCSIid,
        "is_openers": is_openers,
        "commit_tapdisk": commit_tapdisk,
        "commit_cancel": commit_cancel,
        "cancel_coalesce_master": cancel_coalesce_master,
        })
