#!/usr/bin/env python
import Queue
import subprocess
import json
import os
import uuid
import socket
import sys

PACKAGE_PATH = '/var/packages/Virtualization/'
COLLECTOR_VERSION = 3
DEBUG = False

def is_etcd_running():
	return os.path.isfile("/var/run/etcd.pid")

def is_called_by_proactive_care():
	return "NoDedupeData" in os.environ

def err_print(msg):
	if DEBUG:
		print msg
	return

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

def send_webapi(api, method, ver, params=[]):
	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).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_udc_master():
	ret, udc_check = send_webapi('SYNO.CCC.Cluster', 'udc_check', '1')
	if not ret:
		err_print("Failed to check udc in cluster, Stop.")
		return False
	if 0 == udc_check['allow_udc']:
		err_print("UDC not allowed by cluster, Stop.")
		return False
	return True

def get_list(api, key, method='list', version='1', params=[]):
	ret, data = send_webapi(api, method, version, params)
	if not ret or not check_json_key(data, key) or not isinstance(data[key], list):
		return False, []
	return True, data[key]

def cluster_info(clusterDict):
	ret, hostList = get_list('SYNO.Virtualization.Host', 'hosts')
	if not ret:
		err_print("Failed to list all hosts.")
		return
	clusterDict['num_host'] = len(hostList)
	clusterDict['hosts'] = []
	for host in hostList:
		hostDict = {}
		hostDict['model'] = host['model']
		hostDict['version'] = host['version']
		hostDict['package_version'] = host['package_version']
		hostDict['cpu_pin_num'] = host['cpu_pin_num']
		clusterDict['hosts'].append(hostDict)

	ret, clusterNotifyInfo = send_webapi('SYNO.Virtualization.Setting.Notify', 'get', '1')
	if not ret:
		clusterDict['enable_email_notify'] = False
	else:
		clusterDict['enable_email_notify'] = clusterNotifyInfo['enable_mail']

	ret, clusterStatus = send_webapi('SYNO.Virtualization.Cluster', 'check_status', '1')
	if not ret:
		clusterDict['enable_pro'] = False
	else:
		clusterDict['enable_pro'] = clusterStatus['is_pro']

	ret, vncSharingList = get_list('SYNO.Virtualization.Sharing.VNC', 'sharings',
					params=['project_name=\"SYNO.SDS.Virtualization.Application\"', 'all_entry=true', 'offset=0', 'limit=200'])
	if not ret:
		clusterDict['num_vnc_sharing'] = -1
	else:
		clusterDict['num_vnc_sharing'] = len(vncSharingList)

	return


def network_get(singleNetDict, network_id):
	ret, netGetInfo = send_webapi('SYNO.Virtualization.Network', 'get', '1', ['network_id=\"'+network_id+"\""])
	if not ret:
		err_print("Failed to get network"+network_id+" info.")
		return
	for nic in netGetInfo['interfaces']:
		if nic['has_sriov']:
			singleNetDict['enable_sriov'] = True
		if not (-1 == nic['interface_name'].find("bond")):
			singleNetDict['has_bond_interface'] = True
	return

def network_info(networkDict):
	ret, netList = get_list('SYNO.Virtualization.Network', 'networks')
	if not ret:
		err_print("Failed to list all networks.")
		return
	networkDict['num_net_groups'] = len(netList)
	networkDict['net_groups'] = []
	for netListInfo in netList:
		singleNetDict = {}
		singleNetDict['enable_sriov'] = False
		singleNetDict['has_bond_interface'] = False
		singleNetDict['num_interfaces'] = netListInfo['num_interfaces']
		singleNetDict['num_vinterfaces'] = netListInfo['num_vinterfaces']
		singleNetDict['num_hosts'] = netListInfo['num_hosts']
		singleNetDict['num_guests'] = netListInfo['num_guests']
		singleNetDict['enable_vlan'] = (netListInfo['vlan_id'] != 0)
		network_get(singleNetDict, netListInfo['network_id'])

		networkDict['net_groups'].append(singleNetDict)
	return


def repo_info(repoDict):
	ret, repoList = get_list('SYNO.Virtualization.Repo', 'repos')
	if not ret:
		err_print("Failed to list all repos.")
		return
	repoDict['num_repo'] = len(repoList)
	repoDict['state_type'] = []
	for repoListInfo in repoList:
		repoDict['state_type'].append(repoListInfo['status_type'])
	return


