#!/usr/bin/python2.7
# -*- coding: utf-8 -*-
#
# UCS Virtual Machine Manager Daemon
#  UVMM commandline tool
#
# Copyright 2010-2018 Univention GmbH
#
# http://www.univention.de/
#
# All rights reserved.
#
# The source code of this program is made available
# under the terms of the GNU Affero General Public License version 3
# (GNU AGPL V3) as published by the Free Software Foundation.
#
# Binary versions of this program provided by Univention to you as
# well as other copyrighted, protected or trademarked materials like
# Logos, graphics, fonts, specific documentations and configurations,
# cryptographic keys etc. are subject to a license agreement between
# you and Univention and not subject to the GNU AGPL V3.
#
# In the case you use this program under the terms of the GNU AGPL V3,
# the program is provided in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public
# License with the Debian GNU/Linux or Univention distribution in file
# /usr/share/common-licenses/AGPL-3; if not, see
# <http://www.gnu.org/licenses/>.
"""UVMM command line interface."""

import sys
import locale
import json
import errno
from argparse import ArgumentParser, ArgumentTypeError
from univention.uvmm import protocol, client
from univention.uvmm.helpers import TranslatableException, _
from pprint import pprint
from uuid import UUID
try:
	from lxml import etree as ET
except ImportError:
	import xml.etree.ElementTree as ET
__builtins__._ = _


def create_socket(options):
	"""Create socket for communication with UVMMd.

	UNIX socket only."""
	sock = client.UVMM_ClientUnixSocket(options.socket, timeout=options.timeout)
	if options.verbose:
		print >> sys.stderr, "sock=%s" % (sock,)
	return sock


def request(options, req):
	"""Send request and wait for answer."""
	sock = create_socket(options)
	try:
		if options.verbose:
			print >> sys.stderr, "request=%s" % (req,)
		res = sock.send(req)
		if isinstance(res, protocol.Response_DUMP):
			print "DATA:"
			try:
				pprint(dump(res.data))
			except AttributeError:
				print str(res)
			except Exception as ex:
				import traceback
				traceback.print_exc()
				print 'Exception %s\n%s' % (ex, res)
		elif isinstance(res, protocol.Response_ERROR):
			raise TranslatableException(res.translatable_text, res.values)
		else:
			print "OK."
	finally:
		sock.close()


def dump(obj):
	if isinstance(obj, list):
		return map(dump, obj)
	elif isinstance(obj, dict):
		return dict(map(lambda k_v: (k_v[0], dump(k_v[1])), obj.items()))
	elif hasattr(obj, '__dict__'):
		return dict(map(lambda k_v1: (k_v1[0], dump(k_v1[1])), obj.__dict__.items()))
	else:
		return obj


def from_ldap(options):
	"""Add all nodes registered in LDAP."""
	from univention.uvmm.uvmm_ldap import ldap_uris, LdapError
	try:
		uris = ldap_uris()
	except LdapError as (translatable_text, fillin):
		print >> sys.stderr, "ERROR:"
		print >> sys.stderr, _(translatable_text) % fillin
		return False

	if len(uris) == 0:
		print >> sys.stderr, _("No nodes found.")
		return False

	result = True
	sock = create_socket(options)
	for uri in uris:
		try:
			req = protocol.Request_NODE_ADD(uri=uri)
			res = sock.send(req)
			if isinstance(res, protocol.Response_ERROR):
				raise client.ClientError(res.translatable_text, res.values)
		except client.ClientError as (translatable_text, fillin):
			print >> sys.stderr, _(translatable_text) % fillin
			result = False
	sock.close()

	return result


