#!/usr/bin/python

# Copyright (c) 2000-2018 Synology Inc. All rights reserved.

import sys
import json
import glob
import subprocess
import time
import os.path
import random
import xml.etree.ElementTree as ET


# Some define
VIRT_WEBAPI_PREFIX = 'SYNO.Virtualization'
IMAGES = 'images/'
OVF_NAMESPACE = '{http://schemas.dmtf.org/ovf/envelope/1/}'
RASD_NAMESPACE = '{http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData}'

class bcolors:
    OKGREEN = '\033[92m'
    WARNING = '\033[93m'
    FAIL = '\033[91m'
    ENDC = '\033[0m'

def prt_info(msg):
    print('[' + bcolors.OKGREEN + 'INFO' + bcolors.ENDC + '] ' + msg)

def prt_warn(msg):
    print('[' + bcolors.WARNING + 'WARN' + bcolors.ENDC + '] ' + msg)

def prt_err(msg):
    print('[' + bcolors.FAIL + 'ERROR' + bcolors.ENDC + '] ' + msg)

def check_json_key(data, key):
    return data is not None and key in data

def send_webapi(api, method, ver, params=[]):
    devnull = open(os.devnull, 'wb')
    argument = ['/usr/syno/bin/synowebapi', '--exec', 'api=' + api, 'method=' + method, 'version=' + ver]
    for param in params:
	argument.append(param)
    resp = json.loads(subprocess.Popen(argument, stdout=subprocess.PIPE, stderr=devnull).communicate()[0])
    if not check_json_key(resp, 'success'):
	return False, None
    elif resp['success'] == False:
	return False, resp['error']
    else:
	return True, resp['data'] if check_json_key(resp, 'data') else None

def is_guest_exist(name):
    ret, resp = send_webapi(VIRT_WEBAPI_PREFIX + '.Guest', 'list_basic', str(1))
    if not ret:
        return False

    for guest in resp['guests']:
        if guest['name'] == name:
            return True
    return False

def gen_vdisk_obj():
    return {
        "vdisk_mode": 16,
        "vdisk_size": 15,
        "unmap": False
    }

def gen_vdisk_struct_obj(path):
    return {
        "capacity": 20,
        "file_name": path
    }

def gen_vnic_obj(mac):
    return {
        "prefer_sriov": False,
        "vnic_type": 1,
        "type": "add",
        "mac": mac,
        "network_id": "bfffe844-3dee-46fd-a1cc-00d2cae7b767"
    }

def print_vdisk_info(vdisk_info):
    index = 0
    for info in vdisk_info:
        if info['boot'] == 'true':
            print(' ' + str(index) + '. Name:' + info['name'] + ' Size:' + info['size'] + ' (boot)')
        else:
            print(' ' + str(index) + '. Name:' + info['name'] + ' Size:' + info['size'])
        index = index + 1

def valid_order(order, vdisk_num):
    touch_array = [False] * vdisk_num
    order_list = order.split(' ')

    if len(order_list) != vdisk_num:
        return False

    for order in order_list:
        if int(order) >= vdisk_num:
            return False
        touch_array[int(order)] = True

    # check all value exists
    for touch in touch_array:
        if not touch:
            return False

    return True

def get_input_order(vdisk_info, vdisk_info_dummy, vdisk_struct, vdisk_struct_dummy):
    prt_info('Please enter the vdisk order, for example: 2 0 1')
    while True:
        order = raw_input(' >> ')
        if not valid_order(order, len(vdisk_info)):
            prt_err('The vdisk order is invalid, please enter the vdisk order again, for example: 2 0 1')
            continue

        index = 0
        order_list = order.split(' ')
        for i in order_list:
            vdisk_struct[index] = vdisk_struct_dummy[int(i)]
            vdisk_info[index] = vdisk_info_dummy[int(i)]
            index = index + 1
        break

    return


def get_repo_id():
    # get local host id
    if not os.path.isfile('/usr/syno/etc/ccc/localhost_db_host_id'):
        prt_info('Please create VMM cluster')
        return None
    local_host_id = open('/usr/syno/etc/ccc/localhost_db_host_id', "rb").read()

    # get pwd
    pwd = os.path.dirname(os.path.realpath(__file__))
    volume = pwd.split('/')[1]

    # get all repos
    ret, resp = send_webapi(VIRT_WEBAPI_PREFIX + '.Repo', 'list', str(1))
    if not ret:
        prt_err('Oops, failed to get repo.')
        return None

    if len(resp['repos']) == 0:
        prt_info('Please create repo in VMM.')
        return None

    # find repo in the same host and the same volume
    for repo in resp['repos']:
        if repo['host_id'] != local_host_id:
            continue
        if volume in repo['volume_path']:
            return repo['repo_id']

    return None