def get_privilege(guest_id):
	ret, data = send_webapi('SYNO.Virtualization.Guest', 'get_privilege', '1', ["guest_id=\""+guest_id+"\"", "action=\"list\"", "authtype=\"local\"", "entrytype=\"users\"", "limit=20", "offset=0", "substr=\"\""])
	if not ret or not check_json_key(data, 'rules') or not isinstance(data['rules'], list):
		return False, []
	return True, data['rules']

def guest_get_setting(guestDict, guest_id):
	ret, guestGetSettingInfo = send_webapi('SYNO.Virtualization.Guest', 'get_setting', '1', ["guest_id=\""+guest_id+"\""])
	if not ret:
		err_print("Failed to get guest ["+guest_id+"] setting.")
		return
	if not 0 == guestGetSettingInfo['autorun']:
		guestDict['num_vm_with_autorun'] += 1
	return

def guest_get(singleGuestDict, guest_id):
	ret, guestGetInfo = send_webapi('SYNO.Virtualization.Guest', 'get', '1', ["guest_id=\""+guest_id+"\""])
	if not ret:
		err_print("Failed to get guest ["+guest_id+"] info.")
		return
	singleGuestDict['uptime'] = int(guestGetInfo['uptime'])

	singleGuestDict['vdisks'] = []
	singleGuestDict['vdisk_total_size'] = 0
	singleGuestDict['vdisk_total_used'] = 0
	for vdisk in guestGetInfo['vdisks']:
		vdiskDict = {}
		singleGuestDict['vdisk_total_size'] += int(vdisk['size'])
		singleGuestDict['vdisk_total_used'] += int(vdisk['used'])
		vdiskDict['size'] = int(vdisk['size'])
		vdiskDict['used'] = int(vdisk['used'])
		vdiskDict['mode'] = vdisk['vdisk_mode']
		singleGuestDict['vdisks'].append(vdiskDict)

	singleGuestDict['vnics'] = []
	singleGuestDict['num_vnics'] = len(guestGetInfo['vnics'])
	singleGuestDict['num_vnics_with_sriov'] = 0
	for vnic in guestGetInfo['vnics']:
		vnicDict = {}
		vnicDict['type'] = vnic['vnic_type']
		if vnic['prefer_sriov']:
			singleGuestDict['num_vnics_with_sriov'] += 1
		singleGuestDict['vnics'].append(vnicDict)
	return

def guest_get_privilege(singleGuestDict, guest_id):
	ret, ruleList = get_privilege(guest_id)
	if not ret:
		err_print("Failed to get guest privilege.")
		return
	singleGuestDict['assign_to_normal_user'] = False
	for rule in ruleList:
		if (rule['allow'] and (rule['name'] != "admin")):
			singleGuestDict['assign_to_normal_user'] = True
			break
	return

def guest_info(guestDict):
	ret, guestList = get_list('SYNO.Virtualization.Guest', 'guests')
	if not ret:
		err_print("Failed to list all guests.")
		return
	guestDict['num_vm'] = len(guestList)
	guestDict['num_general_vm'] = 0
	guestDict['num_vdsm'] = 0
	guestDict['num_vm_with_ha'] = 0
	guestDict['num_vm_with_autorun'] = 0
	guestDict['num_vm_set_cpu_pin'] = 0
	guestDict['num_vm_set_cpu_weight'] = 0
	guestDict['guests'] = []
	for guestListInfo in guestList:
		singleGuestDict = {}
		# information from "list"
		if guestListInfo['is_general_vm'] == True:
			guestDict['num_general_vm']  += 1
		else:
			guestDict['num_vdsm'] += 1
			singleGuestDict['vdsm_version'] = guestListInfo['dsm_version']
		singleGuestDict['ha_enabled'] = False
		if not ("" == guestListInfo['ha_status']):
			singleGuestDict['ha_enabled'] = True
			guestDict['num_vm_with_ha'] += 1
		if not (0 == guestListInfo['cpu_pin_num']):
			guestDict['num_vm_set_cpu_pin'] += 1
		if not (256 == guestListInfo['cpu_weight']):
			guestDict['num_vm_set_cpu_weight'] += 1
		singleGuestDict['vcpu_num'] = int(guestListInfo['vcpu_num'])
		singleGuestDict['vram_size'] = int(guestListInfo['vram_size'])
		singleGuestDict['video_card'] = guestListInfo['video_card']
		singleGuestDict['status'] = guestListInfo['status']

		# information from "get_setting"
		guest_get_setting(guestDict, guestListInfo['guest_id'])
		# information from "get"
		guest_get(singleGuestDict, guestListInfo['guest_id'])
		# information from "get_privilege"
		guest_get_privilege(singleGuestDict, guestListInfo['guest_id'])

		guestDict['guests'].append(singleGuestDict)
	return