class ParserGenerator(object):

	def __init__(self):
		parser = ArgumentParser()
		parser.add_argument(
			'-u', '--unix',
			action='store',
			dest='socket',
			default="/var/run/uvmm.socket",
			help=_('Path to the UNIX socket'))
		parser.add_argument(
			'-v', '--verbose',
			action='store_true',
			dest='verbose',
			default=False,
			help=_('Print additional information'))
		parser.add_argument(
			'-T', '--timeout',
			action='store',
			dest='timeout',
			default=0,
			type=int,
			help=_('Timeout in seconds for UVMM commands'))

		self._add_subparsers(parser)

		self._parser = parser

	def _add_subparsers(self, parser):
		subparsers = parser.add_subparsers(
			title='subcommands',
			description='valid subcommands',
			dest='mode',
			help='sub-command help'
		)

		for name in sorted(dir(self)):
			if name.startswith('_'):
				continue
			method = getattr(self, name)
			doc = self._usage(method.__doc__)
			sub_parser = subparsers.add_parser(name, help=doc)
			method(sub_parser)

	@staticmethod
	def _usage(doc):
		"""Show usage informations for mode."""
		lines = doc.split('\n')
		while lines and not lines[0].strip():
			del lines[0]
		while lines and not lines[-1].strip():
			del lines[-1]
		indent = min([len(l[0:-len(l.lstrip())]) for l in filter(None, lines)])
		desc = [l[indent:] for l in lines]
		return '\n'.join(desc)

	def __call__(self):
		return self._parser.parse_args()

	def ldap(self, sub_parser):
		"""
		Query LDAP for all virtualization servers and add them to the UVMM.
		"""
		sub_parser.set_defaults(func=from_ldap)

	def add(self, sub_parser):
		"""
		Add the virtualization server specified by <uri> to the UVMM.
		"""
		sub_parser.add_argument(
			'uri',
			help='libvirt URI')
		sub_parser.set_defaults(protocol=protocol.Request_NODE_ADD)

	def remove(self, sub_parser):
		"""
		Remove the virtualization server specified by <uri> from the UVMM.
		"""
		sub_parser.add_argument(
			'uri',
			help='libvirt URI')
		sub_parser.set_defaults(protocol=protocol.Request_NODE_REMOVE)

	def query(self, sub_parser):
		"""
		Query virtualization server specifies by <uri> for all its domains and
		storage pools.
		"""
		sub_parser.add_argument(
			'uri',
			help='libvirt URI')
		sub_parser.set_defaults(protocol=protocol.Request_NODE_QUERY)

	def frequency(self, sub_parser):
		"""
		Set the interval how often UVMM queries all registerd or that specific
		virtualization server.
		"""
		sub_parser.add_argument(
			'hz',
			type=int,
			help='delay between updates in [ms]')
		sub_parser.add_argument(
			'uri',
			nargs='?',
			help='libvirt URI')
		sub_parser.set_defaults(protocol=protocol.Request_NODE_FREQUENCY)

	def groups(self, sub_parser):
		"""
		Return the names of groups of virtualization server.
		"""
		sub_parser.set_defaults(protocol=protocol.Request_GROUP_LIST)

	def nodes(self, sub_parser):
		"""
		Return the list of URIs used to specify the virtualization server,
		which belong to the given <group>.
		"""
		sub_parser.add_argument(
			'group',
			help='group nam')
		sub_parser.add_argument(
			'pattern',
			nargs='?',
			default='*',
			help='globbing filter for host names')
		sub_parser.set_defaults(protocol=protocol.Request_NODE_LIST)

	def bye(self, sub_parser):
		"""
		End the connection. Only useful for debugging.
		"""
		sub_parser.set_defaults(protocol=protocol.Request_BYE)

	def save(self, sub_parser):
		"""
		On the virtualization server specified by <uri> save the domain
		specified by its UUID <domain> locally to the file <statefile>.
		The domain will be turned off.
		"""
		sub_parser.add_argument(
			'uri',
			help='libvirt URI')
		sub_parser.add_argument(
			'domain',
			type=uuid,
			help='domain UUID')
		sub_parser.add_argument(
			'statefile',
			help='file name')
		sub_parser.set_defaults(protocol=protocol.Request_DOMAIN_SAVE)

	def restore(self, sub_parser):
		"""
		On the virtualization server specified by <uri> restore a domain
		locally from the file <statefile>.
		"""
		sub_parser.add_argument(
			'uri',
			help='libvirt URI')
		sub_parser.add_argument(
			'statefile',
			help='file name')
		sub_parser.set_defaults(protocol=protocol.Request_DOMAIN_RESTORE)

	def migrate(self, sub_parser):
		"""
		Migrate the domain <domain> from the virtualization server specifies by
		<uri> to the virtualization server specifies by <target_uri>.
		"""
		sub_parser.add_argument(
			'uri',
			help='libvirt URI of the source host')
		sub_parser.add_argument(
			'domain',
			type=uuid,
			help='domain UUID')
		sub_parser.add_argument(
			'target_uri',
			help='libvirt URI of the target host')
		sub_parser.set_defaults(protocol=protocol.Request_DOMAIN_MIGRATE)

	def domains(self, sub_parser):
		"""
		Return a list of available domains on the given node
		<pattern> is an optional globbing pattern for filtering the domain names.
		"""
		sub_parser.add_argument(
			'uri',
			help='libvirt URI')
		sub_parser.add_argument(
			'pattern',
			nargs='?',
			default='*',
			help='globbing filter for domain names')
		sub_parser.set_defaults(protocol=protocol.Request_DOMAIN_LIST)

	def domain_info(self, sub_parser):
		"""
		Return detailed information about a domain
		"""
		sub_parser.add_argument(
			'uri',
			help='libvirt URI')
		sub_parser.add_argument(
			'domain',
			type=uuid,
			help='domain UUID')
		sub_parser.set_defaults(protocol=protocol.Request_DOMAIN_INFO)

	def state(self, sub_parser):
		"""
		Change the state of domain <domain> on the virtualization server specifies by
		<uri> to the given state <state>.
		"""
		sub_parser.add_argument(
			'uri',
			help='libvirt URI')
		sub_parser.add_argument(
			'domain',
			type=uuid,
			help='domain UUID')
		sub_parser.add_argument(
			'state',
			choices=('RUN', 'PAUSE', 'SUSPEND', 'RESTART', 'SHUTDOWN', 'SHUTOFF'),
			help='domain state')
		sub_parser.set_defaults(protocol=protocol.Request_DOMAIN_STATE)

	def define(self, sub_parser):
		"""
		Define a new domain on the virtualization server specifies by <uri>.
		<file_or_xml> can either be a file-name or an inline XML string
		according to the format used by libvirt.
		"""
		sub_parser.add_argument(
			'uri',
			help='libvirt URI')
		sub_parser.add_argument(
			'file_or_xml',
			help='libvirt XML or file name')
		sub_parser.set_defaults(pre=self._pre_define, protocol=protocol.Request_DOMAIN_DEFINE)

	def _pre_define(self, args):
		if args.file_or_xml.startswith('<'):
			root = ET.fromstring(args.file_or_xml)
		else:
			tree = ET.parse(args.file_or_xml)
			root = tree.getroot()  # domain
		domain = protocol.Data_Domain()
		domain.domain_type = root.attrib['type']
		domain.uuid = root.findtext('uuid')
		domain.name = root.findtext('name')
		domain.os_type = root.find('os').findtext('type')
		try:
			domain.arch = root.find('os').find('type').attrib['arch']
		except KeyError:
			domain.arch = 'i686'  # FIXME
		if domain.os_type == 'hvm':
			try:
				domain.boot = [boot.attrib['dev'] for boot in root.find('os').findall('boot')]
			except AttributeError:
				pass
			except KeyError:
				pass
		else:
			raise ValueError("Unknown os/type='%s'" % (domain.os_type,))
		try:
			domain.bootloader = root.findtext('bootloader')
		except AttributeError:
			pass
		try:
			domain.bootloader_args = root.findtext('bootloader_args')
		except AttributeError:
			pass
		domain.maxMem = int(root.findtext('memory')) << 10  # KiB
		try:
			domain.rtc_offset = root.find('clock').attrib['offset']
		except AttributeError:
			pass
		except KeyError:
			pass
		from univention.uvmm.node import Disk, Interface, Graphic
		for disk in root.find('devices').findall('disk'):
			d = Disk()
			d.type = disk.attrib['type']
			try:
				d.device = disk.attrib['device']
			except KeyError:
				pass
			try:
				d.driver = disk.find('driver').attrib['name']
			except AttributeError:
				pass
			except KeyError:
				pass
			try:
				d.driver_type = disk.find('driver').attrib['type']
			except AttributeError:
				pass
			except KeyError:
				pass
			try:
				d.driver_cache = disk.find('driver').attrib['cache']
			except AttributeError:
				pass
			except KeyError:
				pass
			if d.type == Disk.TYPE_FILE:
				d.source = disk.find('source').attrib['file']
			elif d.type == Disk.TYPE_BLOCK:
				d.source = disk.find('source').attrib['dev']
			else:
				d.source = None  # FIXME
			try:
				d.target_dev = disk.find('target').attrib['dev']
			except AttributeError:
				pass
			except KeyError:
				pass
			try:
				d.target_bus = disk.find('target').attrib['bus']
			except AttributeError:
				pass
			except KeyError:
				pass
			try:
				d.readonly = bool(disk.find('readonly'))
			except KeyError:
				pass
			domain.disks.append(d)
		for interface in root.find('devices').findall('interface'):
			i = Interface()
			i.type = interface.attrib['type']
			try:
				i.mac_address = interface.find('mac').attrib['address']
			except AttributeError:
				pass
			except KeyError:
				pass
			try:
				i.source = interface.find('source').attrib['bridge']
			except AttributeError:
				pass
			except KeyError:
				pass
			try:
				i.script = interface.find('script').attrib['path']
			except AttributeError:
				pass
			except KeyError:
				pass
			try:
				i.target = interface.find('target').attrib['dev']
			except AttributeError:
				pass
			except KeyError:
				pass
			try:
				i.model = interface.find('model').attrib['type']
			except AttributeError:
				pass
			except KeyError:
				pass
			domain.interfaces.append(i)
		for graphics in root.find('devices').findall('graphics'):
			g = Graphic()
			g.type = graphics.attrib['type']
			try:
				g.port = int(graphics.attrib['port'])
			except KeyError:
				pass
			except ValueError:
				pass
			except TypeError:
				pass
			try:
				g.autoport = graphics.attrib['autoport'] == 'yes'
			except KeyError:
				pass
			try:
				g.keymap = graphics.attrib['keymap']
			except KeyError:
				pass
			try:
				g.listen = graphics.attrib['listen']
			except KeyError:
				for listen in graphics.findall('listen'):
					try:
						if listen.attrib['type'] != 'address':
							continue
						g.listen = listen.attrib['address']
						break
					except KeyError:
						pass
			try:
				g.passwd = graphics.attrib['passwd']
			except KeyError:
				pass
			domain.graphics.append(g)
		try:
			for annotation in root.find('annotations').findall('annotation'):
				key = annotation.findtext('key')
				value = annotation.findtext('value')
				domain.annotations[key] = value
		except AttributeError:
			pass
		pprint(domain.__dict__)
		args.domain = domain

	def undefine(self, sub_parser):
		"""
		Undefine the domain <domain> on the virtualization server specified by
		<uri> and optionally delete the given <volumes>.
		All file-volumes of the domain (including shared volumes!) are deleted
		if "ALL" is given two times.
		"""
		sub_parser.add_argument(
			'uri',
			help='libvirt URI')
		sub_parser.add_argument(
			'domain',
			type=uuid,
			help='domain UUID')
		sub_parser.add_argument(
			'volumes',
			nargs='*',
			help='volumes to delete or ALL')
		sub_parser.set_defaults(pre=self._pre_undefine, protocol=protocol.Request_DOMAIN_UNDEFINE)

	def _pre_undefine(self, args):
		if ('ALL', 'ALL') == args.volumes:
			args.volumes = None

	def pools(self, sub_parser):
		"""
		Return the names of all storage-pools on the virtualization server
		specified by <uri>.
		"""
		sub_parser.add_argument(
			'uri',
			help='libvirt URI')
		sub_parser.set_defaults(protocol=protocol.Request_STORAGE_POOLS)

	def volumes(self, sub_parser):
		"""
		Return the names of all storage-volumes on the virtualization server
		specified by <uri> in the storage-pool named <pool>.
		<type> can be used to limit the volumes to a specific type; valid
		values are 'disk', 'cdrom', 'floppy'
		"""
		sub_parser.add_argument(
			'uri',
			help='libvirt URI')
		sub_parser.add_argument(
			'pool',
			help='storage pool name')
		sub_parser.add_argument(
			'type',
			choices=('disk', 'cdrom', 'floppy'),
			nargs='?',
			default=None,
			help='storage pool type')
		sub_parser.set_defaults(protocol=protocol.Request_STORAGE_VOLUMES)

	def volume_define(self, sub_parser):
		"""
		Create a new storage-volume named <name> and the size <size> in
		bytes in the storage-pool named <pool> on the virtualization server
		specified by <uri>.
		"""
		sub_parser.add_argument(
			'uri',
			help='libvirt URI')
		sub_parser.add_argument(
			'pool',
			help='storage pool name')
		sub_parser.add_argument(
			'name',
			help='storage volume name')
		sub_parser.add_argument(
			'size',
			type=int,
			help='storage volume size')
		sub_parser.set_defaults(protocol=protocol.Request_STORAGE_DEFINE)

	def volume_used(self, sub_parser):
		"""
		Return a list of all (uri, domain)s using the given <volume>.
		"""
		sub_parser.add_argument(
			'volume',
			help='storage volume path')
		sub_parser.set_defaults(protocol=protocol.Request_STORAGE_VOLUME_USEDBY)

	def snap_create(self, sub_parser):
		"""
		On the virtualization server specified by <uri> create a snapshot named
		<snapshot> of the domain specified by its UUID <domain>.
		The state of the running domain is unaffected.
		"""
		sub_parser.add_argument(
			'uri',
			help='libvirt URI')
		sub_parser.add_argument(
			'domain',
			type=uuid,
			help='domain UUID')
		sub_parser.add_argument(
			'snapshot',
			help='snapshot name')
		sub_parser.set_defaults(protocol=protocol.Request_DOMAIN_SNAPSHOT_CREATE)

	def snap_revert(self, sub_parser):
		"""
		On the virtualization server specified by <uri> revert the domain
		specified by its UUID <domain> back to the snapshot named <snapshot>.
		The state of the running domain is destroyed.
		"""
		sub_parser.add_argument(
			'uri',
			help='libvirt URI')
		sub_parser.add_argument(
			'domain',
			type=uuid,
			help='domain UUID')
		sub_parser.add_argument(
			'snapshot',
			help='snapshot name')
		sub_parser.set_defaults(protocol=protocol.Request_DOMAIN_SNAPSHOT_REVERT)

	def snap_delete(self, sub_parser):
		"""
		On the virtualization server specified by <uri> delete the snapshot
		named <snapshot> of the domain specified by its UUID <domain>.
		The state of the running domain is unaffected.
		"""
		sub_parser.add_argument(
			'uri',
			help='libvirt URI')
		sub_parser.add_argument(
			'domain',
			type=uuid,
			help='domain UUID')
		sub_parser.add_argument(
			'snapshot',
			help='snapshot name')
		sub_parser.set_defaults(protocol=protocol.Request_DOMAIN_SNAPSHOT_DELETE)

	def domain_update(self, sub_parser):
		"""
		Trigger an update of the domain specified by its UUID <domain>.
		"""
		sub_parser.add_argument(
			'domain',
			type=uuid,
			help='domain UUID')
		sub_parser.set_defaults(protocol=protocol.Request_DOMAIN_UPDATE)

	def domain_clone(self, sub_parser):
		"""
		On the virtualization server specified by <uri> clone the instance
		<domain> with the new name <name>.

		:param subst: provides a mappings from <old> to <new> values, which can be
		used to overwrite the default behaviour for creating new settings:
			mac#<old_mac>=<new_mac>
			copy#<dev>=<method>
			name#<dev>=<name>
		Network interfaces are identified by the MAC address. By default a new
		random MAC address is generated.
		Disk devices are identified by their <dev>-name (e.g. vda, hdb, xvdc).
		<method> can be used for cloning the disk: 'copy' creates a full copy,
		'cow' uses a copy-on-write method to create an overlay over the
		original disk, 'share' shares the disk.
		"""
		sub_parser.add_argument(
			'uri',
			help='libvirt URI')
		sub_parser.add_argument(
			'domain',
			type=uuid,
			help='domain UUID')
		sub_parser.add_argument(
			'name',
			help='new domain name')
		sub_parser.add_argument(
			'subst',
			nargs='*',
			help='libvirt domain XML substitutions')
		sub_parser.set_defaults(pre=self._pre_domain_clone, protocol=protocol.Request_DOMAIN_CLONE)

	def _pre_domain_clone(self, args):
		args.subst = dict([kv.split('=', 1) for kv in args.subst])

	def targethost_add(self, sub_parser):
		"""
		Add a migration target host to a domain
		"""
		sub_parser.add_argument(
			'uri',
			help='libvirt URI')
		sub_parser.add_argument(
			'domain',
			type=uuid,
			help='domain UUID')
		sub_parser.add_argument(
			'targethost',
			help='targethost name')
		sub_parser.set_defaults(protocol=protocol.Request_DOMAIN_TARGETHOST_ADD)

	def targethost_remove(self, sub_parser):
		"""
		Remove a migration target host to a domain
		"""
		sub_parser.add_argument(
			'uri',
			help='libvirt URI')
		sub_parser.add_argument(
			'domain',
			type=uuid,
			help='domain UUID')
		sub_parser.add_argument(
			'targethost',
			help='targethost name')
		sub_parser.set_defaults(protocol=protocol.Request_DOMAIN_TARGETHOST_REMOVE)

	def cloud_add(self, sub_parser):
		"""
		Define new cloud connection.

		:param json_options: defines connection name, credentials, URL, etc.
			"{"name":"myCloud", "type":"OpenStack", "username":"demo", "password":"univention", "auth_url":"http://192.168.0.79:5000", "auth_version":"2.0_password", "tenant":"demo", "service_type":"compute", "service_name":"nova", "service_region":"regionOne"}"
		:param testconnection: by default, the connection is only added if an initial test connection is successful. If this parameter is 'false', the connection is added in any case.
		"""
		sub_parser.add_argument(
			'json_options',
			help='JSON options')
		sub_parser.add_argument(
			'testconnection',
			type=bool,
			default=True,
			help='Require working connection')
		sub_parser.set_defaults(pre=self._pre_cloud_add, protocol=protocol.Request_L_CLOUD_ADD)

	def _pre_cloud_add(self, args):
		try:
			try:
				with open(args.json_options) as json_file:
					args.args = json.load(json_file)
			except EnvironmentError as ex:
				if ex.errno != errno.ENOENT:
					raise
				args.args = json.loads(args.json_options)
		except (TypeError, ValueError) as ex:
			raise ValueError("Could not decode information: %s" % ex)

		if not isinstance(args.args, dict):
			raise ValueError("JSON dictionary required")

		if isinstance(args.testconnection, basestring) and args.testconnection == "false":
			args.testconnection = False

	def cloud_remove(self, sub_parser):
		"""
		Remove cloud connection with the name <name>
		"""
		sub_parser.add_argument(
			'name',
			help='Cloud connection name')
		sub_parser.set_defaults(protocol=protocol.Request_L_CLOUD_REMOVE)

	def cloud_list(self, sub_parser):
		"""
		List all cloud connections matching <pattern> (default="*").
		"""
		sub_parser.add_argument(
			'pattern',
			nargs='?',
			default='*',
			help='globbing filter for cloud names')
		sub_parser.set_defaults(protocol=protocol.Request_L_CLOUD_LIST)

	def cloud_instance_list(self, sub_parser):
		"""
		List all instances matching 'pattern' of cloud connections.

		:param name: cloud connection name. If given, limit results
		to instances of specified connection
		"""
		sub_parser.add_argument(
			'conn_name',
			help='Cloud connection name',
			default='*')
		sub_parser.add_argument(
			'pattern',
			nargs='?',
			default='*',
			help='globbing filter for cloud names')
		sub_parser.set_defaults(protocol=protocol.Request_L_CLOUD_INSTANCE_LIST)

	def cloud_frequency(self, sub_parser):
		"""
		Set the interval how often UVMM queries all registered or that specific
		cloud server. The frequency is given in [ms].
		"""
		sub_parser.add_argument(
			'freq',
			type=int,
			help='delay between updates in [ms]')
		sub_parser.add_argument(
			'name',
			nargs='?',
			default=None,
			help='Cloud connection name')
		sub_parser.set_defaults(protocol=protocol.Request_L_CLOUD_FREQUENCY)

	def cloud_image_list(self, sub_parser):
		"""
		List available cloud images to start new instances with.

		:param name: cloud connection name. If empty, list information from all connections
		"""
		sub_parser.add_argument(
			'conn_name',
			nargs='?',
			default='*',
			help='Cloud connection name')
		sub_parser.set_defaults(protocol=protocol.Request_L_CLOUD_IMAGE_LIST)

	def cloud_size_list(self, sub_parser):
		"""
		List available instance sizes.

		:param name: cloud connection name. If empty, list information from all connections.
		"""
		sub_parser.add_argument(
			'conn_name',
			nargs='?',
			default='*',
			help='Cloud connection name')
		sub_parser.set_defaults(protocol=protocol.Request_L_CLOUD_SIZE_LIST)

	def cloud_location_list(self, sub_parser):
		"""
		List available cloud locations.

		:param name: cloud connection name. If empty, list information from all connections
		"""
		sub_parser.add_argument(
			'conn_name',
			nargs='?',
			default='*',
			help='Cloud connection name')
		sub_parser.set_defaults(protocol=protocol.Request_L_CLOUD_LOCATION_LIST)

	def cloud_keypair_list(self, sub_parser):
		"""
		List available cloud keypairs.

		:param name: cloud connection name. If empty, list information from all connections.
		"""
		sub_parser.add_argument(
			'conn_name',
			nargs='?',
			default='*',
			help='Cloud connection name')
		sub_parser.set_defaults(protocol=protocol.Request_L_CLOUD_KEYPAIR_LIST)

	def cloud_secgroup_list(self, sub_parser):
		"""
		List available cloud security groups.

		:param name: cloud connection name. If empty, list information from all connections
		"""
		sub_parser.add_argument(
			'conn_name',
			nargs='?',
			default='*',
			help='Cloud connection name')
		sub_parser.set_defaults(protocol=protocol.Request_L_CLOUD_SECGROUP_LIST)

	def cloud_network_list(self, sub_parser):
		"""
		List available cloud networks.

		:param name: cloud connection name. If empty, list information from all connections.
		"""
		sub_parser.add_argument(
			'conn_name',
			nargs='?',
			default='*',
			help='Cloud connection name')
		sub_parser.set_defaults(protocol=protocol.Request_L_CLOUD_NETWORK_LIST)

	def cloud_subnet_list(self, sub_parser):
		"""
		List available cloud subnets.

		:param name: cloud connection name. If empty, list information from all connections.
		"""
		sub_parser.add_argument(
			'conn_name',
			nargs='?',
			default='*',
			help='Cloud connection name')
		sub_parser.set_defaults(protocol=protocol.Request_L_CLOUD_SUBNET_LIST)

	def cloud_instance_state(self, sub_parser):
		"""
		Change instance state.

		:param name: cloud connection name.
		:param instance_id: instance ID as shown by uvmm cloud_instance_list.
		:param state: desired state: 'RUN', 'PAUSE', 'SHUTDOWN', 'SHUTOFF', 'SOFTRESTART', 'RESTART', 'SUSPEND'
		"""
		sub_parser.add_argument(
			'conn_name',
			help='Cloud connection name')
		sub_parser.add_argument(
			'instance_id',
			help='Instance ID')
		sub_parser.add_argument(
			'state',
			choices=('RUN', 'PAUSE', 'SHUTDOWN', 'SHUTOFF', 'SOFTRESTART', 'RESTART', 'SUSPEND'),
			help='domain state')
		sub_parser.set_defaults(protocol=protocol.Request_L_CLOUD_INSTANCE_STATE)

	def cloud_instance_terminate(self, sub_parser):
		"""
		Terminate a cloud instance.

		:param name: cloud connection name.
		:param instance_id: instance ID as shown by uvmm cloud_instance_list.
		"""
		sub_parser.add_argument(
			'conn_name',
			help='Cloud connection name')
		sub_parser.add_argument(
			'instance_id',
			help='Instance ID')
		sub_parser.set_defaults(protocol=protocol.Request_L_CLOUD_INSTANCE_TERMINATE)

	def cloud_instance_create(self, sub_parser):
		"""
		Create a cloud instance.

		:param name: cloud connection name.
		:param config_file: file with a JSON object, describing the new instance.
		"""
		sub_parser.add_argument(
			'name',
			help='Cloud connection name')
		sub_parser.add_argument(
			'config_file',
			help='Filename with JSON data')
		sub_parser.set_defaults(pre=self._pre_cloud_instance_create, protocol=protocol.Request_L_CLOUD_INSTANCE_CREATE)

	def _pre_cloud_instance_create(self, args):
		try:
			with open(args.config_file) as f:
				args.args = json.load(f)
		except (TypeError, ValueError) as ex:
			raise ValueError("Could not decode information: %s" % ex)

		if not isinstance(args.args, dict):
			raise ValueError("JSON dictionary required")


def uuid(value):
	try:
		UUID(value)
	except ValueError:
		msg = '%r is not a valid UUID' % value
		raise ArgumentTypeError(msg)
	return value


def main():
	locale.setlocale(locale.LC_ALL, '')

	args = ParserGenerator()()

	try:
		func = args.func
	except AttributeError:
		if args.verbose:
			print >> sys.stderr, "I: No function for mode '{0.mode}'".format(args)
		func = do_request

	ret = func(args)
	sys.exit(0 if ret else 1)


def do_request(args):
	try:
		pre = args.pre
	except AttributeError:
		if args.verbose:
			print >> sys.stderr, "I: No pre-function for mode '{0.mode}'".format(args)
	else:
		pre(args)

	req = args.protocol()
	for key in vars(req):
		if key in args:
			setattr(req, key, getattr(args, key))

	try:
		ret = request(args, req)
		sys.exit(ret)
	except TranslatableException as (translatable_text, fillin):
		print >> sys.stderr, _(translatable_text) % fillin
		sys.exit(1)


if __name__ == '__main__':
	main()
