2016-02-15 05:30:43 -05:00
|
|
|
#. -*- coding: utf-8 -
|
|
|
|
# required proxmox permissions: PVESysAdmin, PVEVMAdmin
|
|
|
|
#
|
|
|
|
# afx 2015-2016
|
|
|
|
|
|
|
|
# import site packages
|
|
|
|
import logging
|
|
|
|
import falcon
|
|
|
|
import sys
|
|
|
|
import json
|
|
|
|
import urllib.parse
|
|
|
|
|
|
|
|
#import local packages
|
|
|
|
import ioconfig
|
|
|
|
import grid
|
|
|
|
import plugin
|
2016-03-07 12:25:13 -05:00
|
|
|
import clientsdb
|
2016-02-15 05:30:43 -05:00
|
|
|
|
|
|
|
config = ioconfig.parser
|
|
|
|
logger = ioconfig.logger
|
|
|
|
|
2016-04-01 21:29:22 -04:00
|
|
|
|
2016-02-15 05:30:43 -05:00
|
|
|
def welcome():
|
|
|
|
"""displays motd in log as welcome message"""
|
|
|
|
logger.info('###################################')
|
|
|
|
logger.info('# proxmaster ][ (c) 2015-2016 afx #')
|
|
|
|
logger.info('###################################')
|
|
|
|
|
2016-04-01 21:29:22 -04:00
|
|
|
|
2016-04-08 10:48:18 -04:00
|
|
|
class RequireJSON(object):
|
|
|
|
def process_request(self, req, resp):
|
|
|
|
if not req.client_accepts_json:
|
|
|
|
raise falcon.HTTPNotAcceptable(
|
|
|
|
'This API only supports responses encoded as JSON.',
|
|
|
|
href='http://docs.examples.com/api/json')
|
|
|
|
|
|
|
|
if req.method in ('POST', 'PUT'):
|
|
|
|
if 'application/json' not in req.content_type:
|
|
|
|
raise falcon.HTTPUnsupportedMediaType(
|
|
|
|
'This API only supports requests encoded as JSON.',
|
|
|
|
href='http://docs.examples.com/api/json')
|
|
|
|
|
|
|
|
|
|
|
|
class JSONTranslator(object):
|
|
|
|
def process_request(self, req, resp):
|
|
|
|
# req.stream corresponds to the WSGI wsgi.input environ variable,
|
|
|
|
# and allows you to read bytes from the request body.
|
|
|
|
#
|
|
|
|
# See also: PEP 3333
|
|
|
|
if req.content_length in (None, 0):
|
|
|
|
# Nothing to do
|
|
|
|
return
|
|
|
|
|
|
|
|
body = req.stream.read()
|
|
|
|
if not body:
|
|
|
|
raise falcon.HTTPBadRequest('Empty request body',
|
|
|
|
'A valid JSON document is required.')
|
|
|
|
|
|
|
|
try:
|
|
|
|
req.context['doc'] = json.loads(body.decode('utf-8'))
|
|
|
|
|
|
|
|
except (ValueError, UnicodeDecodeError):
|
|
|
|
raise falcon.HTTPError(falcon.HTTP_753,
|
|
|
|
'Malformed JSON',
|
|
|
|
'Could not decode the request body. The '
|
|
|
|
'JSON was incorrect or not encoded as '
|
|
|
|
'UTF-8.')
|
|
|
|
|
|
|
|
def process_response(self, req, resp, resource):
|
|
|
|
if 'result' not in req.context:
|
|
|
|
return
|
|
|
|
|
|
|
|
resp.body = json.dumps(req.context['result'])
|
|
|
|
|
|
|
|
|
|
|
|
def max_body(limit):
|
|
|
|
def hook(req, resp, resource, params):
|
|
|
|
length = req.content_length
|
|
|
|
if length is not None and length > limit:
|
|
|
|
msg = ('The size of the request is too large. The body must not '
|
|
|
|
'exceed ' + str(limit) + ' bytes in length.')
|
|
|
|
|
|
|
|
raise falcon.HTTPRequestEntityTooLarge(
|
|
|
|
'Request body is too large', msg)
|
|
|
|
|
|
|
|
return hook
|
|
|
|
|
|
|
|
|
2016-02-15 05:30:43 -05:00
|
|
|
#API methods
|
2016-04-08 10:48:18 -04:00
|
|
|
class ValidateResource(object):
|
|
|
|
@falcon.before(max_body(64 * 1024))
|
2016-03-07 12:25:13 -05:00
|
|
|
def on_post(self, req, resp):
|
2016-04-01 19:53:16 -04:00
|
|
|
""" get clientemail and password and compare it with the client db and returns a list of managed object IDs """
|
2016-04-08 21:10:07 -04:00
|
|
|
json = req.context['doc']
|
|
|
|
apipass = json['apikey']
|
|
|
|
if apipass != config.get('general', 'apipass'):
|
|
|
|
resp.status = falcon.HTTP_404
|
|
|
|
logger.error('grid> access denied. bad api key!')
|
|
|
|
return None
|
|
|
|
|
|
|
|
clientemail = json['clientemail']
|
|
|
|
passwd = json['password']
|
|
|
|
|
2016-03-30 19:26:25 -04:00
|
|
|
logger.info('grid> access requested for {} with {}'.format(clientemail, passwd))
|
2016-04-08 21:10:07 -04:00
|
|
|
|
2016-03-30 19:26:25 -04:00
|
|
|
response = clientsdb.validate(clientemail, passwd)
|
2016-04-08 21:10:07 -04:00
|
|
|
resp.status = falcon.HTTP_202
|
2016-04-08 10:48:18 -04:00
|
|
|
req.context['result'] = response
|
2016-04-08 21:10:07 -04:00
|
|
|
|
2016-03-07 12:25:13 -05:00
|
|
|
|
2016-02-15 05:30:43 -05:00
|
|
|
class ClusterResource(object):
|
|
|
|
def on_get(self, req, resp):
|
|
|
|
"""TEST ONLY. List cluster nodes. TEST ONLY"""
|
2016-04-08 21:10:07 -04:00
|
|
|
json = req.context['doc']
|
|
|
|
apipass = json['apikey']
|
|
|
|
if apipass != config.get('general', 'apipass'):
|
|
|
|
resp.status = falcon.HTTP_404
|
|
|
|
logger.error('grid> access denied. bad api key!')
|
|
|
|
return None
|
|
|
|
|
2016-02-15 05:30:43 -05:00
|
|
|
logger.info('grid> cache status')
|
|
|
|
|
2016-04-08 21:10:07 -04:00
|
|
|
response = grid.sync(False)
|
|
|
|
resp.status = falcon.HTTP_202
|
|
|
|
req.context['result'] = response
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@falcon.before(max_body(64 * 1024))
|
2016-02-15 05:30:43 -05:00
|
|
|
def on_post(self, req, resp):
|
|
|
|
"""Create a cluster node, returns array of: status, vmid, pass, ipv4, """
|
|
|
|
logger.info('grid> create ' + str(req.params))
|
|
|
|
apicheck_stat, apicheck_resp = apicheck(req.params)
|
|
|
|
if apicheck_stat:
|
|
|
|
resp.status = falcon.HTTP_200
|
|
|
|
try:
|
|
|
|
resp.body = urllib.parse.urlencode(plugin.vmcreate(req.params))
|
|
|
|
except:
|
|
|
|
logger.error('grid> create function cancelled')
|
|
|
|
raise
|
|
|
|
resp.status = falcon.HTTP_403
|
|
|
|
response = 'CREATE ERR'
|
|
|
|
resp.body = response
|
|
|
|
else:
|
|
|
|
resp.status = falcon.HTTP_403
|
|
|
|
resp.body = apicheck_resp
|
|
|
|
|
2016-04-01 21:29:22 -04:00
|
|
|
|
2016-02-15 05:30:43 -05:00
|
|
|
class StatusResource(object):
|
|
|
|
def on_get(self, req, resp, vmid):
|
|
|
|
""" check vm status """
|
|
|
|
logger.info('grid> status ' + str(vmid))
|
|
|
|
apicheck_stat, apicheck_resp = apicheck(req.params)
|
|
|
|
if apicheck_stat:
|
|
|
|
resp.status = falcon.HTTP_200
|
|
|
|
try:
|
|
|
|
resp.body = urllib.parse.urlencode(plugin.vmstatus(vmid))
|
|
|
|
except:
|
|
|
|
logger.error('grid> status error')
|
|
|
|
raise
|
|
|
|
resp.status = falcon.HTTP_403
|
|
|
|
response = 'STATUS ERR'
|
|
|
|
resp.body = response
|
|
|
|
else:
|
|
|
|
resp.status = falcon.HTTP_403
|
|
|
|
resp.body = apicheck_resp
|
|
|
|
|
2016-03-07 12:25:13 -05:00
|
|
|
|
2016-02-15 05:30:43 -05:00
|
|
|
class DeleteResource(object):
|
2016-04-08 21:10:07 -04:00
|
|
|
@falcon.before(max_body(64 * 1024))
|
2016-02-15 05:30:43 -05:00
|
|
|
def on_post(self, req, resp, vmid):
|
|
|
|
""" delete machine completely"""
|
|
|
|
logger.info('grid> delete ' + str(vmid))
|
|
|
|
apicheck_stat, apicheck_resp = apicheck(req.params)
|
|
|
|
if apicheck_stat:
|
|
|
|
try:
|
|
|
|
resp.body = urllib.parse.urlencode(plugin.vmdelete(vmid))
|
|
|
|
except:
|
|
|
|
logger.error('grid> delete error')
|
|
|
|
raise
|
|
|
|
resp.status = falcon.HTTP_403
|
|
|
|
response = 'DELETE ERR'
|
|
|
|
resp.body = response
|
|
|
|
else:
|
|
|
|
resp.status = falcon.HTTP_403
|
|
|
|
resp.body = apicheck_resp
|
|
|
|
|
2016-04-01 21:29:22 -04:00
|
|
|
|
2016-02-15 05:30:43 -05:00
|
|
|
class ArchivateResource(object):
|
2016-04-08 21:10:07 -04:00
|
|
|
@falcon.before(max_body(64 * 1024))
|
2016-02-15 05:30:43 -05:00
|
|
|
def on_post(self, req, resp, vmid):
|
|
|
|
""" Temporary suspend the instance """
|
|
|
|
logger.info('grid> suspend ' + str(vmid))
|
|
|
|
apicheck_stat, apicheck_resp = apicheck(req.params)
|
|
|
|
if apicheck_stat:
|
2016-04-08 21:10:07 -04:00
|
|
|
resp.status = falcon.HTTP_202
|
2016-02-15 05:30:43 -05:00
|
|
|
try:
|
|
|
|
resp.body = urllib.parse.urlencode(plugin.vmsuspend(vmid))
|
|
|
|
except:
|
|
|
|
logger.error('grid> pause error')
|
|
|
|
raise
|
|
|
|
resp.status = falcon.HTTP_403
|
|
|
|
response = 'PAUSE ERR'
|
|
|
|
resp.body = response
|
|
|
|
else:
|
|
|
|
resp.status = falcon.HTTP_403
|
|
|
|
resp.body = apicheck_resp
|
|
|
|
|
2016-04-01 21:29:22 -04:00
|
|
|
|
2016-02-15 05:30:43 -05:00
|
|
|
class UnArchiveResource(object):
|
2016-04-08 21:10:07 -04:00
|
|
|
@falcon.before(max_body(64 * 1024))
|
2016-02-15 05:30:43 -05:00
|
|
|
def on_post(self, req, resp, vmid):
|
|
|
|
""" Unuspend the instance """
|
|
|
|
logger.info('grid> resume ' + str(vmid))
|
|
|
|
apicheck_stat, apicheck_resp = apicheck(req.params)
|
|
|
|
if apicheck_stat:
|
2016-04-08 21:10:07 -04:00
|
|
|
resp.status = falcon.HTTP_202
|
2016-02-15 05:30:43 -05:00
|
|
|
try:
|
|
|
|
resp.body = urllib.parse.urlencode(plugin.vmresume(vmid))
|
|
|
|
except:
|
|
|
|
logger.error('grid> resume error')
|
|
|
|
raise
|
|
|
|
resp.status = falcon.HTTP_403
|
|
|
|
response = 'RESUME ERR'
|
|
|
|
resp.body = response
|
|
|
|
else:
|
|
|
|
resp.status = falcon.HTTP_403
|
|
|
|
resp.body = apicheck_resp
|
|
|
|
|
2016-04-01 21:29:22 -04:00
|
|
|
|
2016-02-15 05:30:43 -05:00
|
|
|
class StartResource(object):
|
2016-04-08 21:10:07 -04:00
|
|
|
@falcon.before(max_body(64 * 1024))
|
2016-02-15 05:30:43 -05:00
|
|
|
def on_post(self, req, resp, vmid):
|
|
|
|
""" Start the instance """
|
|
|
|
logger.info('grid> start ' + str(vmid))
|
|
|
|
apicheck_stat, apicheck_resp = apicheck(req.params)
|
|
|
|
if apicheck_stat:
|
2016-04-08 21:10:07 -04:00
|
|
|
resp.status = falcon.HTTP_202
|
2016-02-15 05:30:43 -05:00
|
|
|
try:
|
|
|
|
resp.body = urllib.parse.urlencode(plugin.vmstart(vmid))
|
|
|
|
except:
|
|
|
|
logger.error('grid> start error')
|
|
|
|
#raise
|
|
|
|
resp.status = falcon.HTTP_403
|
|
|
|
response = 'START ERR'
|
|
|
|
resp.body = response
|
|
|
|
else:
|
|
|
|
resp.status = falcon.HTTP_403
|
|
|
|
resp.body = apicheck_resp
|
|
|
|
|
|
|
|
|
|
|
|
class ShutdownResource(object):
|
2016-04-08 21:10:07 -04:00
|
|
|
@falcon.before(max_body(64 * 1024))
|
2016-02-15 05:30:43 -05:00
|
|
|
def on_post(self, req, resp, vmid):
|
|
|
|
""" ACPI Shutdown the instance """
|
|
|
|
logger.info('grid> shutdown ' + str(vmid))
|
|
|
|
apicheck_stat, apicheck_resp = apicheck(req.params)
|
|
|
|
if apicheck_stat:
|
2016-04-08 21:10:07 -04:00
|
|
|
resp.status = falcon.HTTP_202
|
2016-02-15 05:30:43 -05:00
|
|
|
try:
|
|
|
|
resp.body = urllib.parse.urlencode(plugin.vmshutdown(vmid))
|
|
|
|
#TODO: Try few times and then return proper status message
|
|
|
|
except:
|
|
|
|
logger.error('grid> shutdown error')
|
|
|
|
#raise
|
|
|
|
resp.status = falcon.HTTP_403
|
|
|
|
response = 'SHUTDOWN ERR'
|
|
|
|
resp.body = response
|
|
|
|
else:
|
|
|
|
resp.status = falcon.HTTP_403
|
|
|
|
resp.body = apicheck_resp
|
|
|
|
|
|
|
|
|
|
|
|
class StopResource(object):
|
2016-04-08 21:10:07 -04:00
|
|
|
@falcon.before(max_body(64 * 1024))
|
2016-02-15 05:30:43 -05:00
|
|
|
def on_post(self, req, resp, vmid):
|
|
|
|
""" Stop the instance """
|
|
|
|
logger.info('grid> stop ' + str(vmid))
|
|
|
|
apicheck_stat, apicheck_resp = apicheck(req.params)
|
|
|
|
if apicheck_stat:
|
2016-04-08 21:10:07 -04:00
|
|
|
resp.status = falcon.HTTP_202
|
2016-02-15 05:30:43 -05:00
|
|
|
try:
|
|
|
|
resp.body = urllib.parse.urlencode(plugin.vmstop(vmid))
|
|
|
|
except:
|
|
|
|
logger.error('grid> stop error')
|
|
|
|
#raise
|
|
|
|
resp.status = falcon.HTTP_403
|
|
|
|
response = 'STOP ERR'
|
|
|
|
resp.body = response
|
|
|
|
else:
|
|
|
|
resp.status = falcon.HTTP_403
|
|
|
|
resp.body = apicheck_resp
|
|
|
|
|
|
|
|
class VNCResource(object):
|
2016-04-08 21:10:07 -04:00
|
|
|
@falcon.before(max_body(64 * 1024))
|
2016-02-15 05:30:43 -05:00
|
|
|
def on_post(self, req, resp, vmid):
|
|
|
|
""" Create a VNC link to the instance """
|
|
|
|
apicheck_stat, apicheck_resp = apicheck(req.params)
|
|
|
|
logger.info('grid> vnc ' + str(vmid))
|
|
|
|
if apicheck_stat:
|
|
|
|
try:
|
2016-04-08 21:10:07 -04:00
|
|
|
resp.status = falcon.HTTP_202
|
2016-02-15 05:30:43 -05:00
|
|
|
resp.body = urllib.parse.urlencode(plugin.vmvnc(vmid))
|
|
|
|
except:
|
|
|
|
logger.error('grid> vnc error')
|
|
|
|
raise
|
|
|
|
resp.status = falcon.HTTP_403
|
|
|
|
response = 'VNC ERR'
|
|
|
|
resp.body = response
|
|
|
|
else:
|
|
|
|
resp.status = falcon.HTTP_403
|
|
|
|
resp.body = apicheck_resp
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
sys.exit("invoke proxmaster via uwsgi. thanks. bye. o/")
|
|
|
|
|
|
|
|
#setup routes
|
2016-04-08 10:48:18 -04:00
|
|
|
wsgi_app = api = application = falcon.API(middleware=[
|
|
|
|
RequireJSON(),
|
|
|
|
JSONTranslator(),
|
|
|
|
])
|
2016-02-15 05:30:43 -05:00
|
|
|
|
|
|
|
#display motd
|
|
|
|
welcome()
|
|
|
|
#logger.info('grid> sync')
|
|
|
|
#grid.sync()
|
|
|
|
|
|
|
|
# setup routes
|
2016-04-08 10:48:18 -04:00
|
|
|
res_validate = ValidateResource()
|
|
|
|
api.add_route('/validate', res_validate)
|
2016-03-07 12:25:13 -05:00
|
|
|
|
2016-02-15 05:30:43 -05:00
|
|
|
res_cluster = ClusterResource()
|
|
|
|
api.add_route('/instance', res_cluster)
|
|
|
|
|
|
|
|
res_status = StatusResource()
|
|
|
|
api.add_route('/instance/{vmid}', res_status)
|
|
|
|
|
|
|
|
res_delete = DeleteResource()
|
|
|
|
api.add_route('/instance/delete/{vmid}', res_delete)
|
|
|
|
|
|
|
|
res_archivate = ArchivateResource()
|
|
|
|
api.add_route('/instance/archivate/{vmid}', res_archivate)
|
|
|
|
|
|
|
|
res_unarchive = UnArchiveResource()
|
|
|
|
api.add_route('/instance/unarchive/{vmid}', res_unarchive)
|
|
|
|
|
|
|
|
res_start = StartResource()
|
|
|
|
api.add_route('/instance/start/{vmid}', res_start)
|
|
|
|
|
|
|
|
res_shutdown = ShutdownResource()
|
|
|
|
api.add_route('/instance/shutdown/{vmid}', res_shutdown)
|
|
|
|
|
|
|
|
res_stop = StopResource()
|
|
|
|
api.add_route('/instance/stop/{vmid}', res_stop)
|
|
|
|
|
|
|
|
res_vnc = VNCResource()
|
|
|
|
api.add_route('/instance/vnc/{vmid}', res_vnc)
|
|
|
|
|