def random_mac():
    return [ 0x00, 0x16, 0x3e,
        random.randint(0x00, 0x7f),
        random.randint(0x00, 0xff),
        random.randint(0x00, 0xff) ]

def pretty_mac(mac):
    return ':'.join(map(lambda x: "%02x" % x, mac))


def prepare_obj_array_to_str(obj_array):
    ret = '['

    for obj in obj_array:
        ret = ret + json.dumps(obj) + ','
    ret = ret[:-1]
    ret = ret + ']'
    return ret

def wait_p2v_finish(name):
    for i in range(1800):
        time.sleep(2)

        ret, resp = send_webapi(VIRT_WEBAPI_PREFIX + '.Guest', 'list_basic', str(1))
        if not ret:
            continue

        for guest in resp['guests']:
            if guest['name'] != name:
                continue
            if guest['status'] == 'shutdown':
                return True
            break
    return False

def migrate(file_path):
    tree = ET.ElementTree(file=file_path)

    vdisk_info = []
    vdisk_info_dummy = []
    vdisk = []
    vdisk_struct = []
    vdisk_struct_dummy = []
    vnic = []

    name = tree.find('Content/Name').text

    if is_guest_exist(name):
        prt_info('Guest [' + name + '] alreay exists.')
        return

    prt_info('Start to import vm: ' + name)

    # Parse disk
    for elem in tree.findall('Section/Disk'):
        info = {}
        info['boot'] = elem.attrib[OVF_NAMESPACE + 'boot']
        info['size'] = elem.attrib[OVF_NAMESPACE + 'size']
        info['name'] = elem.attrib[OVF_NAMESPACE + 'disk-alias']
        path = os.getcwd() + '/' + IMAGES + elem.attrib[OVF_NAMESPACE + 'fileRef']
        if not os.path.isfile(path):
            prt_err('File ['+ path +'] not exist.')
            return
        vdisk_info.append(info)
        vdisk_info_dummy.append(info)
        vdisk.append(gen_vdisk_obj())
        vdisk_struct.append(gen_vdisk_struct_obj(path))
        vdisk_struct_dummy.append(gen_vdisk_struct_obj(path))

    prt_info('There are ' + str(len(vdisk_info)) + ' vdisks in this VM as follow:')
    print_vdisk_info(vdisk_info)
    get_input_order(vdisk_info, vdisk_info_dummy, vdisk_struct, vdisk_struct_dummy)
    prt_info('After reorder:')
    print_vdisk_info(vdisk_info)

    # Parse MAC
    for item in tree.findall('Content/Section/Item/' + RASD_NAMESPACE + 'MACAddress'):
        vnic.append(gen_vnic_obj(item.text))

    # if vm doesn't have vnic, we append a fake one to prevent webapi fail
    if len(vnic) == 0:
        vnic.append(gen_vnic_obj(pretty_mac(random_mac())))

    # drop vdisk larger than 8
    if len(vdisk_struct) > 8:
        prt_warn('There are too many vdisks, we only keep 8 vdisks.')
        vdisk_struct = vdisk_struct[0:8]
        vdisk = vdisk[0:8]

    # drop vnic larger than 8
    if len(vnic) > 8:
        prt_warn('There are too many vnics, we only keep 8 vnics.')
        vnic = vnic[0:8]

    repo_id = get_repo_id()
    if repo_id is None:
        prt_err('Failed to get repo.')
        return

    vdisk_str = prepare_obj_array_to_str(vdisk)
    vdisk_struct_str = prepare_obj_array_to_str(vdisk_struct)
    vnic_str = prepare_obj_array_to_str(vnic)

    prt_info('Importing...')

    # Send p2v webapi
    ret, resp = send_webapi(VIRT_WEBAPI_PREFIX + '.Guest.P2V', 'open_in_vm', str(1),
            ["vcpu_num=1", "vram_size=1024", "name=\""+name+"\"", "repo_id=\""+repo_id+"\"", "vdisks="+vdisk_str,
                "vdisk_struct="+vdisk_struct_str, "vnics="+vnic_str])
    if not ret:
        prt_err('Oops, import webapi failed..., resp: ' + json.dumps(resp))
        return

    if not wait_p2v_finish(name):
        prt_err('Oops, failed to wait for import finishing')
        return

    prt_info('Finish import vm: ' + name)

if __name__ == '__main__':
    prt_info('Copyright (c) 2000-2018 Synology Inc. All rights reserved.\n')

    vms = glob.glob('master/vms/*/*.ovf')

    if len(vms) == 0:
        prt_info('Please put and run rhel2vmm in export_domain, which will contain [master/images].')

    for vm in vms:
        migrate(vm)
        print('-------')
