#!/usr/bin/env python3
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.

# Version @VERSION@
#
# A plugin for executing script needed by vmops cloud

import os, sys, time
import XenAPIPlugin
if os.path.exists("/opt/xensource/sm"):
    sys.path.extend(["/opt/xensource/sm/", "/usr/local/sbin/", "/sbin/"])
if os.path.exists("/usr/lib/xcp/sm"):
    sys.path.extend(["/usr/lib/xcp/sm/", "/usr/local/sbin/", "/sbin/"])

import SR, VDI, SRCommand, util, lvutil
from util import CommandException
import vhdutil
import shutil
import lvhdutil
import errno
import subprocess
import xs_errors
import cleanup
import stat
import random
import cloudstack_pluginlib as lib
import logging

lib.setup_logging("/var/log/cloud/cloud.log")

VHDUTIL = "vhd-util"
VHD_PREFIX = 'VHD-'
CLOUD_DIR = '/var/run/cloud_mount'

def echo(fn):
    def wrapped(*v, **k):
        name = fn.__name__
        logging.debug("#### CLOUD enter  %s ####" % name )
        res = fn(*v, **k)
        logging.debug("#### CLOUD exit  %s ####" % name )
        return res
    return wrapped

def getPrimarySRPath(primaryStorageSRUuid, isISCSI):
    if isISCSI:
        primarySRDir = lvhdutil.VG_PREFIX + primaryStorageSRUuid
        return os.path.join(lvhdutil.VG_LOCATION, primarySRDir)
    else:
        return os.path.join(SR.MOUNT_BASE, primaryStorageSRUuid)

def getBackupVHD(UUID):
    return UUID + '.' + SR.DEFAULT_TAP

def getVHD(UUID, isISCSI):
    if isISCSI:
        return VHD_PREFIX + UUID
    else:
        return UUID + '.' + SR.DEFAULT_TAP

def getIsTrueString(stringValue):
    booleanValue = False
    if (stringValue and stringValue == 'true'):
        booleanValue = True
    return booleanValue

def makeUnavailable(uuid, primarySRPath, isISCSI):
    if not isISCSI:
        return
    VHD = getVHD(uuid, isISCSI)
    path = os.path.join(primarySRPath, VHD)
    manageAvailability(path, '-an')
    return

def manageAvailability(path, value):
    if path.__contains__("/var/run/sr-mount"):
        return
    logging.debug("Setting availability of " + path + " to " + value)
    try:
        cmd = ['/usr/sbin/lvchange', value, path]
        util.pread2(cmd)
    except: #CommandException, (rc, cmdListStr, stderr):
        #errMsg = "CommandException thrown while executing: " + cmdListStr + " with return code: " + str(rc) + " and stderr: " + stderr
        errMsg = "Unexpected exception thrown by lvchange"
        logging.debug(errMsg)
        if value == "-ay":
            # Raise an error only if we are trying to make it available.
            # Just warn if we are trying to make it unavailable after the
            # snapshot operation is done.
            raise xs_errors.XenError(errMsg)
    return


def checkVolumeAvailability(path):
    try:
        if not isVolumeAvailable(path):
            # The VHD file is not available on XenSever. The volume is probably
            # inactive or detached.
            # Do lvchange -ay to make it available on XenServer
            manageAvailability(path, '-ay')
    except:
        errMsg = "Could not determine status of ISCSI path: " + path
        logging.debug(errMsg)
        raise xs_errors.XenError(errMsg)

    success = False
    i = 0
    while i < 6:
        i = i + 1
        # Check if the vhd is actually visible by checking for the link
        # set isISCSI to true
        success = isVolumeAvailable(path)
        if success:
            logging.debug("Made vhd: " + path + " available and confirmed that it is visible")
            break

        # Sleep for 10 seconds before checking again.
        time.sleep(10)

    # If not visible within 1 min fail
    if not success:
        logging.debug("Could not make vhd: " +  path + " available despite waiting for 1 minute. Does it exist?")

    return success

def isVolumeAvailable(path):
    # Check if iscsi volume is available on this XenServer.
    status = "0"
    try:
        p = subprocess.Popen(["/bin/bash", "-c", "if [ -L " + path + " ]; then echo 1; else echo 0;fi"], stdout=subprocess.PIPE)
        status = p.communicate()[0].strip("\n")
    except:
        errMsg = "Could not determine status of ISCSI path: " + path
        logging.debug(errMsg)
        raise xs_errors.XenError(errMsg)

    return (status == "1")

def scanParent(path):
    # Do a scan for the parent for ISCSI volumes
    # Note that the parent need not be visible on the XenServer
    parentUUID = ''
    try:
        lvName = os.path.basename(path)
        dirname = os.path.dirname(path)
        vgName = os.path.basename(dirname)
        vhdInfo = vhdutil.getVHDInfoLVM(lvName, lvhdutil.extractUuid, vgName)
        parentUUID = vhdInfo.parentUuid
    except:
        errMsg = "Could not get vhd parent of " + path
        logging.debug(errMsg)
        raise xs_errors.XenError(errMsg)
    return parentUUID

