#!/usr/bin/env python
import sqlalchemy
import datetime
import json
import web

"""
REST service that provides information about cloud resources, machine types,
and project quotas.  Should be run on each management node in the cloud.
"""

NOVA_CONF = '/etc/nova/nova.conf'
URL_PREFIX = '/nova-info'

urls = (
	'%s/resources' % URL_PREFIX, 'Resources',
	'%s/machinetypes' % URL_PREFIX, 'MachineTypes',
	'%s/quotas/(.*)' % URL_PREFIX, 'Quotas'
)

web.config.debug = False
app = web.application(urls, globals())

max_cores = None
max_gigabytes = None
with open(NOVA_CONF, 'r') as f:
	for line in f:
		if line.startswith('--sql_connection'):
			db_url = line.strip().split('=', 1)[1]
		elif line.startswith('--max_cores'):
			max_cores = int(line.strip().split('=', 1)[1])
		elif line.startswith('--max_gigabytes'):
			max_gigabytes = int(line.strip().split('=', 1)[1])

engine = sqlalchemy.create_engine(db_url, pool_recycle=7200)

class CachedContent():
	EXPIRY_TIME = datetime.timedelta(seconds=10)

	def __init__(self):
		self.last_refreshed = datetime.datetime.today() - self.EXPIRY_TIME

	def on_expire(self, *args):
		"""Implement to refresh self.content after expiry"""
		raise NotImplementedError

	def on_get(self, *args):
		"""Override to perform any actions needed per GET"""
		pass

	def format(self, *args):
		"""Override to modify output formatting"""
		web.header('Content-Type', 'application/json; charset=utf-8')
		return json.dumps(self.content)

	def GET(self, *args):
		time_since_refresh = datetime.datetime.today() - self.last_refreshed
		if time_since_refresh > self.EXPIRY_TIME:
			self.on_expire(*args)
			self.last_refreshed = datetime.datetime.today()

		self.on_get(*args)
		return self.format(*args)

class Resources(CachedContent):
	def on_expire(self):
		node_usage = engine.execute(
			"SELECT host, "
			"vcpus,vcpus_used, "
			"memory_mb,memory_mb_used, "
			"memory_mb-memory_mb_used AS memory_mb_avail, "
			"local_gb,local_gb_used "
			"FROM compute_nodes "
			"INNER JOIN services AS s ON service_id=s.id "
			"WHERE s.binary='nova-compute' AND s.deleted=0 AND s.disabled=0"
		)
	
		resources = {}	
		resources['nodes'] = {}
		for row in node_usage:
			node = resources['nodes'][row['host']] = dict(row.items())
			node['vcpus'] = max_cores * row['vcpus']
			node['vcpus_avail'] = node['vcpus'] - node['vcpus_used']
			node['local_gb'] = max_gigabytes or row['local_gb']
			node['local_gb_avail'] = node['local_gb'] - node['local_gb_used']
			del node['host']
		
		resources['total'] = {}
		for node in resources['nodes'].itervalues():
			for (key, value) in node.items():
				try:
					resources['total'][key] += value
				except KeyError:
					resources['total'][key] = value

		node_usage.close()
		self.content = resources

class MachineTypes(CachedContent):
	EXPIRY_TIME = datetime.timedelta(seconds=60)

	def on_expire(self):
		result = engine.execute(
			"SELECT name,vcpus,memory_mb,local_gb,swap "
			"FROM instance_types "
			"WHERE deleted=0"
		)

		machine_types = {}
		for row in result:
			machine_types[row['name']] = dict(row.items())
			del machine_types[row['name']]['name']

		result.close()
		self.content = machine_types

class Quotas(CachedContent):
	EXPIRY_TIME = datetime.timedelta(seconds=60)

	def on_expire(self, *args):
		self.content = {}

	def on_get(self, *args):
		project = args[0]

		if self.content.has_key(project):
			return

		conn = engine.connect()

		total = conn.execute(
			"SELECT instances,cores,volumes,gigabytes,floating_ips "
			"FROM quotas "
			"WHERE project_id=%(project)s AND deleted=0",
			project=project
		)

		quotas = {}
		try:
			quotas['total'] = dict(total.first())
		except TypeError:
			conn.close()
			raise web.notfound()

		used_compute = conn.execute(
			"SELECT COUNT(1) as instances, "
			"IFNULL(CAST(SUM(vcpus) AS SIGNED), 0) as cores "
			"FROM instances "
			"WHERE project_id=%(project)s AND deleted=0",
			project=project
		)

		used_volume = conn.execute(
			"SELECT COUNT(1) as volumes, "
			"IFNULL(CAST(SUM(size) AS SIGNED), 0) as gigabytes "
			"FROM volumes "
			"WHERE project_id=%(project)s AND deleted=0",
			project=project
		)

		used_ip = conn.execute(
			"SELECT COUNT(1) as floating_ips "
			"FROM floating_ips "
			"WHERE project_id=%(project)s AND deleted=0",
			project=project
		)

		quotas['used'] = dict(used_compute.first().items() + used_volume.first().items() + used_ip.first().items())
		
		conn.close()
		self.content[project] = quotas

	def format(self, *args):
		project = args[0]
		web.header('Content-Type', 'application/json; charset=utf-8')
		return json.dumps(self.content[project])

if __name__ == '__main__':
	app.run()