def image_info(imageDict):
	ret, imageList = get_list('SYNO.Virtualization.Guest.Image', 'images', version='2')
	if not ret:
		err_print("Failed to list all images.")
		return
	imageDict['num_images'] = len(imageList)
	imageDict['num_iso_images'] = 0
	imageDict['num_vdsm_images'] = 0
	imageDict['num_disk_images'] = 0
	imageDict['has_guest_tool'] = False
	for imageListInfo in imageList:
		if "iso" == imageListInfo['type']:
			imageDict['num_iso_images'] += 1
			if "major" in imageListInfo:
				imageDict['has_guest_tool'] = True
		elif "vdsm" == imageListInfo['type']:
			imageDict['num_vdsm_images'] += 1
		elif "disk" == imageListInfo['type']:
			imageDict['num_disk_images'] += 1
		else:
			err_print("Unknown image type: "+imageListInfo['type'])
	return

def list_plan_snap(singlePlanDict, planInfo):
	ret, planSnapList = send_webapi('SYNO.Virtualization.GuestProtect.Snap', 'list', '1',
					["guest_id=\""+planInfo['guest_id']+"\"", "protect_id=\""+planInfo['protect_id']+"\""])
	if not ret:
		err_print("Failed to get plan ["+planInfo['protect_id']+"] info.")
		return
	singlePlanDict['num_local_snaps'] = planSnapList['local_count']
	singlePlanDict['num_remote_snaps'] = planSnapList['remote_count']

def snapshot_info(snapDict):
	ret, snapList = get_list('SYNO.Virtualization.GuestProtect.Snap', 'guest_snaps', method='list_all')
	if not ret:
		err_print("Failed to list all guest with snapshots.")
		return
	snapDict['num_guest_applying_scheduling'] = 0 # obsolete item
	snapDict['num_guest_taking_snapshot'] = 0
	snapDict['num_recover_point_for_each_vm'] = []
	snapDict['num_remote_recover_point_for_each_vm'] = []
	for snapListInfo in snapList:
		if 0 < snapListInfo['local_count'] or 0 < snapListInfo['remote_count']:
			snapDict['num_guest_taking_snapshot'] += 1
		snapDict['num_recover_point_for_each_vm'].append(snapListInfo['local_count'])
		snapDict['num_remote_recover_point_for_each_vm'].append(snapListInfo['remote_count'])
	ret, protectPlanList = get_list('SYNO.Virtualization.GuestProtect.Plan', 'protect_ids')
	if not ret:
		err_print("Failed to list all guest with snapshots.")
		return
	snapDict['num_protection_plan'] = len(protectPlanList)
	snapDict['protection_plans'] = []
	for planInfo in protectPlanList:
		singlePlanDict = {}
		singlePlanDict['has_replica_dest'] = check_json_key(planInfo, "dr_repo_name")
		singlePlanDict['has_schedule'] = (planInfo['schedule']['policy_id'] != "")
		singlePlanDict['has_retention'] = (planInfo['retention']['replica']['policy_id'] != "") or \
						  (planInfo['retention']['snapshot']['policy_id'] != "")
		list_plan_snap(singlePlanDict, planInfo)
		snapDict['protection_plans'].append(singlePlanDict)
	return

def __main__():
	if (not os.path.exists(PACKAGE_PATH)) or (not is_etcd_running()):
		print '{}'
		return

	skip_check = (len(sys.argv) > 1) and (sys.argv[1] == "skip_check")
	skip_check |= is_called_by_proactive_care()
	if (not skip_check) and (not is_udc_master()):
		print json.dumps({'collector_version': COLLECTOR_VERSION})
		return

	clusterDict = {}
	networkDict = {}
	repoDict = {}
	guestDict = {}
	imageDict = {}
	snapDict = {}

	cluster_info(clusterDict)
	network_info(networkDict)
	repo_info(repoDict)
	guest_info(guestDict)
	image_info(imageDict)
	snapshot_info(snapDict)

	print json.dumps({
		'collector_version': COLLECTOR_VERSION,
		'cluster': clusterDict,
		'network': networkDict,
		'repo': repoDict,
		'guests': guestDict,
		'images': imageDict,
		'guest_snapshot': snapDict
	})

if __name__ == '__main__':
	__main__()
