#!/usr/bin/env python

# Multipath test support plugin

import subprocess, sys, socket, struct, time, syslog
import os
import re
import signal
from threading import Timer

import XenAPI, inventory

import XenAPIPlugin

dmsetup = "/sbin/dmsetup"
iptables = "/sbin/iptables"
dd = "/bin/dd"
scli = "/usr/local/bin/scli"
list_domains = "@OPTDIR@/bin/list_domains"
xs = "@OPTDIR@/debug/xs"

def doexec(args, inputtext=None):
    """Execute a subprocess, then return its return code, stdout and stderr"""
    proc = subprocess.Popen(args,stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE,close_fds=True)
    (stdout,stderr) = proc.communicate(inputtext)
    rc = proc.returncode
    return (rc,stdout,stderr)

def get_devmapper_status(session, args):
    (rc,stdout,stderr) = doexec([dmsetup, "status", "--target", "multipath"])
    return stdout

def modify_paths(session, args):
    if args['remove'] == 'true':
        remove = '-A'
    else:
        remove = '-D'

    num_paths = int(args['num_paths'])

    # Get the IP addresses of the paths to remove
    rcs = []
    for i in range(num_paths):
        ip = args['ip%d' % i]
        (rc,stdout,stderr) = doexec([iptables, remove, "OUTPUT", "-d", ip, "-j", "DROP"])
        rcs.append(str(rc))

    # Return the iptables exit codes
    return ','.join(rcs)

def time_data_transfer(session, args):
    dev = "/dev/%s" % args['device']

    # Time the dd
    start_time = time.time()
    (rc,stdout,stderr) = doexec([dd, "if=/dev/zero", "of=%s" % dev, "bs=1K", "count=1024"])
    finish_time = time.time()

    return str(finish_time - start_time)

def get_hba_status(session, args):
    (rc,stdout,stderr) = doexec([scli, "-t"])

    # Extract the WWPNs and online/offline status
    wwpn = None
    wwpns = []

    lines = stdout.splitlines()
    for line in lines:
        if line.find('HBA Instance') == 0:
            fields = line.split(' ')
            wwpn = fields[7]
        if line.find('Status') == 0:
            fields = re.split('\s+', line)
            status = fields[2]
            wwpns.append("%s:%s" % (wwpn, status))

    return ','.join(wwpns)

def check_xenstore_key_exists(key):
    # See if there's a value in XenStore against the given key
    (rc,stdout,stderr) = doexec([xs, "read", key])
    if rc == 0:
        return True
    else:
        return False

def get_domid_for_vm(vm_uuid):
    (rc,stdout,stderr) = doexec([list_domains])

    lines = stdout.split("\n")
    lines.pop() # remove last element (blank line)
    lines.pop(0) # remove first element (column headers)
    
    for line in lines:
        cols = line.split(" | ")
        if cols[1] == vm_uuid:
            return cols[0].strip() # remove start and end whitespace

    return None

def wait_for_iscsi_vm_boot(session, args):
    vm_uuid = args['vm_uuid']
    delay = float(args['delay'])

    # Find the domain ID for the VM with the given UUID
    domid = get_domid_for_vm(vm_uuid)
    if domid == None:
        return 'ERROR: could not find domain with uuid %s' % vm_uuid
    
    # Now wait for the key to appear in XenStore
    key = "/local/domain/%s/data/updated" % domid
    
    finished = False
    if check_xenstore_key_exists(key):
        finished = True
    
    proc = subprocess.Popen([xs,'watch',key],stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE,close_fds=True)
    
    def kill():
        os.kill(proc.pid, signal.SIGKILL)
    
    # Kill the watch if it takes too long
    timer = Timer(delay, kill)
    timer.start()
    
    while not finished:
        # Wait for the watch to fire
        line = proc.stdout.readline()
        if line == '':
            # Then EOF was reached
            return 'ERROR: waiting for xenstore key %s timed out after %.1f secs' % (key,delay)
        if check_xenstore_key_exists(key):
            kill()
	    timer.cancel()
            finished = True
    
    return 'OK'

if __name__ == "__main__":
    XenAPIPlugin.dispatch({"get_devmapper_status": get_devmapper_status,
                           "modify_paths": modify_paths,
			   "time_data_transfer": time_data_transfer,
			   "get_hba_status": get_hba_status,
			   "wait_for_iscsi_vm_boot": wait_for_iscsi_vm_boot})

