#!/usr/bin/python2.7
# -*- coding: utf-8 -*-
#
# UCS Virtual Machine Manager Daemon
#  UVMM commandline tool
#
# Copyright 2010-2014 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 os
import locale
import json
from optparse import OptionParser
from univention.uvmm import protocol, client
from univention.uvmm.helpers import TranslatableException, _, N_
import PAM
from getpass import getpass
from pprint import pprint
try:
	import xml.etree.ElementTree as ET
except ImportError:
	import elementtree.ElementTree as ET
__builtins__._ = _

class ParameterModeError(TranslatableException):
	"""Unknown mode."""

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

	Either SSL, TCP or UNIX."""
	if options.ssl:
		if ':' in options.ssl:
			host, port = options.ssl.rsplit(':', 1)
		else:
			host, port = options.ssl, 2106
		sock = client.UVMM_ClientSSLSocket(host, int(port), tcp_timeout=options.timeout, ssl_timeout=options.timeout)
	elif options.tcp:
		if ':' in options.tcp:
			host, port = options.tcp.rsplit(':', 1)
		else:
			host, port = options.tcp, 2105
		sock = client.UVMM_ClientTCPSocket(host, int(port), timeout=options.timeout)
	else:
		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)
		while isinstance(res, protocol.Response_AUTHENTICATION):
			resp = []
			for query, type in res.challenge:
				if type == PAM.PAM_PROMPT_ECHO_ON:
					val = raw_input(query)
					resp.append((val, PAM.PAM_SUCCESS))
				elif type == PAM.PAM_PROMPT_ECHO_OFF:
					val = getpass(query)
					resp.append((val, PAM.PAM_SUCCESS))
				elif type == PAM.PAM_PROMPT_ERROR_MSG:
					print >>sys.stderr, query
					resp.append(('', PAM.PAM_SUCCESS))
				elif type == PAM.PAM_PROMPT_TEXT_INFO:
					print >>sys.stdout, query
					resp.append(('', PAM.PAM_SUCCESS))
			req2 = protocol.Request_AUTHENTICATION(response=resp)
			res = sock.send(req2)
			if isinstance(res, protocol.Response_OK):
				# repeat original request
				res = sock.send(req)
		if isinstance(res, protocol.Response_DUMP):
			print "DATA:"
			try:
				def dump(obj):
					if isinstance(obj, list):
						return map(dump, obj)
					elif isinstance(obj, dict):
						return dict(map(lambda (k, v): (k, dump(v)), obj.items()))
					elif hasattr(obj, '__dict__'):
						return dict(map(lambda (k, v): (k, dump(v)), obj.__dict__.items()))
					else:
						return obj
				pprint(dump(res.data))
			except AttributeError, e:
				print str(res)
			except Exception, e:
				import traceback
				traceback.print_exc()
				print 'Exception', e
				print str(res)
		elif isinstance(res, protocol.Response_ERROR):
			raise TranslatableException(res.translatable_text, res.values)
		else:
			print "OK."
	finally:
		sock.close()

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, (translatable_text, dict):
		print >>sys.stderr, "ERROR:"
		print >>sys.stderr, _(translatable_text) % dict
		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, (translatable_text, dict):
			print >>sys.stderr, _(translatable_text) % dict
			result = False
	sock.close()

	return result


class Client(object):
	"""Implementation of client commands."""
	def __init__(self, options):
		"""Save options."""
		self._options = options

	@classmethod
	def _modes(self):
		"""Return list of commands."""
		return [mode for mode in sorted(dir(self)) if not mode.startswith('_')]

	@classmethod
	def _usage(self, mode=None):
		"""Show usage informations for mode."""
		if mode:
			cmd = getattr(self, mode)
			doc = cmd.__doc__
			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]
		else:
			desc = [self._usage(mode)[0] for mode in self._modes()]
		return desc

	def __call__(self, mode, *arguments):
		"""Execute command <mode>(*arguments)."""
		try:
			cmd = getattr(self, mode)
		except AttributeError, e:
			desc = '\n'.join(Client._usage())
			raise ParameterModeError(N_('Unknown mode "%(mode)s"'), mode=mode)

		try:
			ret = cmd(*arguments)
		except TypeError, e:
			# if len(arguments) < cmd.__code__.co_argcount:
			desc = '\n'.join(Client._usage(mode))
			raise ParameterModeError(N_('Insufficient arguments'), desc=desc)
		if isinstance(ret, protocol.Request):
			return request(options, ret)
		return ret

	def ldap(self):
		"""
		%prog ldap

		Query LDAP for all virtualization servers and add them to the UVMM
		"""
		result = from_ldap(options)
		return result and 0 or 1

	def add(self, uri):
		"""
		%prog add <uri>

		Add the virtualization server specified by <uri> to the UVMM.
		Examples:
			%prog add qemu://server.domain.name/system
		"""
		return protocol.Request_NODE_ADD(uri=uri)

	def remove(self, uri):
		"""
		%prog remove <uri>

		Remove the virtualization server specified by <uri> from the UVMM.
		Examples:
			%prog remove qemu://server.domain.name/system
		"""
		return protocol.Request_NODE_REMOVE(uri=uri)

	def query(self, uri):
		"""
		%prog query <uri>

		Query virtualization server specifies by <uri> for all its domains and
		storage pools.
		Examples:
			%prog query qemu://server.domain.name/system
		"""
		return protocol.Request_NODE_QUERY(uri=uri)

	def frequency(self, frequency, uri=None):
		"""
		%prog frequency <frequency> [uri]

		Set the interval how often UVMM queries all registers or that specific
		virtualization server. The frequency is given in [ms].
		Examples:
			%prog frequency 15000
			%prog frequency 15000 qemu://server.domain.name/system
		"""
		return protocol.Request_NODE_FREQUENCY(hz=frequency, uri=uri)

	def groups(self):
		"""
		%prog groups

		Return the names of groups of virtualization server.
		Examples:
			%prog groups
		"""
		return protocol.Request_GROUP_LIST()

	def nodes(self, group, pattern='*'):
		"""
		%prog nodes <group> [pattern]

		Return the list of uris used to specify the virtualization server,
		which belong to the given <group>.
		<pattern> is an optional globbing pattern for filtering the host names.
		Examples:
			%prog nodes default *
		"""
		return protocol.Request_NODE_LIST( group = group, pattern = pattern )

	def bye(self):
		"""
		%prog bye

		End the connection. Only useful for debugging.
		Examples:
			%prog bye
		"""
		return protocol.Request_BYE()

	def save(self, uri, domain, statefile):
		"""
		%prog save <uri> <domain> <statefile>

		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.
		Examples:
			%prog save qemu://server.domain.name/system 1-2-3-4-5 /var/lib/libvirt/save/12345.save
		"""
		return protocol.Request_DOMAIN_SAVE(uri=uri, domain=domain, statefile=statefile)

	def restore(self, uri, statefile):
		"""
		%prog restore <uri> <statefile>

		On the virtualization server specified by <uri> restore a domain
		locally from the file <statefile>.
		Examples:
			%prog restore qemu://server.domain.name/system /var/lib/libvirt/save/12345.save
		"""
		return protocol.Request_DOMAIN_RESTORE(uri=uri, statefile=statefile)

	def migrate(self, uri, domain, target_uri):
		"""
		%prog migrate <uri> <domain> <target_uri>

		Migrate the domain <domain> from the virtualization server specifies by
		<uri> to the virtualization server specifies by <target_uri>.
		Examples:
			%prog migrate qemu://server.domain.name/system 1-2-3-4-5 qemu://server2.domain.name/system
		"""
		return protocol.Request_DOMAIN_MIGRATE(uri=uri, domain=domain, target_uri=target_uri)

	def domains( self, uri, pattern = '*' ):
		"""
		%prog domains <uri> [pattern]

		Return a list of available domains on the given node
		<pattern> is an optional globbing pattern for filtering the domain names.
		Examples:
			%prog domains qemu://server.domain.name/system *
		"""
		return protocol.Request_DOMAIN_LIST( uri = uri, pattern = pattern )

	def domain_info( self, uri, domain ):
		"""
		%prog domain <uri> <domain>

		Return detailed information about a domain
		Examples:
			%prog domain_info qemu://server.domain.name/system a0087e58-92db-6457-679a-ed81a43defcb
		"""
		return protocol.Request_DOMAIN_INFO( uri = uri, domain = domain )

	def state(self, uri, domain, state):
		"""
		%prog state <uri> <domain> <state>

		Change the state of domain <domain> on the virtualization server specifies by
		<uri> to the given state <state>, which can be one of
		  RUN, PAUSE, SUSPEND, RESTART, SHUTDOWN, SHUTOFF
		Examples:
			%prog state qemu://server.domain.name/system 1-2-3-4-5 PAUSE
		"""
		return protocol.Request_DOMAIN_STATE(uri=uri, domain=domain, state=state)

	def define(self, uri, file_or_xml):
		"""
		%prog define <uri> <file_or_xml>

		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.
		Examples:
			%prog define qemu://server.domain.name/system ./domain.xml
			%prog define qemu://server.domain.name/system '<domain type="qemu"><name>Name</name>...</domain>'
		"""
		if file_or_xml.startswith('<'):
			root = ET.fromstring(file_or_xml)
		else:
			tree = ET.parse(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 = name=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__)
		return protocol.Request_DOMAIN_DEFINE(uri=uri, domain=domain)

	def undefine(self, uri, domain, *volumes):
		"""
		%prog undefine <uri> <domain> [volumes...]

		Undefine the domain <domain> on the virtualization server specifies 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.
		Examples:
			%prog undefine qemu://server.domain.name/system 1-2-3-4-5
			%prog undefine qemu://server.domain.name/system 1-2-3-4-5 ALL ALL
		"""
		if ('ALL', 'ALL') == volumes:
			volumes = None
		return protocol.Request_DOMAIN_UNDEFINE(uri=uri, domain=domain, volumes=volumes)

	def pools(self, uri):
		"""
		%prog pools <uri>

		Return the names of all storage-pools on the virtualization server
		specified by <uri>.
		Examples:
			%prog pools qemu://server.domain.name/system
		"""
		return protocol.Request_STORAGE_POOLS(uri=uri)

	def volumes(self, uri, pool, type=None):
		"""
		%prog volumes <uri> <pool> [type]

		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'
		Examples:
			%prog volumes qemu://server.domain.name/system default
		"""
		return protocol.Request_STORAGE_VOLUMES(uri=uri, pool=pool, type=type)

	def volume_define(self, uri, pool, name, size):
		"""
		%prog volume_define <uri> <pool> <name> <size>

		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>.
		Examples:
			%prog volume_define qemu://server.domain.name/system default test 8000000000
		"""
		return protocol.Request_STORAGE_DEFINE(uri=uri, pool=pool, name=name, size=size)

	def volume_used(self, volume):
		"""
		%prog volume_used <volume>

		Return a list of all (uri, domain)s using the given <volume>.
		Example:
			%prog volume_used /var/lib/libvirt/images/ucs_3.0-0-latest-amd64.iso
		"""
		return protocol.Request_STORAGE_VOLUME_USEDBY(volume=volume)

	def snap_create(self, uri, domain, snapshot):
		"""
		%prog snap_create <uri> <domain> <snapshot>

		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.
		Examples:
			%prog snap_create qemu:///system 1-2-3-4-5 before-update
		"""
		return protocol.Request_DOMAIN_SNAPSHOT_CREATE(uri=uri, domain=domain, snapshot=snapshot)

	def snap_revert(self, uri, domain, snapshot):
		"""
		%prog snap_revert <uri> <domain> <snapshot>

		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.
		Examples:
			%prog snap_revert qemu:///system 1-2-3-4-5 before-update
		"""
		return protocol.Request_DOMAIN_SNAPSHOT_REVERT(uri=uri, domain=domain, snapshot=snapshot)

	def snap_delete(self, uri, domain, snapshot):
		"""
		%prog snap_delete <uri> <domain> <snapshot>

		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.
		Examples:
			%prog snap_delete qemu:///system 1-2-3-4-5 before-update
		"""
		return protocol.Request_DOMAIN_SNAPSHOT_DELETE(uri=uri, domain=domain, snapshot=snapshot)

	def domain_update(self, domain):
		"""
		%prog domain_update <domain>

		Trigger an update of the domain specified by its UUID <domain>.
		Examples:
			%prog domain_update 1-2-3-4-5
		"""
		return protocol.Request_DOMAIN_UPDATE(domain=domain)

	def domain_clone(self, uri, domain, name, *subst):
		"""
		%prog clone <uri> <domain> <name> <subst>...

		On the virtualization server specified by <uri> clone the instance
		<domain> with the new name <name>.
		<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.
		Examples:
			%prog clone qemu://server.domain.name/system 1-2-3-4-5 test mac#1:2:3:4:5:6=2:3:4:5:6 copy#vda=cow
		"""
		_subst = dict([kv.split('=', 1) for kv in subst])
		p = protocol.Request_DOMAIN_CLONE(uri=uri, domain=domain, name=name, subst=_subst)
		return p

	def cloud_add(self, json_options, testconnection=True):
		"""
		%prog cloud_add <json_options> [<testconnection>]

		Define new cloud connection
		<json_options> defines connection name, credentials, URL, etc. Example:
		%prog cloud_add "{"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"}"
		<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
		"""
		try:
			if json_options.startswith('"'):  # json string
				args = json.loads(json_options)
			else:  # filename
				with open(json_options) as f:
					args = json.load(f)

			if not isinstance(args, dict):
				raise ValueError("JSON object required")
		except (TypeError, ValueError) as e:
			raise ValueError("Could not decode information: %s" % e)

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

		return protocol.Request_L_CLOUD_ADD(args=args, testconnection=testconnection)

	def cloud_remove(self, name):
		"""
		%prog cloud_remove name

		Remove cloud connection with the name <name>
		"""
		return protocol.Request_L_CLOUD_REMOVE(name=name)

	def cloud_list(self, pattern="*"):
		"""
		%prog cloud_list [<pattern>]

		List all cloud connections matching <pattern> (default="*")
		"""
		return protocol.Request_L_CLOUD_LIST(pattern=pattern)

	def cloud_instance_list(self, conn_name="*", pattern="*"):
		"""
		%prog cloud_instance_list [<name>] [<pattern>]

		List all instances matching 'pattern' of cloud connections
		name is a cloud connection name. If given, limit results
		to instances of specified connection
		"""
		return protocol.Request_L_CLOUD_INSTANCE_LIST(conn_name=conn_name, pattern=pattern)

	def cloud_frequency(self, frequency, name=None):
		"""
		%prog cloud_frequency <frequency> [name]

		Set the interval how often UVMM queries all registered or that specific
		cloud server. The frequency is given in [ms].
		Examples:
			%prog frequency 15000
			%prog frequency 15000 localOpenStack
		"""
		return protocol.Request_L_CLOUD_FREQUENCY(freq=frequency, name=name)

	def cloud_image_list(self, name="*"):
		"""
		%prog cloud_image_list <name>

		List available cloud images matching <pattern> to start new instances with
		name is the cloud connection name. If empty, list information from all connections
		"""
		return protocol.Request_L_CLOUD_IMAGE_LIST(conn_name=name)

	def cloud_size_list(self, name="*"):
		"""
		%prog cloud_size_list <name>

		List available instance sizes
		name is the cloud connection name. If empty, list information from all connections
		"""
		return protocol.Request_L_CLOUD_SIZE_LIST(conn_name=name)

	def cloud_location_list(self, name="*"):
		"""
		%prog cloud_location_list <name>

		List available cloud locations
		name is the cloud connection name. If empty, list information from all connections
		"""
		return protocol.Request_L_CLOUD_LOCATION_LIST(conn_name=name)

	def cloud_keypair_list(self, name="*"):
		"""
		%prog cloud_keypair_list <name>

		List available cloud keypairs
		name is the cloud connection name. If empty, list information from all connections
		"""
		return protocol.Request_L_CLOUD_KEYPAIR_LIST(conn_name=name)

	def cloud_secgroup_list(self, name="*"):
		"""
		%prog cloud_secgroup_list <name>

		List available cloud security groups
		name is the cloud connection name. If empty, list information from all connections
		"""
		return protocol.Request_L_CLOUD_SECGROUP_LIST(conn_name=name)

	def cloud_network_list(self, name="*"):
		"""
		%prog cloud_network_list <name>

		List available cloud networks
		name is the cloud connection name. If empty, list information from all connections
		"""
		return protocol.Request_L_CLOUD_NETWORK_LIST(conn_name=name)

	def cloud_instance_state(self, name, instance_id, state):
		"""
		%prog cloud_instance_state <name> <instance_id> <state>

		Change instance state
		name is the cloud connection name. If empty, list information from all connections
		instance_id is the instance ID as shown by uvmm cloud_instance_list
		state is the desired state: 'RUN', 'PAUSE', 'SHUTDOWN', 'SHUTOFF', 'SOFTRESTART', 'RESTART', 'SUSPEND'
		"""
		return protocol.Request_L_CLOUD_INSTANCE_STATE(conn_name=name, instance_id=instance_id, state=state)

	def cloud_instance_terminate(self, name, instance_id):
		"""
		%prog cloud_instance_terminate <name> <instance_id>

		Terminate a cloud instance
		name is the cloud connection name
		instance_id is the instance ID as shown by uvmm cloud_instance_list
		"""
		return protocol.Request_L_CLOUD_INSTANCE_TERMINATE(conn_name=name, instance_id=instance_id)

	def cloud_instance_create(self, name, config_file):
		"""
		%prog cloud_instance_create <name> <config_file>

		Create a cloud instance
		name is the cloud connection name
		config_file is a file with a json object, describing the new instance
		"""
		try:
			with open(config_file) as f:
				args = json.load(f)

			if not isinstance(args, dict):
				raise ValueError("JSON object required")
		except (TypeError, ValueError) as e:
			raise ValueError("Could not decode information: %s" % e)
		return protocol.Request_L_CLOUD_INSTANCE_CREATE(conn_name=name, args=args)


if __name__ == '__main__':
	locale.setlocale(locale.LC_ALL, '')

	progname = os.path.basename(sys.argv[0])
	modes = Client._modes()
	try:
		mode = progname[1 + progname.rindex('-'):]
		usage = '\n'.join(Client._usage(mode)[0])
	except:
		mode = None
		usage = _("usage: %%prog [options] {%(modes)s} uri") % {'modes':','.join(modes)}

	parser = OptionParser(usage=usage, add_help_option=False, prog=progname)
	parser.add_option('-h', '--help',
			action='store_true', dest='help',
			help=_("show this help message and exit"))
	parser.add_option('-u', '--unix',
			action='store', dest='socket', default="/var/run/uvmm.socket",
			help=_('Path to the UNIX socket'))
	parser.add_option('-t', '--tcp',
			action='store', dest='tcp', default=None, metavar=_("HOST:PORT"),
			help=_('HOST and PORT number of the TCP socket'))
	parser.add_option('-s', '--ssl',
			action='store', dest='ssl', default=False, metavar=_("HOST:PORT"),
			help=_('HOST and PORT number of the SSL encrypted TCP socket'))
	parser.add_option( '-v', '--verbose',
			action='store_true', dest='verbose', default=False,
			help=_('Print additional information'))
	parser.add_option( '-T', '--timeout',
			action='store', dest='timeout', default=0, type="int",
			help=_('Timeout in seconds for UVMM commands'))

	(options, arguments) = parser.parse_args()

	try:
		try:
			if mode is None:
				mode = arguments.pop(0)
			if options.verbose:
				print >>sys.stderr, "mode=%s" % (mode,)
			usage = Client._usage(mode)
		except IndexError:
			raise ParameterModeError(N_('No mode specified'))
		except AttributeError:
			raise ParameterModeError(N_('Unknown mode "%(mode)s"'), mode=mode)

		if options.help:
			parser.set_usage('\n'.join(usage))
			parser.print_help()
			parser.exit()
		parser.set_usage(usage[0])

		c = Client(options)
		c(mode, *arguments)
		sys.exit(0)
	except ParameterModeError, (translatable_text, dict):
		if options.help:
			parser.print_help()
			parser.exit()
		parser.error(_(translatable_text) % dict)
		sys.exit(2)
	except TranslatableException, (translatable_text, dict):
		print >>sys.stderr, "ERROR:"
		print >>sys.stderr, _(translatable_text) % dict
	sys.exit(1)
