#!/usr/bin/env python
#Copyright (c) Citrix Systems Inc.
#All rights reserved.
#
#Redistribution and use in source and binary forms, with or without modification,
#are permitted provided that the following conditions are met:
#
#1. Redistributions of source code must retain the above copyright notice, this
#list of conditions and the following disclaimer.
#
#2. Redistributions in binary form must reproduce the above copyright notice,
#this list of conditions and the following disclaimer in the documentation and/or
#other materials provided with the distribution.
#
#THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
#ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
#WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
#IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
#INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
#NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
#PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
#WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
#ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
#POSSIBILITY OF SUCH DAMAGE.
#
# XenAPI plugin for starting the conversion VPX
#

import time
# when python is upgraded to higher than 2.7.9, this should be
# updated to using xmlrpclib.py 
from xcmxmlrpclib import ServerProxy

import XenAPI
import XenAPIPlugin

from time import gmtime, strftime

CONVERSION_VM = 'conversionvm'

# How long to wait for a VM to start up and report its IP before giving up.
VM_START_TIMEOUT_SECONDS = 120

def poll_until_true_value(func):
    """Decorator for Xenstore read functions.
    Does the read in a loop until a non-None value is received, or raises ConfigurationError on timeout.
    """
    def decorated(session, vm, device=None):
        starttime = time.time()
        while True:
            value = func(session, vm, device)
            if value:
                return value
            if time.time() - starttime > VM_START_TIMEOUT_SECONDS:
                power = session.xenapi.VM.get_power_state(vm)
                vmid = session.xenapi.VM.get_uuid(vm)
                if power == 'Running':
                    raise Exception("Conversion VM %r started, but did not respond in %d seconds." % (vmid, VM_START_TIMEOUT_SECONDS))
                else:
                    raise Exception("Conversion VM %r failed to start in %d seconds, still in power_state %r." % (vmid, VM_START_TIMEOUT_SECONDS, power))
            time.sleep(1)
    return decorated

def vms_with_records(session):
    expr = 'field "is_a_template" = "false"'
    return session.xenapi.VM.get_all_records_where(expr)

def is_conversion_vm(vmrec):
    """Returns true if the VM is a conversion VM cloned from the conversion VPX template"""
    return not vmrec['is_a_template'] and CONVERSION_VM in vmrec['other_config']

def find_conversion_vm(session):
    for vm_ref, vm_rec in vms_with_records(session).iteritems():
        if is_conversion_vm(vm_rec):
            return vm_ref, vm_rec
    return False, None

@poll_until_true_value
def blocking_read_ip_address(session, vm, device):
    # Reads and returns the IP address of the VM's network interface on device 1 from xenstore,
    # or None if it could not be read.
    # device 0 is the internal network for the VM
    # device 1 is the external network for the VM
    try:
        metrics = session.xenapi.VM.get_guest_metrics(vm)
        networks = session.xenapi.VM_guest_metrics.get_networks(metrics)
        netdevice = device
        netdevice += '/ip'
        return networks.get(netdevice, None)
    except:
        return None

def is_conversion_service_running(ip):
    host = 'https://'
    host += ip
    TLS_CIPHER = 'AES128-SHA256'
    try:
        import ssl
        context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
        context.set_ciphers(TLS_CIPHER)
        server = ServerProxy(host,context=context)
        server.svc.get_version()
        return True
    except:
        return False

def poll_until_conversion_service_running(ip):
    starttime = time.time()
    while True:
        value = is_conversion_service_running(ip)
        if value:
            return value
        if time.time() - starttime > VM_START_TIMEOUT_SECONDS:
            raise Exception("Conversion VM started, but the conversion service did not respond in %d seconds." % (VM_START_TIMEOUT_SECONDS))
        time.sleep(1)

def main(session, args):
    vm_ref, vm_rec = find_conversion_vm(session)
    if vm_ref:
        try:
            # Start VM if it is not in the started state
            if vm_rec['power_state'] == 'Halted':
                session.xenapi.VM.start(vm_ref, False, True)
        except XenAPI.Failure, e:
            if 'VM_BAD_POWER_STATE' in str(e):
                #ignore VM_BAD_POWER_STATE since that can happen if we get reentered while starting the VPX
                pass
            else:
                raise
        external_ip = blocking_read_ip_address(session, vm_ref, '1')
        himn_ip = blocking_read_ip_address(session, vm_ref, '0')
	poll_until_conversion_service_running(external_ip)
	return external_ip
    else:
        raise Exception("Cannot find Conversion VM on this host!")

if __name__ == "__main__":
    XenAPIPlugin.dispatch({"main":main})