def getParentOfSnapshot(snapshotUuid, primarySRPath, isISCSI):
    snapshotVHD    = getVHD(snapshotUuid, isISCSI)
    snapshotPath   = os.path.join(primarySRPath, snapshotVHD)

    baseCopyUuid = ''
    if isISCSI:
        checkVolumeAvailability(snapshotPath)
        baseCopyUuid = scanParent(snapshotPath)
    else:
        baseCopyUuid = getParent(snapshotPath, isISCSI)

    logging.debug("Base copy of snapshotUuid: " + snapshotUuid + " is " + baseCopyUuid)
    return baseCopyUuid

def getParent(path, isISCSI):
    parentUUID = ''
    try :
        if isISCSI:
            parentUUID = vhdutil.getParent(path, lvhdutil.extractUuid)
        else:
            parentUUID = vhdutil.getParent(path, cleanup.FileVDI.extractUuid)
    except:
        errMsg = "Could not get vhd parent of " + path
        logging.debug(errMsg)
        raise xs_errors.XenError(errMsg)
    return parentUUID

def getVhdParent(session, args):
    logging.debug("getParent with " + str(args))
    try:
        primaryStorageSRUuid      = args['primaryStorageSRUuid']
        snapshotUuid              = args['snapshotUuid']
        isISCSI                   = getIsTrueString(args['isISCSI'])

        primarySRPath = getPrimarySRPath(primaryStorageSRUuid, isISCSI)
        logging.debug("primarySRPath: " + primarySRPath)

        baseCopyUuid = getParentOfSnapshot(snapshotUuid, primarySRPath, isISCSI)

        return  baseCopyUuid
    except:
        logging.debug('getVhdParent', exc_info=True)
        raise xs_errors.XenError("Failed to getVhdParent")
def makedirs(path):
    if not os.path.isdir(path):
        try:
            os.makedirs(path)
        except OSError as e:
            umount(path)
            if os.path.isdir(path):
                return
            errMsg = "OSError while creating " + path + " with errno: " + str(e.errno) + " and strerr: " + e.strerror
            logging.debug(errMsg)
            raise xs_errors.XenError(errMsg)
    return

def umount(localDir):
    try:
        cmd = ['umount', localDir]
        util.pread2(cmd)
    except CommandException:
        errMsg = "CommandException raised while trying to umount " + localDir
        logging.debug(errMsg)
        raise xs_errors.XenError(errMsg)

    logging.debug("Successfully unmounted " + localDir)
    return

@echo
def mountNfsSecondaryStorage(session, args):
    remoteDir = args['remoteDir']
    localDir  = args['localDir']
    nfsVersion = args['nfsVersion']
    logging.debug("mountNfsSecondaryStorage with params: " + str(args))
    mounted = False
    f = open("/proc/mounts", 'r')
    for line in f:
        tokens = line.split(" ")
        if len(tokens) > 2 and tokens[0] == remoteDir and tokens[1] == localDir:
            mounted = True

    if mounted:
        return "true"

    makedirs(localDir)
    options = "soft,tcp,timeo=133,retrans=1"
    if nfsVersion:
        options += ",vers=" + nfsVersion
    try:
        cmd = ['mount', '-o', options, remoteDir, localDir]
        txt = util.pread2(cmd)
    except:
        txt = ''
        errMsg = "Unexpected error while trying to mount " + remoteDir + " to " + localDir
        logging.debug(errMsg)
        raise xs_errors.XenError(errMsg)
    logging.debug("Successfully mounted " + remoteDir + " to " + localDir)

    return "true"

@echo
def umountNfsSecondaryStorage(session, args):
    localDir = args['localDir']
    try:
        cmd = ['umount', localDir]
        util.pread2(cmd)
    except CommandException:
        errMsg = "CommandException raised while trying to umount " + localDir
        logging.debug(errMsg)
        raise xs_errors.XenError(errMsg)
    try:
        os.system("rmdir " + localDir)
    except:
        pass
    logging.debug("Successfully unmounted " + localDir)
    return "true"

@echo
def makeDirectory(session, args):
    path = args['path']
    if not os.path.isdir(path):
        try:
            os.makedirs(path)
        except OSError as e:
            if os.path.isdir(path):
                return "true"
            errMsg = "OSError while creating " + path + " with errno: " + str(e.errno) + " and strerr: " + e.strerror
            logging.debug(errMsg)
            raise xs_errors.XenError(errMsg)
    return "true"

if __name__ == "__main__":
    XenAPIPlugin.dispatch({"getVhdParent":getVhdParent, "mountNfsSecondaryStorage":mountNfsSecondaryStorage,
        "umountNfsSecondaryStorage":umountNfsSecondaryStorage,
        "makeDirectory":makeDirectory})
