diff --git a/IM/REST.py b/IM/REST.py index 91b28f330..f04f8853d 100644 --- a/IM/REST.py +++ b/IM/REST.py @@ -18,8 +18,11 @@ import threading import json import base64 -import bottle +import flask +from cheroot.wsgi import Server as WSGIServer, PathInfoDispatcher +from cheroot.ssl.builtin import BuiltinSSLAdapter +from werkzeug.middleware.proxy_fix import ProxyFix from IM.InfrastructureInfo import IncorrectVMException, DeletedVMException, IncorrectStateException from IM.InfrastructureManager import (InfrastructureManager, DeletedInfrastructureException, IncorrectInfrastructureException, UnauthorizedUserException, @@ -53,121 +56,41 @@ """ REST_URL = None - -app = bottle.Bottle() -bottle_server = None - -# Declaration of new class that inherits from ServerAdapter -# It's almost equal to the supported cherrypy class CherryPyServer - - -class MySSLCherryPy(bottle.ServerAdapter): - - def run(self, handler): - try: - # First try to use the new version - from cheroot.ssl.pyopenssl import pyOpenSSLAdapter - from cheroot import wsgi - server = wsgi.Server((self.host, self.port), handler, request_queue_size=32) - except Exception: - from cherrypy.wsgiserver.ssl_pyopenssl import pyOpenSSLAdapter - from cherrypy import wsgiserver - server = wsgiserver.CherryPyWSGIServer((self.host, self.port), handler, request_queue_size=32) - - self.srv = server - - # If cert variable is has a valid path, SSL will be used - # You can set it to None to disable SSL - server.ssl_adapter = pyOpenSSLAdapter(Config.REST_SSL_CERTFILE, - Config.REST_SSL_KEYFILE, - Config.REST_SSL_CA_CERTS) - try: - server.start() - finally: - server.stop() - - def shutdown(self): - self.srv.stop() - - -class MyCherryPy(bottle.ServerAdapter): - - def run(self, handler): - try: - # First try to use the new version - from cheroot import wsgi - server = wsgi.Server((self.host, self.port), handler, request_queue_size=32) - except Exception: - from cherrypy import wsgiserver - server = wsgiserver.CherryPyWSGIServer((self.host, self.port), handler, request_queue_size=32) - - self.srv = server - try: - server.start() - finally: - server.stop() - - def shutdown(self): - self.srv.stop() +app = flask.Flask(__name__) +app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_port=1, x_prefix=1) +flask_server = None def run_in_thread(host, port): - bottle_thr = threading.Thread(target=run, args=(host, port)) - bottle_thr.daemon = True - bottle_thr.start() + flask_thr = threading.Thread(target=run, args=(host, port)) + flask_thr.daemon = True + flask_thr.start() def run(host, port): - global bottle_server + global flask_server + flask_server = WSGIServer((host, port), PathInfoDispatcher({'/': app})) if Config.REST_SSL: - # Add our new MySSLCherryPy class to the supported servers - # under the key 'mysslcherrypy' - bottle_server = MySSLCherryPy(host=host, port=port) - bottle.run(app, host=host, port=port, server=bottle_server, quiet=True) - else: - bottle_server = MyCherryPy(host=host, port=port) - bottle.run(app, server=bottle_server, quiet=True) + flask_server.ssl_adapter = BuiltinSSLAdapter(Config.REST_SSL_CERTFILE, + Config.REST_SSL_KEYFILE, + Config.REST_SSL_CA_CERTS) + flask_server.start() def return_error(code, msg): content_type = get_media_type('Accept') if "application/json" in content_type: - bottle.response.status = code - bottle.response.content_type = "application/json" - return json.dumps({'message': msg, 'code': code}) + return flask.Response(json.dumps({'message': msg, 'code': code}), status=code, mimetype='application/json') elif "text/html" in content_type: - bottle.response.status = code - bottle.response.content_type = "text/html" - return HTML_ERROR_TEMPLATE % (code, code, msg) + return flask.Response(HTML_ERROR_TEMPLATE % (code, code, msg), status=code, mimetype='text/html') else: - bottle.response.status = code - bottle.response.content_type = 'text/plain' - return msg - - -def get_full_url(path): - """ - Get the full URL to be returned by the API calls - """ - protocol = "http://" - if Config.REST_SSL: - protocol = "https://" - - # if it is a forwarded call use the original protocol - if 'HTTP_X_FORWARDED_PROTO' in bottle.request.environ and bottle.request.environ['HTTP_X_FORWARDED_PROTO']: - protocol = bottle.request.environ['HTTP_X_FORWARDED_PROTO'] + "://" - - # if it is a forwarded call add the original prefix - if 'HTTP_X_FORWARDED_PREFIX' in bottle.request.environ and bottle.request.environ['HTTP_X_FORWARDED_PREFIX']: - path = bottle.request.environ['HTTP_X_FORWARDED_PREFIX'].rstrip('/') + path - - return protocol + bottle.request.environ['HTTP_HOST'] + path + return flask.Response(msg, status=code, mimetype='text/plain') def stop(): - if bottle_server: - bottle_server.shutdown() + logger.info('Stopping REST API server...') + flask_server.stop() def get_media_type(header): @@ -176,7 +99,7 @@ def get_media_type(header): Returns a List of strings. """ res = [] - accept = bottle.request.headers.get(header) + accept = flask.request.headers.get(header) if accept: media_types = accept.split(",") for media_type in media_types: @@ -199,9 +122,9 @@ def get_auth_header(): # Initialize REST_URL global REST_URL if REST_URL is None: - REST_URL = get_full_url("") + REST_URL = flask.request.url_root - auth_header = bottle.request.headers['AUTHORIZATION'] + auth_header = flask.request.headers['AUTHORIZATION'] user_pass = None token = None @@ -265,76 +188,71 @@ def format_output_json(res, field_name=None, list_field_name=None): return json.dumps(res_dict) -def format_output(res, default_type="text/plain", field_name=None, list_field_name=None): +def format_output(res, default_type="text/plain", field_name=None, list_field_name=None, extra_headers=None): """ Format the output of the API responses """ accept = get_media_type('Accept') - if accept: - content_type = None - for accept_item in accept: - if accept_item in ["application/json", "application/*"]: - if isinstance(res, RADL): - if field_name: - res_dict = {field_name: radlToSimple(res)} - info = json.dumps(res_dict) - else: - info = dump_radl_json(res, enter="", indent="") - # This is the case of the "contains" properties - elif isinstance(res, dict) and all(isinstance(x, Feature) for x in res.values()): - features = Features() - features.props = res - res_dict = featuresToSimple(features) - if field_name: - res_dict = {field_name: res_dict} + if not accept: + accept = [default_type] + + content_type = None + for accept_item in accept: + if accept_item in ["application/json", "application/*"]: + if isinstance(res, RADL): + if field_name: + res_dict = {field_name: radlToSimple(res)} info = json.dumps(res_dict) else: - # Always return a complex object to make easier parsing - # steps - info = format_output_json(res, field_name, list_field_name) - content_type = "application/json" - break - elif accept_item in [default_type, "*/*", "text/*"]: - if default_type == "application/json": - info = format_output_json(res, field_name, list_field_name) + info = dump_radl_json(res, enter="", indent="") + # This is the case of the "contains" properties + elif isinstance(res, dict) and all(isinstance(x, Feature) for x in res.values()): + features = Features() + features.props = res + res_dict = featuresToSimple(features) + if field_name: + res_dict = {field_name: res_dict} + info = json.dumps(res_dict) + else: + # Always return a complex object to make easier parsing + # steps + info = format_output_json(res, field_name, list_field_name) + content_type = "application/json" + break + elif accept_item in [default_type, "*/*", "text/*"]: + if default_type == "application/json": + info = format_output_json(res, field_name, list_field_name) + else: + if isinstance(res, list): + info = "\n".join(res) else: - if isinstance(res, list): - info = "\n".join(res) - else: - info = "%s" % res - content_type = default_type - break - - if content_type: - bottle.response.content_type = content_type - else: - return return_error(415, "Unsupported Accept Media Types: %s" % ",".join(accept)) + info = "%s" % res + content_type = default_type + break + + if content_type: + headers = {'Content-Type': content_type} + if extra_headers: + headers.update(extra_headers) + return flask.make_response(info, 200, headers) else: - if default_type == "application/json": - info = format_output_json(res, field_name, list_field_name) - else: - if isinstance(res, list): - info = "\n".join(res) - else: - info = "%s" % res - bottle.response.content_type = default_type - - return info + return return_error(415, "Unsupported Accept Media Types: %s" % ",".join(accept)) -@app.hook('after_request') -def enable_cors(): +@app.after_request +def enable_cors(response): """ Enable CORS to javascript SDK """ if Config.ENABLE_CORS: - bottle.response.headers['Access-Control-Allow-Origin'] = Config.CORS_ORIGIN - bottle.response.headers['Access-Control-Allow-Methods'] = 'PUT, GET, POST, DELETE, OPTIONS' - bottle.response.headers['Access-Control-Allow-Headers'] = 'Origin, Accept, Content-Type, Authorization' + response.headers['Access-Control-Allow-Origin'] = Config.CORS_ORIGIN + response.headers['Access-Control-Allow-Methods'] = 'PUT, GET, POST, DELETE, OPTIONS' + response.headers['Access-Control-Allow-Headers'] = 'Origin, Accept, Content-Type, Authorization' + return response -@app.route('/infrastructures/:infid', method='DELETE') +@app.route('/infrastructures/', methods=['DELETE']) def RESTDestroyInfrastructure(infid=None): try: auth = get_auth_header() @@ -343,8 +261,8 @@ def RESTDestroyInfrastructure(infid=None): try: force = False - if "force" in bottle.request.params.keys(): - str_force = bottle.request.params.get("force").lower() + if "force" in flask.request.args.keys(): + str_force = flask.request.args.get("force").lower() if str_force in ['yes', 'true', '1']: force = True elif str_force in ['no', 'false', '0']: @@ -353,8 +271,8 @@ def RESTDestroyInfrastructure(infid=None): return return_error(400, "Incorrect value in force parameter") async_call = False - if "async" in bottle.request.params.keys(): - str_ctxt = bottle.request.params.get("async").lower() + if "async" in flask.request.args.keys(): + str_ctxt = flask.request.args.get("async").lower() if str_ctxt in ['yes', 'true', '1']: async_call = True elif str_ctxt in ['no', 'false', '0']: @@ -363,8 +281,7 @@ def RESTDestroyInfrastructure(infid=None): return return_error(400, "Incorrect value in async parameter") InfrastructureManager.DestroyInfrastructure(infid, auth, force, async_call) - bottle.response.content_type = "text/plain" - return "" + return flask.make_response("", 200, {'Content-Type': 'text/plain'}) except DeletedInfrastructureException as ex: return return_error(404, "Error Destroying Inf: %s" % get_ex_error(ex)) except IncorrectInfrastructureException as ex: @@ -380,7 +297,7 @@ def RESTDestroyInfrastructure(infid=None): return return_error(400, "Error Destroying Inf: %s" % get_ex_error(ex)) -@app.route('/infrastructures/:infid', method='GET') +@app.route('/infrastructures/', methods=['GET']) def RESTGetInfrastructureInfo(infid=None): try: auth = get_auth_header() @@ -392,7 +309,7 @@ def RESTGetInfrastructureInfo(infid=None): res = [] for vm_id in vm_ids: - res.append(get_full_url('/infrastructures/' + str(infid) + '/vms/' + str(vm_id))) + res.append("%sinfrastructures/%s/vms/%s" % (flask.request.url_root, infid, vm_id)) return format_output(res, "text/uri-list", "uri-list", "uri") except DeletedInfrastructureException as ex: @@ -406,7 +323,7 @@ def RESTGetInfrastructureInfo(infid=None): return return_error(400, "Error Getting Inf. info: %s" % get_ex_error(ex)) -@app.route('/infrastructures/:infid/:prop', method='GET') +@app.route('/infrastructures//') def RESTGetInfrastructureProperty(infid=None, prop=None): try: auth = get_auth_header() @@ -416,8 +333,8 @@ def RESTGetInfrastructureProperty(infid=None, prop=None): try: if prop == "contmsg": headeronly = False - if "headeronly" in bottle.request.params.keys(): - str_headeronly = bottle.request.params.get("headeronly").lower() + if "headeronly" in flask.request.args.keys(): + str_headeronly = flask.request.args.get("headeronly").lower() if str_headeronly in ['yes', 'true', '1']: headeronly = True elif str_headeronly in ['no', 'false', '0']: @@ -432,33 +349,28 @@ def RESTGetInfrastructureProperty(infid=None, prop=None): accept = get_media_type('Accept') if accept and "application/json" not in accept and "*/*" not in accept and "application/*" not in accept: return return_error(415, "Unsupported Accept Media Types: %s" % accept) - bottle.response.content_type = "application/json" auth = InfrastructureManager.check_auth_data(auth) sel_inf = InfrastructureManager.get_infrastructure(infid, auth) if "TOSCA" in sel_inf.extra_info: res = sel_inf.extra_info["TOSCA"].serialize() else: - bottle.abort( - 403, "'tosca' infrastructure property is not valid in this infrastructure") + flask.abort(403, "'tosca' infrastructure property is not valid in this infrastructure") elif prop == "state": accept = get_media_type('Accept') if accept and "application/json" not in accept and "*/*" not in accept and "application/*" not in accept: return return_error(415, "Unsupported Accept Media Types: %s" % accept) - bottle.response.content_type = "application/json" res = InfrastructureManager.GetInfrastructureState(infid, auth) return format_output(res, default_type="application/json", field_name="state") elif prop == "outputs": accept = get_media_type('Accept') if accept and "application/json" not in accept and "*/*" not in accept and "application/*" not in accept: return return_error(415, "Unsupported Accept Media Types: %s" % accept) - bottle.response.content_type = "application/json" auth = InfrastructureManager.check_auth_data(auth) sel_inf = InfrastructureManager.get_infrastructure(infid, auth) if "TOSCA" in sel_inf.extra_info: res = sel_inf.extra_info["TOSCA"].get_outputs(sel_inf) else: - bottle.abort( - 403, "'outputs' infrastructure property is not valid in this infrastructure") + flask.abort(403, "'outputs' infrastructure property is not valid in this infrastructure") return format_output(res, default_type="application/json", field_name="outputs") elif prop == "data": accept = get_media_type('Accept') @@ -466,8 +378,8 @@ def RESTGetInfrastructureProperty(infid=None, prop=None): return return_error(415, "Unsupported Accept Media Types: %s" % accept) delete = False - if "delete" in bottle.request.params.keys(): - str_delete = bottle.request.params.get("delete").lower() + if "delete" in flask.request.args.keys(): + str_delete = flask.request.args.get("delete").lower() if str_delete in ['yes', 'true', '1']: delete = True elif str_delete in ['no', 'false', '0']: @@ -494,7 +406,7 @@ def RESTGetInfrastructureProperty(infid=None, prop=None): return return_error(400, "Error Getting Inf. prop: %s" % get_ex_error(ex)) -@app.route('/infrastructures', method='GET') +@app.route('/infrastructures', methods=['GET']) def RESTGetInfrastructureList(): try: auth = get_auth_header() @@ -503,14 +415,14 @@ def RESTGetInfrastructureList(): try: flt = None - if "filter" in bottle.request.params.keys(): - flt = bottle.request.params.get("filter") + if "filter" in flask.request.args.keys(): + flt = flask.request.args.get("filter") inf_ids = InfrastructureManager.GetInfrastructureList(auth, flt) res = [] for inf_id in inf_ids: - res.append(get_full_url('/infrastructures/%s' % inf_id)) + res.append("%sinfrastructures/%s" % (flask.request.url_root, inf_id)) return format_output(res, "text/uri-list", "uri-list", "uri") except InvaliddUserException as ex: @@ -520,7 +432,7 @@ def RESTGetInfrastructureList(): return return_error(400, "Error Getting Inf. List: %s" % get_ex_error(ex)) -@app.route('/infrastructures', method='POST') +@app.route('/infrastructures', methods=['POST']) def RESTCreateInfrastructure(): try: auth = get_auth_header() @@ -529,12 +441,12 @@ def RESTCreateInfrastructure(): try: content_type = get_media_type('Content-Type') - radl_data = bottle.request.body.read().decode("utf-8") + radl_data = flask.request.data.decode("utf-8") tosca_data = None async_call = False - if "async" in bottle.request.params.keys(): - str_async = bottle.request.params.get("async").lower() + if "async" in flask.request.args.keys(): + str_async = flask.request.args.get("async").lower() if str_async in ['yes', 'true', '1']: async_call = True elif str_async in ['no', 'false', '0']: @@ -543,8 +455,8 @@ def RESTCreateInfrastructure(): return return_error(400, "Incorrect value in async parameter") dry_run = False - if "dry_run" in bottle.request.params.keys(): - str_dry_run = bottle.request.params.get("dry_run").lower() + if "dry_run" in flask.request.args.keys(): + str_dry_run = flask.request.args.get("dry_run").lower() if str_dry_run in ['yes', 'true', '1']: dry_run = True elif str_dry_run in ['no', 'false', '0']: @@ -574,11 +486,8 @@ def RESTCreateInfrastructure(): sel_inf = InfrastructureManager.get_infrastructure(inf_id, auth) sel_inf.extra_info['TOSCA'] = tosca_data - bottle.response.headers['InfID'] = inf_id - bottle.response.content_type = "text/uri-list" - res = get_full_url('/infrastructures/%s' % inf_id) - - return format_output(res, "text/uri-list", "uri") + res = "%sinfrastructures/%s" % (flask.request.url_root, inf_id) + return format_output(res, "text/uri-list", "uri", extra_headers={'InfID': inf_id}) except InvaliddUserException as ex: return return_error(401, "Error Getting Inf. info: %s" % get_ex_error(ex)) except DisabledFunctionException as ex: @@ -588,7 +497,7 @@ def RESTCreateInfrastructure(): return return_error(400, "Error Creating Inf.: %s" % get_ex_error(ex)) -@app.route('/infrastructures', method='PUT') +@app.route('/infrastructures', methods=['PUT']) def RESTImportInfrastructure(): try: auth = get_auth_header() @@ -597,7 +506,7 @@ def RESTImportInfrastructure(): try: content_type = get_media_type('Content-Type') - data = bottle.request.body.read().decode("utf-8") + data = flask.request.data.decode("utf-8") if content_type: if "application/json" not in content_type: @@ -605,8 +514,7 @@ def RESTImportInfrastructure(): new_id = InfrastructureManager.ImportInfrastructure(data, auth) - bottle.response.content_type = "text/uri-list" - res = get_full_url('/infrastructures/%s' % new_id) + res = "%sinfrastructures/%s" % (flask.request.url_root, new_id) return format_output(res, "text/uri-list", "uri") except InvaliddUserException as ex: @@ -618,7 +526,7 @@ def RESTImportInfrastructure(): return return_error(400, "Error Impporting Inf.: %s" % get_ex_error(ex)) -@app.route('/infrastructures/:infid/vms/:vmid', method='GET') +@app.route('/infrastructures//vms/', methods=['GET']) def RESTGetVMInfo(infid=None, vmid=None): try: auth = get_auth_header() @@ -643,7 +551,7 @@ def RESTGetVMInfo(infid=None, vmid=None): return return_error(400, "Error Getting VM info: %s" % get_ex_error(ex)) -@app.route('/infrastructures/:infid/vms/:vmid/:prop', method='GET') +@app.route('/infrastructures//vms//', methods=['GET']) def RESTGetVMProperty(infid=None, vmid=None, prop=None): try: auth = get_auth_header() @@ -658,11 +566,11 @@ def RESTGetVMProperty(infid=None, vmid=None, prop=None): sel_inf = InfrastructureManager.get_infrastructure(infid, auth) step = 1 - if "step" in bottle.request.params.keys(): - step = int(bottle.request.params.get("step")) + if "step" in flask.request.args.keys(): + step = int(flask.request.args.get("step")) if step == 1: - url = get_full_url('/infrastructures/' + str(infid) + '/vms/' + str(vmid) + '/command?step=2') + url = "%sinfrastructures/%s/vms/%s/command?step=2" % (flask.request.url_root, infid, vmid) auth = sel_inf.auth.getAuthInfo("InfrastructureManager")[0] if 'token' in auth: imauth = "token = %s" % auth['token'] @@ -747,7 +655,7 @@ def RESTGetVMProperty(infid=None, vmid=None, prop=None): return return_error(400, "Error Getting VM property: %s" % get_ex_error(ex)) -@app.route('/infrastructures/:infid', method='POST') +@app.route('/infrastructures/', methods=['POST']) def RESTAddResource(infid=None): try: auth = get_auth_header() @@ -756,8 +664,8 @@ def RESTAddResource(infid=None): try: context = True - if "context" in bottle.request.params.keys(): - str_ctxt = bottle.request.params.get("context").lower() + if "context" in flask.request.args.keys(): + str_ctxt = flask.request.args.get("context").lower() if str_ctxt in ['yes', 'true', '1']: context = True elif str_ctxt in ['no', 'false', '0']: @@ -766,7 +674,7 @@ def RESTAddResource(infid=None): return return_error(400, "Incorrect value in context parameter") content_type = get_media_type('Content-Type') - radl_data = bottle.request.body.read().decode("utf-8") + radl_data = flask.request.data.decode("utf-8") tosca_data = None remove_list = [] @@ -797,10 +705,6 @@ def RESTAddResource(infid=None): if not remove_list and not vm_ids and context: InfrastructureManager.Reconfigure(infid, "", auth) - # If we have to reconfigure the infra, return the ID for the HAProxy stickiness - if context: - bottle.response.headers['InfID'] = infid - # Replace the TOSCA document if tosca_data: sel_inf = InfrastructureManager.get_infrastructure(infid, auth) @@ -808,12 +712,16 @@ def RESTAddResource(infid=None): res = [] for vm_id in vm_ids: - res.append(get_full_url("/infrastructures/" + str(infid) + "/vms/" + str(vm_id))) + res.append("%sinfrastructures/%s/vms/%s" % (flask.request.url_root, infid, vm_id)) if not vm_ids and remove_list and len(remove_list) != removed_vms: return return_error(404, "Error deleting resources %s (removed %s)" % (remove_list, removed_vms)) else: - return format_output(res, "text/uri-list", "uri-list", "uri") + extra_headers = {} + # If we have to reconfigure the infra, return the ID for the HAProxy stickiness + if context: + extra_headers = {'InfID': infid} + return format_output(res, "text/uri-list", "uri-list", "uri", extra_headers) except DeletedInfrastructureException as ex: return return_error(404, "Error Adding resources: %s" % get_ex_error(ex)) except IncorrectInfrastructureException as ex: @@ -827,7 +735,7 @@ def RESTAddResource(infid=None): return return_error(400, "Error Adding resources: %s" % get_ex_error(ex)) -@app.route('/infrastructures/:infid/vms/:vmid', method='DELETE') +@app.route('/infrastructures//vms/', methods=['DELETE']) def RESTRemoveResource(infid=None, vmid=None): try: auth = get_auth_header() @@ -836,8 +744,8 @@ def RESTRemoveResource(infid=None, vmid=None): try: context = True - if "context" in bottle.request.params.keys(): - str_ctxt = bottle.request.params.get("context").lower() + if "context" in flask.request.args.keys(): + str_ctxt = flask.request.args.get("context").lower() if str_ctxt in ['yes', 'true', '1']: context = True elif str_ctxt in ['no', 'false', '0']: @@ -846,8 +754,7 @@ def RESTRemoveResource(infid=None, vmid=None): return return_error(400, "Incorrect value in context parameter") InfrastructureManager.RemoveResource(infid, vmid, auth, context) - bottle.response.content_type = "text/plain" - return "" + return flask.make_response("", 200, {'Content-Type': 'text/plain'}) except DeletedInfrastructureException as ex: return return_error(404, "Error Removing resources: %s" % get_ex_error(ex)) except IncorrectInfrastructureException as ex: @@ -865,7 +772,7 @@ def RESTRemoveResource(infid=None, vmid=None): return return_error(400, "Error Removing resources: %s" % get_ex_error(ex)) -@app.route('/infrastructures/:infid/vms/:vmid', method='PUT') +@app.route('/infrastructures//vms/', methods=['PUT']) def RESTAlterVM(infid=None, vmid=None): try: auth = get_auth_header() @@ -874,7 +781,7 @@ def RESTAlterVM(infid=None, vmid=None): try: content_type = get_media_type('Content-Type') - radl_data = bottle.request.body.read().decode("utf-8") + radl_data = flask.request.data.decode("utf-8") if content_type: if "application/json" in content_type: @@ -907,7 +814,7 @@ def RESTAlterVM(infid=None, vmid=None): return return_error(400, "Error modifying resources: %s" % get_ex_error(ex)) -@app.route('/infrastructures/:infid/reconfigure', method='PUT') +@app.route('/infrastructures//reconfigure', methods=['PUT']) def RESTReconfigureInfrastructure(infid=None): try: auth = get_auth_header() @@ -916,15 +823,15 @@ def RESTReconfigureInfrastructure(infid=None): try: vm_list = None - if "vm_list" in bottle.request.params.keys(): - str_vm_list = bottle.request.params.get("vm_list") + if "vm_list" in flask.request.args.keys(): + str_vm_list = flask.request.args.get("vm_list") try: vm_list = [int(vm_id) for vm_id in str_vm_list.split(",")] except Exception: return return_error(400, "Incorrect vm_list format.") content_type = get_media_type('Content-Type') - radl_data = bottle.request.body.read().decode("utf-8") + radl_data = flask.request.data.decode("utf-8") if radl_data: if content_type: @@ -939,10 +846,10 @@ def RESTReconfigureInfrastructure(infid=None): return return_error(415, "Unsupported Media Type %s" % content_type) else: radl_data = "" + + res = InfrastructureManager.Reconfigure(infid, radl_data, auth, vm_list) # As we have to reconfigure the infra, return the ID for the HAProxy stickiness - bottle.response.headers['InfID'] = infid - bottle.response.content_type = "text/plain" - return InfrastructureManager.Reconfigure(infid, radl_data, auth, vm_list) + return flask.make_response(res, 200, {'Content-Type': 'text/plain', 'InfID': infid}) except DeletedInfrastructureException as ex: return return_error(404, "Error reconfiguring infrastructure: %s" % get_ex_error(ex)) except IncorrectInfrastructureException as ex: @@ -956,139 +863,74 @@ def RESTReconfigureInfrastructure(infid=None): return return_error(400, "Error reconfiguring infrastructure: %s" % get_ex_error(ex)) -@app.route('/infrastructures/:infid/start', method='PUT') -def RESTStartInfrastructure(infid=None): - try: - auth = get_auth_header() - except Exception: - return return_error(401, "No authentication data provided") - - try: - bottle.response.content_type = "text/plain" - return InfrastructureManager.StartInfrastructure(infid, auth) - except DeletedInfrastructureException as ex: - return return_error(404, "Error starting infrastructure: %s" % get_ex_error(ex)) - except IncorrectInfrastructureException as ex: - return return_error(404, "Error starting infrastructure: %s" % get_ex_error(ex)) - except UnauthorizedUserException as ex: - return return_error(403, "Error starting infrastructure: %s" % get_ex_error(ex)) - except DisabledFunctionException as ex: - return return_error(403, "Error starting infrastructure: %s" % get_ex_error(ex)) - except Exception as ex: - logger.exception("Error starting infrastructure") - return return_error(400, "Error starting infrastructure: %s" % get_ex_error(ex)) - - -@app.route('/infrastructures/:infid/stop', method='PUT') -def RESTStopInfrastructure(infid=None): - try: - auth = get_auth_header() - except Exception: - return return_error(401, "No authentication data provided") - - try: - bottle.response.content_type = "text/plain" - return InfrastructureManager.StopInfrastructure(infid, auth) - except DeletedInfrastructureException as ex: - return return_error(404, "Error stopping infrastructure: %s" % get_ex_error(ex)) - except IncorrectInfrastructureException as ex: - return return_error(404, "Error stopping infrastructure: %s" % get_ex_error(ex)) - except UnauthorizedUserException as ex: - return return_error(403, "Error stopping infrastructure: %s" % get_ex_error(ex)) - except DisabledFunctionException as ex: - return return_error(403, "Error stopping infrastructure: %s" % get_ex_error(ex)) - except Exception as ex: - logger.exception("Error stopping infrastructure") - return return_error(400, "Error stopping infrastructure: %s" % get_ex_error(ex)) - - -@app.route('/infrastructures/:infid/vms/:vmid/start', method='PUT') -def RESTStartVM(infid=None, vmid=None): +@app.route('/infrastructures//', methods=['PUT']) +def RESTOperateInfrastructure(infid=None, op=None): try: auth = get_auth_header() except Exception: return return_error(401, "No authentication data provided") try: - bottle.response.content_type = "text/plain" - return InfrastructureManager.StartVM(infid, vmid, auth) - except DeletedInfrastructureException as ex: - return return_error(404, "Error starting VM: %s" % get_ex_error(ex)) - except IncorrectInfrastructureException as ex: - return return_error(404, "Error starting VM: %s" % get_ex_error(ex)) - except UnauthorizedUserException as ex: - return return_error(403, "Error starting VM: %s" % get_ex_error(ex)) - except DeletedVMException as ex: - return return_error(404, "Error starting VM: %s" % get_ex_error(ex)) - except IncorrectVMException as ex: - return return_error(404, "Error starting VM: %s" % get_ex_error(ex)) - except DisabledFunctionException as ex: - return return_error(403, "Error starting VM: %s" % get_ex_error(ex)) - except Exception as ex: - logger.exception("Error starting VM") - return return_error(400, "Error starting VM: %s" % get_ex_error(ex)) - - -@app.route('/infrastructures/:infid/vms/:vmid/stop', method='PUT') -def RESTStopVM(infid=None, vmid=None): - try: - auth = get_auth_header() - except Exception: - return return_error(401, "No authentication data provided") - - try: - bottle.response.content_type = "text/plain" - return InfrastructureManager.StopVM(infid, vmid, auth) + if op == "start": + res = InfrastructureManager.StartInfrastructure(infid, auth) + elif op == "stop": + res = InfrastructureManager.StopInfrastructure(infid, auth) + else: + flask.abort(404) + return flask.make_response(res, 200, {'Content-Type': 'text/plain'}) except DeletedInfrastructureException as ex: - return return_error(404, "Error stopping VM: %s" % get_ex_error(ex)) + return return_error(404, "Error in %s operation: %s" % (op, get_ex_error(ex))) except IncorrectInfrastructureException as ex: - return return_error(404, "Error stopping VM: %s" % get_ex_error(ex)) + return return_error(404, "Error in %s operation: %s" % (op, get_ex_error(ex))) except UnauthorizedUserException as ex: - return return_error(403, "Error stopping VM: %s" % get_ex_error(ex)) - except DeletedVMException as ex: - return return_error(404, "Error stopping VM: %s" % get_ex_error(ex)) - except IncorrectVMException as ex: - return return_error(404, "Error stopping VM: %s" % get_ex_error(ex)) + return return_error(403, "Error in %s operation: %s" % (op, get_ex_error(ex))) except DisabledFunctionException as ex: - return return_error(403, "Error stopping VM: %s" % get_ex_error(ex)) + return return_error(403, "Error in %s operation: %s" % (op, get_ex_error(ex))) except Exception as ex: - logger.exception("Error stopping VM") - return return_error(400, "Error stopping VM: %s" % get_ex_error(ex)) + logger.exception("Error in %s operation" % op) + return return_error(400, "Error in %s operation: %s" % (op, get_ex_error(ex))) -@app.route('/infrastructures/:infid/vms/:vmid/reboot', method='PUT') -def RESTRebootVM(infid=None, vmid=None): +@app.route('/infrastructures//vms//', methods=['PUT']) +def RESTOperateVM(infid=None, vmid=None, op=None): try: auth = get_auth_header() except Exception: return return_error(401, "No authentication data provided") try: - bottle.response.content_type = "text/plain" - return InfrastructureManager.RebootVM(infid, vmid, auth) + if op == "start": + res = InfrastructureManager.StartVM(infid, vmid, auth) + elif op == "stop": + res = InfrastructureManager.StopVM(infid, vmid, auth) + elif op == "reboot": + res = InfrastructureManager.RebootVM(infid, vmid, auth) + else: + flask.abort(404) + return flask.make_response(res, 200, {'Content-Type': 'text/plain'}) except DeletedInfrastructureException as ex: - return return_error(404, "Error rebooting VM: %s" % get_ex_error(ex)) + return return_error(404, "Error in %s op in VM: %s" % (op, get_ex_error(ex))) except IncorrectInfrastructureException as ex: - return return_error(404, "Error rebooting VM: %s" % get_ex_error(ex)) + return return_error(404, "Error in %s op in VM: %s" % (op, get_ex_error(ex))) except UnauthorizedUserException as ex: - return return_error(403, "Error rebooting VM: %s" % get_ex_error(ex)) + return return_error(403, "Error in %s op in VM: %s" % (op, get_ex_error(ex))) except DeletedVMException as ex: - return return_error(404, "Error rebooting VM: %s" % get_ex_error(ex)) + return return_error(404, "Error in %s op in VM: %s" % (op, get_ex_error(ex))) except IncorrectVMException as ex: - return return_error(404, "Error rebooting VM: %s" % get_ex_error(ex)) + return return_error(404, "Error in %s op in VM: %s" % (op, get_ex_error(ex))) except DisabledFunctionException as ex: - return return_error(403, "Error rebooting VM: %s" % get_ex_error(ex)) + return return_error(403, "Error in %s op in VM: %s" % (op, get_ex_error(ex))) except Exception as ex: - logger.exception("Error rebooting VM") - return return_error(400, "Error rebooting VM: %s" % get_ex_error(ex)) + logger.exception("Error in %s op in VM" % op) + return return_error(400, "Error in %s op in VM: %s" % (op, get_ex_error(ex))) -@app.route("/", method='OPTIONS') +@app.route('/', methods=['OPTIONS']) def ReturnOptions(**kwargs): return {} -@app.route('/version', method='GET') +@app.route('/version') def RESTGetVersion(): try: from IM import __version__ as version @@ -1097,7 +939,7 @@ def RESTGetVersion(): return return_error(400, "Error getting IM version: %s" % get_ex_error(ex)) -@app.route('/infrastructures/:infid/vms/:vmid/disks/:disknum/snapshot', method='PUT') +@app.route('/infrastructures//vms//disks//snapshot', methods=['PUT']) def RESTCreateDiskSnapshot(infid=None, vmid=None, disknum=None): try: auth = get_auth_header() @@ -1105,14 +947,12 @@ def RESTCreateDiskSnapshot(infid=None, vmid=None, disknum=None): return return_error(401, "No authentication data provided") try: - bottle.response.content_type = "text/plain" - - if "image_name" in bottle.request.params.keys(): - image_name = bottle.request.params.get("image_name") + if "image_name" in flask.request.args.keys(): + image_name = flask.request.args.get("image_name") else: return return_error(400, "Parameter image_name required.") - if "auto_delete" in bottle.request.params.keys(): - str_auto_delete = bottle.request.params.get("auto_delete").lower() + if "auto_delete" in flask.request.args.keys(): + str_auto_delete = flask.request.args.get("auto_delete").lower() if str_auto_delete in ['yes', 'true', '1']: auto_delete = True elif str_auto_delete in ['no', 'false', '0']: @@ -1122,7 +962,8 @@ def RESTCreateDiskSnapshot(infid=None, vmid=None, disknum=None): else: auto_delete = False - return InfrastructureManager.CreateDiskSnapshot(infid, vmid, int(disknum), image_name, auto_delete, auth) + res = InfrastructureManager.CreateDiskSnapshot(infid, vmid, int(disknum), image_name, auto_delete, auth) + return flask.make_response(res, 200, {'Content-Type': 'text/plain'}) except DeletedInfrastructureException as ex: return return_error(404, "Error creating snapshot: %s" % get_ex_error(ex)) except IncorrectInfrastructureException as ex: @@ -1151,7 +992,7 @@ def _filters_str_to_dict(filters_str): return filters -@app.route('/clouds/:cloudid/:param', method='GET') +@app.route('/clouds//', methods=['GET']) def RESTGetCloudInfo(cloudid=None, param=None): try: auth = get_auth_header() @@ -1161,17 +1002,15 @@ def RESTGetCloudInfo(cloudid=None, param=None): try: if param == 'images': filters = None - if "filters" in bottle.request.params.keys(): + if "filters" in flask.request.args.keys(): try: - filters = _filters_str_to_dict(bottle.request.params.get("filters")) + filters = _filters_str_to_dict(flask.request.args.get("filters")) except Exception: return return_error(400, "Invalid format in filters parameter.") images = InfrastructureManager.GetCloudImageList(cloudid, auth, filters) - bottle.response.content_type = "application/json" return format_output(images, default_type="application/json", field_name="images") elif param == 'quotas': quotas = InfrastructureManager.GetCloudQuotas(cloudid, auth) - bottle.response.content_type = "application/json" return format_output(quotas, default_type="application/json", field_name="quotas") except InvaliddUserException as ex: return return_error(401, "Error getting cloud info: %s" % get_ex_error(ex)) @@ -1180,7 +1019,7 @@ def RESTGetCloudInfo(cloudid=None, param=None): return return_error(400, "Error getting cloud info: %s" % get_ex_error(ex)) -@app.route('/infrastructures/:infid/authorization', method='POST') +@app.route('/infrastructures//authorization', methods=['POST']) def RESTChangeInfrastructureAuth(infid=None): try: auth = get_auth_header() @@ -1189,8 +1028,8 @@ def RESTChangeInfrastructureAuth(infid=None): try: overwrite = False - if "overwrite" in bottle.request.params.keys(): - str_overwrite = bottle.request.params.get("overwrite").lower() + if "overwrite" in flask.request.args.keys(): + str_overwrite = flask.request.args.get("overwrite").lower() if str_overwrite in ['yes', 'true', '1']: overwrite = True elif str_overwrite in ['no', 'false', '0']: @@ -1201,7 +1040,7 @@ def RESTChangeInfrastructureAuth(infid=None): content_type = get_media_type('Content-Type') or ["application/json"] if "application/json" in content_type: - auth_dict = json.loads(bottle.request.body.read().decode("utf-8")) + auth_dict = json.loads(flask.request.data.decode("utf-8")) if "type" not in auth_dict: auth_dict["type"] = "InfrastructureManager" new_auth = Authentication([auth_dict]) @@ -1209,9 +1048,7 @@ def RESTChangeInfrastructureAuth(infid=None): return return_error(415, "Unsupported Media Type %s" % content_type) InfrastructureManager.ChangeInfrastructureAuth(infid, new_auth, overwrite, auth) - bottle.response.content_type = "text/plain" - return "" - + return flask.make_response("", 200, {'Content-Type': 'text/plain'}) except DeletedInfrastructureException as ex: return return_error(404, "Error modifying infrastructure owner: %s" % get_ex_error(ex)) except IncorrectInfrastructureException as ex: @@ -1229,21 +1066,21 @@ def RESTChangeInfrastructureAuth(infid=None): return return_error(400, "Error modifying infrastructure owner: %s" % get_ex_error(ex)) -@app.error(403) +@app.errorhandler(403) def error_mesage_403(error): - return return_error(403, error.body) + return return_error(403, error.description) -@app.error(404) +@app.errorhandler(404) def error_mesage_404(error): - return return_error(404, error.body) + return return_error(404, error.description) -@app.error(405) +@app.errorhandler(405) def error_mesage_405(error): - return return_error(405, error.body) + return return_error(405, error.description) -@app.error(500) +@app.errorhandler(500) def error_mesage_500(error): - return return_error(500, error.body) + return return_error(500, error.description) diff --git a/README.md b/README.md index ef7470de7..c424ccfd4 100644 --- a/README.md +++ b/README.md @@ -161,23 +161,22 @@ pipelining = True ### 3.2 OPTIONAL PACKAGES -The Bottle framework () is used for the REST API. -It is typically available as the ``python-bottle`` system package or ``bottle`` -pip package. +The Flask framework () is used for the REST API. +It is typically available as the ``python3-flask`` and ``python3-werkzeug`` system +packages or ``flask`` and ``werkzeug`` pip packages. The CherryPy Web framework (), is needed for the REST -API. It is typically available as the ``python-cherrypy`` or -``python-cherrypy3`` system package or ``CherryPy`` pip package. -In newer versions (9.0 and later) the functionality has been moved to the -``cheroot`` library () it can be -installed using pip. +API. It is typically available as the ``python3-cherrypy`` system package or +``CherryPy`` pip package. In newer versions (9.0 and later) the functionality +has been moved to the ``cheroot`` library (). +It is typically available ``python3-cheroot`` system package or ``cheroot`` pip package. Apache-libcloud () 3.0 or later is used in the LibCloud, OpenStack and GCE connectors. It is typically available as the -``python-libcloud`` system package or ``apache-libcloud`` pip package. +``python3-libcloud`` system package or ``apache-libcloud`` pip package. Boto () 2.29.0 or later is used as interface to -Amazon EC2. It is available as package named ``python-boto`` in Debian based +Amazon EC2. It is available as package named ``python3-boto`` in Debian based distributions or ``boto`` pip package. It can also be downloaded from boto GitHub repository (). Download the file and copy the boto subdirectory into the IM install path. diff --git a/contextualization/conf-ansible.yml b/contextualization/conf-ansible.yml index 8e0b9e593..67a03f296 100644 --- a/contextualization/conf-ansible.yml +++ b/contextualization/conf-ansible.yml @@ -125,6 +125,8 @@ pip: name: pip>18.0,<21.0 executable: pip3 + # in some old distros we need to trust in the pypi to avoid SSL errors + extra_args: --trusted-host files.pythonhosted.org --trusted-host pypi.org --trusted-host pypi.python.org when: ansible_python_version is version('3.7', '<') - name: Upgrade pip in py3.7-py3.8 diff --git a/doc/source/REST.rst b/doc/source/REST.rst index d8f8f65d9..3334f41b3 100644 --- a/doc/source/REST.rst +++ b/doc/source/REST.rst @@ -15,6 +15,9 @@ password are not valid, it is returned the HTTP error code 401. In case that Vau support has been configured (:ref:`vault-creds`) also a Bearer authorization header is supported, using the same access token to authenticate with the Vault server. +In case of using EGI Check-in authentication, see :ref:`egi-auth` to know how to get +a valid token. + Next tables summaries the resources and the HTTP methods available. +-------------+------------------------------------+------------------------------------+-------------------------------------------+ diff --git a/doc/source/client.rst b/doc/source/client.rst index b4601c31d..2d96a01f6 100644 --- a/doc/source/client.rst +++ b/doc/source/client.rst @@ -465,8 +465,10 @@ So the auth line will be like that:: id = ost; type = OpenStack; host = https://ostserver:5000; username = indigo-dc; tenant = oidc; password = iam_token_value; auth_version = 3.x_oidc_access_token -EGI FedCloud specific parameters -******************************* +.. _egi-auth: + +EGI Cloud Compute specific parameters +************************************* To use the EGI CheckIn to authenticate with a Keystone server properly configured the parameters are the following (see more info at `EGI Documentation `_): @@ -486,9 +488,22 @@ From IM version 1.10.2 the EGI connector is available and you can also use this id = egi; type = EGI; host = CESGA; vo = vo.access.egi.eu; token = egi_aai_token_value In this case the information needed to access the OpenStack API of the EGI FedCloud site will be obtained from -`AppDB REST API `_). This connector is recommended for non advanced users. If you +`AppDB REST API `_. This connector is recommended for non advanced users. If you can get the data to access the OpenStack API directly it is recommened to use it. +There are several ways to get the EGI AAI token: + +* One of them is using the `oidc-agent `_, configuring the + `EGI CheckIn as a provider `_. + Then you can get the token using the command keyworkd in the auth file:: + + token = command(oidc-token OIDC_ACCOUNT) + +* Another way is using the IM-Dashboard (:ref:`use-dashboard`). In the "Advanced" menu, the "Settings" + item enables getting the some configuration settings as the OIDC issuer or the current user's + access token. + + Open Telekom Cloud ++++++++++++++++++ diff --git a/doc/source/manual.rst b/doc/source/manual.rst index aa13e5840..bf9a6d762 100644 --- a/doc/source/manual.rst +++ b/doc/source/manual.rst @@ -93,16 +93,17 @@ Finally, check the next values in the Ansible configuration file Optional Packages ----------------- -* `The Bottle framework `_ is used for the REST API. - It is typically available as the 'python-bottle' package. +* `The Flask framework `_ is used for the REST API. + It is typically available as the 'python3-flask' and 'python3-werkzeug' packages. * `The CherryPy Web framework `_, is needed for the REST API. - It is typically available as the 'python-cherrypy' or 'python-cherrypy3' package. - In newer versions (9.0 and later) the functionality has been moved `the cheroot - library `_ it can be installed using pip. + It is typically available as the 'python3-cherrypy' package. In newer versions (9.0 + and later) the functionality has been moved `the cheroot + library `_. It is typically available as the + 'python3-cherrot' package. * `apache-libcloud `_ 3.0 or later is used in the LibCloud, OpenStack, EGI and GCE connectors. * `boto `_ 2.29.0 or later is used as interface to - Amazon EC2. It is available as package named ``python-boto`` in Debian based + Amazon EC2. It is available as package named 'python3-boto' in Debian based distributions. It can also be downloaded from `boto GitHub repository `_. Download the file and copy the boto subdirectory into the IM install path. * `pyOpenSSL `_ is needed to secure the REST API diff --git a/im_service.py b/im_service.py index a242e56ea..98c3ee38c 100755 --- a/im_service.py +++ b/im_service.py @@ -367,14 +367,14 @@ def im_stop(): Function to safely stop the service """ try: - # Assure that the IM data are correctly saved - InfrastructureManager.logger.info('Stopping Infrastructure Manager daemon...') - InfrastructureManager.stop() - if Config.ACTIVATE_REST: # we have to stop the REST server import IM.REST IM.REST.stop() + + # Assure that the IM data are correctly saved + InfrastructureManager.logger.info('Stopping Infrastructure Manager daemon...') + InfrastructureManager.stop() except Exception: InfrastructureManager.logger.exception("Error stopping Infrastructure Manager daemon") diff --git a/packages/generate_deb.sh b/packages/generate_deb.sh deleted file mode 100755 index 6daad14ef..000000000 --- a/packages/generate_deb.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -apt update -apt install -y python-stdeb -# remove the ansible requirement as it makes to generate an incorrect dependency python-ansible -# also remove the pysqlite requirement as it makes to generate an incorrect dependency python-pysqlite1.1 -sed -i '/install_requires/c\ install_requires=["paramiko >= 1.14", "PyYAML", suds_pkg,' setup.py -python setup.py --command-packages=stdeb.command sdist_dsc --depends "python-radl, python-mysqldb, python-pysqlite2, ansible, python-paramiko, python-yaml, python-suds, python-boto, python-libcloud, python-bottle, python-netaddr, python-scp, python-cherrypy3, python-requests, python-tosca-parser" bdist_deb -mkdir dist_pkg -cp deb_dist/*.deb dist_pkg - - diff --git a/packages/generate_rpm.sh b/packages/generate_rpm.sh deleted file mode 100755 index a2cc6790a..000000000 --- a/packages/generate_rpm.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -yum -y install rpm-build python-setuptools -echo "%_unpackaged_files_terminate_build 0" > ~/.rpmmacros -python setup.py bdist_rpm --release="$1" --requires="which, MySQL-python, python-sqlite3dbm, RADL, ansible, python-paramiko, PyYAML, python-suds, python-boto >= 2.29, python-libcloud, python-bottle, python-netaddr, python-scp, python-cherrypy, python-requests, python-xmltodict, tosca-parser" -mkdir dist_pkg -cp dist/*.noarch.rpm dist_pkg diff --git a/requirements-tests.txt b/requirements-tests.txt index e08ffc8c3..48bdd4d1e 100644 --- a/requirements-tests.txt +++ b/requirements-tests.txt @@ -6,7 +6,8 @@ cheroot boto >= 2.29 apache-libcloud >= 3.3.1 RADL >= 1.3.3 -bottle +flask +werkzeug netaddr requests >= 2.19 scp diff --git a/setup.py b/setup.py index dfb350ae9..2423968b5 100644 --- a/setup.py +++ b/setup.py @@ -68,7 +68,7 @@ description="IM is a tool to manage virtual infrastructures on Cloud deployments", platforms=["any"], install_requires=["ansible >=2.4", "paramiko >= 1.14", "PyYAML", suds_pkg, sqlite_pkg, "cheroot", - "boto >= 2.29", "apache-libcloud >= 3.2.0", "RADL >= 1.3.3", "bottle", "netaddr", + "boto >= 2.29", "apache-libcloud >= 3.2.0", "RADL >= 1.3.3", "flask", "netaddr", "requests >= 2.19", "scp", "tosca-parser", 'defusedxml', 'urllib3>=1.23', 'hvac', - 'psutil', 'scar', 'requests-cache >= 1.0.0', 'packaging'] + 'psutil', 'scar', 'requests-cache >= 1.0.0', 'packaging', 'werkzeug'] ) diff --git a/test/unit/AppDB.py b/test/unit/AppDB.py index 055bfc728..d716ebb78 100644 --- a/test/unit/AppDB.py +++ b/test/unit/AppDB.py @@ -3,6 +3,12 @@ from urlparse import urlparse except ImportError: from urllib.parse import urlparse + +import sys + +sys.path.append("..") +sys.path.append(".") + from IM.AppDB import AppDB from mock import patch, MagicMock diff --git a/test/unit/REST.py b/test/unit/REST.py index 4b3ac4ec7..48f7a1681 100755 --- a/test/unit/REST.py +++ b/test/unit/REST.py @@ -17,7 +17,6 @@ # along with this program. If not, see . import os -import json import unittest import sys from io import BytesIO @@ -30,36 +29,13 @@ sys.path.append("..") sys.path.append(".") -from IM.config import Config from IM import __version__ as version from IM.InfrastructureManager import (DeletedInfrastructureException, IncorrectInfrastructureException, UnauthorizedUserException, InvaliddUserException) from IM.InfrastructureInfo import IncorrectVMException, DeletedVMException, IncorrectStateException -from IM.REST import (RESTDestroyInfrastructure, - RESTGetInfrastructureInfo, - RESTGetInfrastructureProperty, - RESTGetInfrastructureList, - RESTCreateInfrastructure, - RESTGetVMInfo, - RESTGetVMProperty, - RESTAddResource, - RESTRemoveResource, - RESTAlterVM, - RESTReconfigureInfrastructure, - RESTStartInfrastructure, - RESTStopInfrastructure, - RESTStartVM, - RESTStopVM, - RESTRebootVM, - RESTGetVersion, - RESTCreateDiskSnapshot, - RESTImportInfrastructure, - RESTGetCloudInfo, - RESTChangeInfrastructureAuth, - return_error, - format_output) +from IM.REST import app def read_file_as_bytes(file_name): @@ -74,112 +50,70 @@ class TestREST(unittest.TestCase): def __init__(self, *args): unittest.TestCase.__init__(self, *args) + def setUp(self): + self.client = app.test_client() + @patch("IM.InfrastructureManager.InfrastructureManager.GetInfrastructureList") - @patch("bottle.request") - def test_GetInfrastructureList(self, bottle_request, GetInfrastructureList): - """Test REST GetInfrastructureList.""" - bottle_request.environ = {'HTTP_HOST': 'imserver.com'} - bottle_request.return_value = MagicMock() - bottle_request.headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" - "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " - "username = user; password = pass"), - "Accept": "application/json"} + def test_GetInfrastructureList(self, GetInfrastructureList): + headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" + "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " + "username = user; password = pass"), + "Accept": "application/json"} GetInfrastructureList.return_value = ["1", "2"] - res = RESTGetInfrastructureList() - self.assertEqual(res, ('{"uri-list": [{"uri": "http://imserver.com/infrastructures/1"},' - ' {"uri": "http://imserver.com/infrastructures/2"}]}')) + res = self.client.get('/infrastructures', headers=headers) + self.assertEqual(200, res.status_code) + self.assertEqual(res.json, ({"uri-list": [{"uri": "http://localhost/infrastructures/1"}, + {"uri": "http://localhost/infrastructures/2"}]})) GetInfrastructureList.side_effect = InvaliddUserException() - res = RESTGetInfrastructureList() - res = json.loads(res) - self.assertEqual(res, {"message": "Error Getting Inf. List: Invalid InfrastructureManager credentials", - "code": 401}) + res = self.client.get('/infrastructures', headers=headers) + self.assertEqual(401, res.status_code) + self.assertEqual(res.json, {"message": "Error Getting Inf. List: Invalid InfrastructureManager credentials", + "code": 401}) GetInfrastructureList.side_effect = UnauthorizedUserException() - res = RESTGetInfrastructureList() - res = json.loads(res) - self.assertEqual(res, {"message": "Error Getting Inf. List: Access to this infrastructure not granted.", - "code": 400}) - - @patch("IM.InfrastructureManager.InfrastructureManager.GetInfrastructureList") - @patch("bottle.request") - def test_GetInfrastructureListSingleSite(self, bottle_request, GetInfrastructureList): - """Test REST GetInfrastructureList.""" - bottle_request.environ = {'HTTP_HOST': 'imserver.com'} - bottle_request.return_value = MagicMock() - - Config.SINGLE_SITE = True - Config.SINGLE_SITE_AUTH_HOST = 'host' - - Config.SINGLE_SITE_TYPE = 'OpenNebula' - Config.SINGLE_SITE_IMAGE_URL_PREFIX = 'one' - bottle_request.headers = {"AUTHORIZATION": "Basic dXNlcjpwYXNz", "Accept": "application/json"} - GetInfrastructureList.return_value = ["1", "2"] - res = RESTGetInfrastructureList() - self.assertEqual(res, ('{"uri-list": [{"uri": "http://imserver.com/infrastructures/1"},' - ' {"uri": "http://imserver.com/infrastructures/2"}]}')) - - Config.SINGLE_SITE_TYPE = 'OpenStack' - Config.SINGLE_SITE_IMAGE_URL_PREFIX = 'ost' - bottle_request.headers = {"AUTHORIZATION": "Bearer access_token", "Accept": "application/json"} - GetInfrastructureList.return_value = ["1", "2"] - res = RESTGetInfrastructureList() - self.assertEqual(res, ('{"uri-list": [{"uri": "http://imserver.com/infrastructures/1"},' - ' {"uri": "http://imserver.com/infrastructures/2"}]}')) - Config.SINGLE_SITE = False - - @patch("IM.InfrastructureManager.InfrastructureManager.GetInfrastructureList") - @patch("bottle.request") - def test_GetInfrastructureListWithErrors(self, bottle_request, GetInfrastructureList): - """Test REST GetInfrastructureList without auth data.""" - bottle_request.environ = {'HTTP_HOST': 'imserver.com'} - bottle_request.return_value = MagicMock() - bottle_request.headers = {"Accept": "application/json"} - - GetInfrastructureList.return_value = ["1", "2"] - res = RESTGetInfrastructureList() - res_json = json.loads(res) - self.assertEqual(res_json['code'], 401) + res = self.client.get('/infrastructures', headers=headers) + self.assertEqual(400, res.status_code) + self.assertEqual(res.json, {"message": "Error Getting Inf. List: Access to this infrastructure not granted.", + "code": 400}) @patch("IM.InfrastructureManager.InfrastructureManager.GetInfrastructureInfo") - @patch("bottle.request") - def test_GetInfrastructureInfo(self, bottle_request, GetInfrastructureInfo): - """Test REST GetInfrastructureInfo.""" - bottle_request.environ = {'HTTP_HOST': 'imserver.com'} - bottle_request.return_value = MagicMock() - bottle_request.headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" - "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " - "username = user; password = pass")} + def test_GetInfrastructureInfo(self, GetInfrastructureInfo): + headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" + "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " + "username = user; password = pass")} + GetInfrastructureInfo.return_value = ["1", "2"] - res = RESTGetInfrastructureInfo("1") - self.assertEqual(res, ("http://imserver.com/infrastructures/1/vms/1\n" - "http://imserver.com/infrastructures/1/vms/2")) + res = self.client.get('/infrastructures/1', headers=headers) + self.assertEqual(200, res.status_code) + self.assertEqual(res.text, ("http://localhost/infrastructures/1/vms/1\n" + "http://localhost/infrastructures/1/vms/2")) GetInfrastructureInfo.side_effect = DeletedInfrastructureException() - res = RESTGetInfrastructureInfo("1") - self.assertEqual(res, "Error Getting Inf. info: Deleted infrastructure.") + res = self.client.get('/infrastructures/1', headers=headers) + self.assertEqual(404, res.status_code) + self.assertEqual(res.text, "Error Getting Inf. info: Deleted infrastructure.") GetInfrastructureInfo.side_effect = IncorrectInfrastructureException() - res = RESTGetInfrastructureInfo("1") - self.assertEqual(res, "Error Getting Inf. info: Invalid infrastructure ID or access not granted.") + res = self.client.get('/infrastructures/1', headers=headers) + self.assertEqual(404, res.status_code) + self.assertEqual(res.text, "Error Getting Inf. info: Invalid infrastructure ID or access not granted.") GetInfrastructureInfo.side_effect = UnauthorizedUserException() - res = RESTGetInfrastructureInfo("1") - self.assertEqual(res, "Error Getting Inf. info: Access to this infrastructure not granted.") + res = self.client.get('/infrastructures/1', headers=headers) + self.assertEqual(403, res.status_code) + self.assertEqual(res.text, "Error Getting Inf. info: Access to this infrastructure not granted.") @patch("IM.InfrastructureManager.InfrastructureManager.GetInfrastructureContMsg") @patch("IM.InfrastructureManager.InfrastructureManager.GetInfrastructureRADL") @patch("IM.InfrastructureManager.InfrastructureManager.GetInfrastructureState") @patch("IM.InfrastructureManager.InfrastructureManager.get_infrastructure") - @patch("bottle.request") - def test_GetInfrastructureProperty(self, bottle_request, get_infrastructure, GetInfrastructureState, + def test_GetInfrastructureProperty(self, get_infrastructure, GetInfrastructureState, GetInfrastructureRADL, GetInfrastructureContMsg): - """Test REST GetInfrastructureProperty.""" - bottle_request.return_value = MagicMock() - bottle_request.headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" - "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " - "username = user; password = pass")} + headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" + "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " + "username = user; password = pass")} GetInfrastructureState.return_value = {'state': "running", 'vm_states': {"vm1": "running", "vm2": "running"}} GetInfrastructureRADL.return_value = "radl" @@ -192,725 +126,527 @@ def test_GetInfrastructureProperty(self, bottle_request, get_infrastructure, Get tosca.get_outputs.return_value = "outputs" tosca.serialize.return_value = "tosca" - res = RESTGetInfrastructureProperty("1", "state") - self.assertEqual(json.loads(res)["state"]["state"], "running") + res = self.client.get('/infrastructures/1/state', headers=headers) + self.assertEqual(res.json["state"]["state"], "running") - res = RESTGetInfrastructureProperty("1", "contmsg") - self.assertEqual(res, "contmsg") + res = self.client.get('/infrastructures/1/contmsg', headers=headers) + self.assertEqual(res.text, "contmsg") - bottle_request.params = {'headeronly': 'yes'} - res = RESTGetInfrastructureProperty("1", "contmsg") - self.assertEqual(res, "contmsg") + res = self.client.get('/infrastructures/1/contmsg?headeronly=yes', headers=headers) + self.assertEqual(res.text, "contmsg") - bottle_request.params = {'headeronly': 'no'} - res = RESTGetInfrastructureProperty("1", "contmsg") - self.assertEqual(res, "contmsg") + res = self.client.get('/infrastructures/1/contmsg?headeronly=no', headers=headers) + self.assertEqual(res.text, "contmsg") - res = RESTGetInfrastructureProperty("1", "radl") - self.assertEqual(res, "radl") + res = self.client.get('/infrastructures/1/radl', headers=headers) + self.assertEqual(res.text, "radl") - res = RESTGetInfrastructureProperty("1", "outputs") - self.assertEqual(res, '{"outputs": "outputs"}') + res = self.client.get('/infrastructures/1/outputs', headers=headers) + self.assertEqual(res.json, {"outputs": "outputs"}) - res = RESTGetInfrastructureProperty("1", "tosca") - self.assertEqual(res, "tosca") + res = self.client.get('/infrastructures/1/tosca', headers=headers) + self.assertEqual(res.text, "tosca") GetInfrastructureRADL.side_effect = DeletedInfrastructureException() - res = RESTGetInfrastructureProperty("1", "radl") - self.assertEqual(res, "Error Getting Inf. prop: Deleted infrastructure.") + res = self.client.get('/infrastructures/1/radl', headers=headers) + self.assertEqual(res.text, "Error Getting Inf. prop: Deleted infrastructure.") GetInfrastructureRADL.side_effect = IncorrectInfrastructureException() - res = RESTGetInfrastructureProperty("1", "radl") - self.assertEqual(res, "Error Getting Inf. prop: Invalid infrastructure ID or access not granted.") + res = self.client.get('/infrastructures/1/radl', headers=headers) + self.assertEqual(res.text, "Error Getting Inf. prop: Invalid infrastructure ID or access not granted.") GetInfrastructureRADL.side_effect = UnauthorizedUserException() - res = RESTGetInfrastructureProperty("1", "radl") - self.assertEqual(res, "Error Getting Inf. prop: Access to this infrastructure not granted.") + res = self.client.get('/infrastructures/1/radl', headers=headers) + self.assertEqual(res.text, "Error Getting Inf. prop: Access to this infrastructure not granted.") @patch("IM.InfrastructureManager.InfrastructureManager.DestroyInfrastructure") - @patch("bottle.request") - def test_DestroyInfrastructure(self, bottle_request, DestroyInfrastructure): - """Test REST DestroyInfrastructure.""" - bottle_request.return_value = MagicMock() - bottle_request.headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" - "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " - "username = user; password = pass")} - - res = RESTDestroyInfrastructure("1") - self.assertEqual(res, "") + def test_DestroyInfrastructure(self, DestroyInfrastructure): + headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" + "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " + "username = user; password = pass")} + + res = self.client.delete('/infrastructures/1', headers=headers) + self.assertEqual(res.text, "") self.assertEqual(DestroyInfrastructure.call_args_list[0][0][0], "1") self.assertEqual(DestroyInfrastructure.call_args_list[0][0][2], False) - bottle_request.params = {"force": "yes"} - res = RESTDestroyInfrastructure("1") - self.assertEqual(res, "") + res = self.client.delete('/infrastructures/1?force=yes', headers=headers) + self.assertEqual(res.text, "") self.assertEqual(DestroyInfrastructure.call_args_list[1][0][0], "1") self.assertEqual(DestroyInfrastructure.call_args_list[1][0][2], True) - bottle_request.params = {"async": "yes"} - res = RESTDestroyInfrastructure("1") - self.assertEqual(res, "") + res = self.client.delete('/infrastructures/1?async=yes', headers=headers) + self.assertEqual(res.text, "") self.assertEqual(DestroyInfrastructure.call_args_list[2][0][0], "1") self.assertEqual(DestroyInfrastructure.call_args_list[2][0][3], True) DestroyInfrastructure.side_effect = DeletedInfrastructureException() - res = RESTDestroyInfrastructure("1") - self.assertEqual(res, "Error Destroying Inf: Deleted infrastructure.") + res = self.client.delete('/infrastructures/1', headers=headers) + self.assertEqual(res.text, "Error Destroying Inf: Deleted infrastructure.") DestroyInfrastructure.side_effect = IncorrectInfrastructureException() - res = RESTDestroyInfrastructure("1") - self.assertEqual(res, "Error Destroying Inf: Invalid infrastructure ID or access not granted.") + res = self.client.delete('/infrastructures/1', headers=headers) + self.assertEqual(res.text, "Error Destroying Inf: Invalid infrastructure ID or access not granted.") DestroyInfrastructure.side_effect = UnauthorizedUserException() - res = RESTDestroyInfrastructure("1") - self.assertEqual(res, "Error Destroying Inf: Access to this infrastructure not granted.") + res = self.client.delete('/infrastructures/1', headers=headers) + self.assertEqual(res.text, "Error Destroying Inf: Access to this infrastructure not granted.") DestroyInfrastructure.side_effect = IncorrectStateException() - res = RESTDestroyInfrastructure("1") - self.assertEqual(res, "Error Destroying Inf: Invalid State to perform this operation.") + res = self.client.delete('/infrastructures/1', headers=headers) + self.assertEqual(res.text, "Error Destroying Inf: Invalid State to perform this operation.") @patch("IM.InfrastructureManager.InfrastructureManager.CreateInfrastructure") @patch("IM.InfrastructureManager.InfrastructureManager.get_infrastructure") - @patch("bottle.request") - def test_CreateInfrastructure(self, bottle_request, get_infrastructure, CreateInfrastructure): + def test_CreateInfrastructure(self, get_infrastructure, CreateInfrastructure): """Test REST CreateInfrastructure.""" - bottle_request.environ = {'HTTP_HOST': 'imserver.com'} - bottle_request.return_value = MagicMock() - bottle_request.headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" - "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " - "username = user; password = pass")} - bottle_request.body = BytesIO(b"radl") - + headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" + "id = one; type = OpenNebula; host = ramses.i3m.upv.es:2633; " + "username = user; password = pass")} CreateInfrastructure.return_value = "1" - res = RESTCreateInfrastructure() - self.assertEqual(res, "http://imserver.com/infrastructures/1") + res = self.client.post('/infrastructures', headers=headers, data=BytesIO(b"radl")) + self.assertEqual(res.text, "http://localhost/infrastructures/1") + self.assertEqual(res.headers['InfID'], "1") - bottle_request.params = {"async": "yes"} - res = RESTCreateInfrastructure() - self.assertEqual(res, "http://imserver.com/infrastructures/1") + res = self.client.post('/infrastructures?async=yes', headers=headers, data=BytesIO(b"radl")) + self.assertEqual(res.text, "http://localhost/infrastructures/1") - bottle_request.headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" - "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " - "username = user; password = pass"), - "Content-Type": "application/json"} - bottle_request.body = read_file_as_bytes("../files/test_simple.json") + headers["Content-Type"] = "application/json" + res = self.client.post('/infrastructures', headers=headers, + data=read_file_as_bytes("../files/test_simple.json")) + self.assertEqual(res.text, "http://localhost/infrastructures/1") - CreateInfrastructure.return_value = "1" - - res = RESTCreateInfrastructure() - self.assertEqual(res, "http://imserver.com/infrastructures/1") + headers["Content-Type"] = "text/yaml" + res = self.client.post('/infrastructures', headers=headers, + data=read_file_as_bytes("../files/tosca_simple.yml")) + self.assertEqual(res.text, "http://localhost/infrastructures/1") - bottle_request.headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" - "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " - "username = user; password = pass"), - "Content-Type": "text/yaml"} - bottle_request.body = read_file_as_bytes("../files/tosca_simple.yml") - - CreateInfrastructure.return_value = "1" - - res = RESTCreateInfrastructure() - self.assertEqual(res, "http://imserver.com/infrastructures/1") - - bottle_request.headers["Content-Type"] = "application/json" - bottle_request.body = read_file_as_bytes("../files/test_simple.json") + headers["Content-Type"] = "application/json" + # Test the dry_run option to get the estimation of the resources + res = self.client.post('/infrastructures?dry_run=yes', headers=headers, + data=read_file_as_bytes("../files/test_simple.json")) + self.assertEqual(res.json, {"one": {"cloudType": "OpenNebula", + "cloudEndpoint": "http://ramses.i3m.upv.es:2633", + "compute": [{"cpuCores": 1, "memoryInMegabytes": 1024}, + {"cpuCores": 1, "memoryInMegabytes": 1024}], "storage": []}}) + + headers["Content-Type"] = "application/json" CreateInfrastructure.side_effect = InvaliddUserException() - res = RESTCreateInfrastructure() - self.assertEqual(res, "Error Getting Inf. info: Invalid InfrastructureManager credentials") + res = self.client.post('/infrastructures', headers=headers, + data=read_file_as_bytes("../files/test_simple.json")) + self.assertEqual(res.text, "Error Getting Inf. info: Invalid InfrastructureManager credentials") - bottle_request.body = read_file_as_bytes("../files/test_simple.json") CreateInfrastructure.side_effect = UnauthorizedUserException() - res = RESTCreateInfrastructure() - self.assertEqual(res, "Error Creating Inf.: Access to this infrastructure not granted.") - - # Test the dry_run option to get the estimation of the resources - bottle_request.headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" - "id = one; type = OpenNebula; host = ramses.i3m.upv.es:2633; " - "username = user; password = pass"), - "Content-Type": "application/json"} - bottle_request.body = read_file_as_bytes("../files/test_simple.json") - bottle_request.params = {"dry_run": "yes"} - res = RESTCreateInfrastructure() - self.assertEqual(res, ('{"one": {"cloudType": "OpenNebula", "cloudEndpoint":' - ' "http://ramses.i3m.upv.es:2633",' - ' "compute": [{"cpuCores": 1, "memoryInMegabytes": 1024},' - ' {"cpuCores": 1, "memoryInMegabytes": 1024}], "storage": []}}')) + res = self.client.post('/infrastructures', headers=headers, + data=read_file_as_bytes("../files/test_simple.json")) + self.assertEqual(res.text, "Error Creating Inf.: Access to this infrastructure not granted.") @patch("IM.InfrastructureManager.InfrastructureManager.CreateInfrastructure") - @patch("bottle.request") - def test_CreateInfrastructureWithErrors(self, bottle_request, CreateInfrastructure): + def test_CreateInfrastructureWithErrors(self, CreateInfrastructure): """Test REST CreateInfrastructure.""" - bottle_request.environ = {'HTTP_HOST': 'imserver.com'} - bottle_request.return_value = MagicMock() - bottle_request.headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" - "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " - "username = user; password = pass"), - "Content-Type": "application/pdf", "Accept": "application/json"} - bottle_request.body = BytesIO(b"radl") + headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" + "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " + "username = user; password = pass"), + "Content-Type": "application/pdf", "Accept": "application/json"} CreateInfrastructure.return_value = "1" - res = RESTCreateInfrastructure() - res_json = json.loads(res) - self.assertEqual(res_json['code'], 415) + res = self.client.post('/infrastructures', headers=headers, data=BytesIO(b"radl")) + self.assertEqual(res.json['code'], 415) @patch("IM.InfrastructureManager.InfrastructureManager.GetVMInfo") - @patch("bottle.request") - def test_GetVMInfo(self, bottle_request, GetVMInfo): + def test_GetVMInfo(self, GetVMInfo): """Test REST GetVMInfo.""" - bottle_request.return_value = MagicMock() - bottle_request.headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" - "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " - "username = user; password = pass"), - "Accept": "application/json"} + headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" + "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " + "username = user; password = pass"), + "Accept": "application/json"} GetVMInfo.return_value = parse_radl("system test (cpu.count = 1)") - res = RESTGetVMInfo("1", "1") - self.assertEqual(json.loads(res), json.loads('{"radl": [{"cpu.count": 1, "class": "system", "id": "test"}]}')) + res = self.client.get('/infrastructures/1/vms/1', headers=headers) + self.assertEqual(res.json, {"radl": [{"cpu.count": 1, "class": "system", "id": "test"}]}) - bottle_request.headers["Accept"] = "text/*" - res = RESTGetVMInfo("1", "1") - self.assertEqual(res, 'system test (\ncpu.count = 1\n)\n\n') + headers["Accept"] = "text/*" + res = self.client.get('/infrastructures/1/vms/1', headers=headers) + self.assertEqual(res.text, 'system test (\ncpu.count = 1\n)\n\n') GetVMInfo.side_effect = DeletedInfrastructureException() - res = RESTGetVMInfo("1", "1") - self.assertEqual(res, "Error Getting VM. info: Deleted infrastructure.") + res = self.client.get('/infrastructures/1/vms/1', headers=headers) + self.assertEqual(res.text, "Error Getting VM. info: Deleted infrastructure.") GetVMInfo.side_effect = IncorrectInfrastructureException() - res = RESTGetVMInfo("1", "1") - self.assertEqual(res, "Error Getting VM. info: Invalid infrastructure ID or access not granted.") + res = self.client.get('/infrastructures/1/vms/1', headers=headers) + self.assertEqual(res.text, "Error Getting VM. info: Invalid infrastructure ID or access not granted.") GetVMInfo.side_effect = UnauthorizedUserException() - res = RESTGetVMInfo("1", "1") - self.assertEqual(res, "Error Getting VM. info: Access to this infrastructure not granted.") + res = self.client.get('/infrastructures/1/vms/1', headers=headers) + self.assertEqual(res.text, "Error Getting VM. info: Access to this infrastructure not granted.") GetVMInfo.side_effect = DeletedVMException() - res = RESTGetVMInfo("1", "1") - self.assertEqual(res, "Error Getting VM. info: Deleted VM.") + res = self.client.get('/infrastructures/1/vms/1', headers=headers) + self.assertEqual(res.text, "Error Getting VM. info: Deleted VM.") GetVMInfo.side_effect = IncorrectVMException() - res = RESTGetVMInfo("1", "1") - self.assertEqual(res, "Error Getting VM. info: Invalid VM ID") + res = self.client.get('/infrastructures/1/vms/1', headers=headers) + self.assertEqual(res.text, "Error Getting VM. info: Invalid VM ID") @patch("IM.InfrastructureManager.InfrastructureManager.GetVMProperty") @patch("IM.InfrastructureManager.InfrastructureManager.GetVMContMsg") - @patch("bottle.request") - def test_GetVMProperty(self, bottle_request, GetVMContMsg, GetVMProperty): + def test_GetVMProperty(self, GetVMContMsg, GetVMProperty): """Test REST GetVMProperty.""" - bottle_request.return_value = MagicMock() - bottle_request.headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" - "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " - "username = user; password = pass")} + headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" + "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " + "username = user; password = pass")} GetVMProperty.return_value = "prop" GetVMContMsg.return_value = "contmsg" - res = RESTGetVMProperty("1", "1", "prop") - self.assertEqual(res, "prop") + res = self.client.get('/infrastructures/1/vms/1/prop', headers=headers) + self.assertEqual(res.text, "prop") - res = RESTGetVMProperty("1", "1", "contmsg") - self.assertEqual(res, "contmsg") + res = self.client.get('/infrastructures/1/vms/1/contmsg', headers=headers) + self.assertEqual(res.text, "contmsg") GetVMProperty.side_effect = DeletedInfrastructureException() - res = RESTGetVMProperty("1", "1", "prop") - self.assertEqual(res, "Error Getting VM. property: Deleted infrastructure.") + res = self.client.get('/infrastructures/1/vms/1/prop', headers=headers) + self.assertEqual(res.text, "Error Getting VM. property: Deleted infrastructure.") GetVMProperty.side_effect = IncorrectInfrastructureException() - res = RESTGetVMProperty("1", "1", "prop") - self.assertEqual(res, "Error Getting VM. property: Invalid infrastructure ID or access not granted.") + res = self.client.get('/infrastructures/1/vms/1/prop', headers=headers) + self.assertEqual(res.text, "Error Getting VM. property: Invalid infrastructure ID or access not granted.") GetVMProperty.side_effect = UnauthorizedUserException() - res = RESTGetVMProperty("1", "1", "prop") - self.assertEqual(res, "Error Getting VM. property: Access to this infrastructure not granted.") + res = self.client.get('/infrastructures/1/vms/1/prop', headers=headers) + self.assertEqual(res.text, "Error Getting VM. property: Access to this infrastructure not granted.") GetVMProperty.side_effect = DeletedVMException() - res = RESTGetVMProperty("1", "1", "prop") - self.assertEqual(res, "Error Getting VM. property: Deleted VM.") + res = self.client.get('/infrastructures/1/vms/1/prop', headers=headers) + self.assertEqual(res.text, "Error Getting VM. property: Deleted VM.") GetVMProperty.side_effect = IncorrectVMException() - res = RESTGetVMProperty("1", "1", "prop") - self.assertEqual(res, "Error Getting VM. property: Invalid VM ID") + res = self.client.get('/infrastructures/1/vms/1/prop', headers=headers) + self.assertEqual(res.text, "Error Getting VM. property: Invalid VM ID") @patch("IM.InfrastructureManager.InfrastructureManager.AddResource") @patch("IM.InfrastructureManager.InfrastructureManager.get_infrastructure") - @patch("bottle.request") - def test_AddResource(self, bottle_request, get_infrastructure, AddResource): + def test_AddResource(self, get_infrastructure, AddResource): """Test REST AddResource.""" - bottle_request.environ = {'HTTP_HOST': 'imserver.com'} - bottle_request.return_value = MagicMock() - bottle_request.headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" - "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " - "username = user; password = pass")} - bottle_request.body = BytesIO(b"radl") - bottle_request.params = {'context': 'yes'} + headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" + "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " + "username = user; password = pass")} AddResource.return_value = "1" - res = RESTAddResource("1") - self.assertEqual(res, "http://imserver.com/infrastructures/1/vms/1") - - bottle_request.headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" - "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " - "username = user; password = pass"), - "Content-Type": "application/json"} - bottle_request.body = read_file_as_bytes("../files/test_simple.json") + res = self.client.post('/infrastructures/1?context=yes', headers=headers, data=BytesIO(b"radl")) + self.assertEqual(res.text, "http://localhost/infrastructures/1/vms/1") + self.assertEqual(res.headers['InfID'], "1") - res = RESTAddResource("1") - self.assertEqual(res, "http://imserver.com/infrastructures/1/vms/1") + headers["Content-Type"] = "application/json" + res = self.client.post('/infrastructures/1', headers=headers, + data=read_file_as_bytes("../files/test_simple.json")) + self.assertEqual(res.text, "http://localhost/infrastructures/1/vms/1") - bottle_request.headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" - "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " - "username = user; password = pass"), - "Content-Type": "text/yaml"} - bottle_request.body = read_file_as_bytes("../files/tosca_simple.yml") + headers["Content-Type"] = "text/yaml" + res = self.client.post('/infrastructures/1', headers=headers, + data=read_file_as_bytes("../files/tosca_simple.yml")) + self.assertEqual(res.text, "http://localhost/infrastructures/1/vms/1") - res = RESTAddResource("1") - self.assertEqual(res, "http://imserver.com/infrastructures/1/vms/1") - - bottle_request.headers["Content-Type"] = "application/json" - bottle_request.body = read_file_as_bytes("../files/test_simple.json") + headers["Content-Type"] = "application/json" AddResource.side_effect = DeletedInfrastructureException() - res = RESTAddResource("1") - self.assertEqual(res, "Error Adding resources: Deleted infrastructure.") + res = self.client.post('/infrastructures/1', headers=headers, + data=read_file_as_bytes("../files/test_simple.json")) + self.assertEqual(res.text, "Error Adding resources: Deleted infrastructure.") - bottle_request.body = read_file_as_bytes("../files/test_simple.json") AddResource.side_effect = IncorrectInfrastructureException() - res = RESTAddResource("1") - self.assertEqual(res, "Error Adding resources: Invalid infrastructure ID or access not granted.") + res = self.client.post('/infrastructures/1', headers=headers, + data=read_file_as_bytes("../files/test_simple.json")) + self.assertEqual(res.text, "Error Adding resources: Invalid infrastructure ID or access not granted.") - bottle_request.body = read_file_as_bytes("../files/test_simple.json") AddResource.side_effect = UnauthorizedUserException() - res = RESTAddResource("1") - self.assertEqual(res, "Error Adding resources: Access to this infrastructure not granted.") + res = self.client.post('/infrastructures/1', headers=headers, + data=read_file_as_bytes("../files/test_simple.json")) + self.assertEqual(res.text, "Error Adding resources: Access to this infrastructure not granted.") @patch("IM.InfrastructureManager.InfrastructureManager.RemoveResource") - @patch("bottle.request") - def test_RemoveResource(self, bottle_request, RemoveResource): + def test_RemoveResource(self, RemoveResource): """Test REST RemoveResource.""" - bottle_request.return_value = MagicMock() - bottle_request.headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" - "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " - "username = user; password = pass")} - bottle_request.params = {'context': 'yes'} + headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" + "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " + "username = user; password = pass")} RemoveResource.return_value = 2 - res = RESTRemoveResource("1", "1,2") - self.assertEqual(res, "") + res = self.client.delete('/infrastructures/1/vms/1,2?context=yes', headers=headers) + self.assertEqual(res.text, "") RemoveResource.side_effect = DeletedInfrastructureException() - res = RESTRemoveResource("1", "1,2") - self.assertEqual(res, "Error Removing resources: Deleted infrastructure.") + res = self.client.delete('/infrastructures/1/vms/1', headers=headers) + self.assertEqual(res.text, "Error Removing resources: Deleted infrastructure.") RemoveResource.side_effect = IncorrectInfrastructureException() - res = RESTRemoveResource("1", "1,2") - self.assertEqual(res, "Error Removing resources: Invalid infrastructure ID or access not granted.") + res = self.client.delete('/infrastructures/1/vms/1', headers=headers) + self.assertEqual(res.text, "Error Removing resources: Invalid infrastructure ID or access not granted.") RemoveResource.side_effect = UnauthorizedUserException() - res = RESTRemoveResource("1", "1,2") - self.assertEqual(res, "Error Removing resources: Access to this infrastructure not granted.") + res = self.client.delete('/infrastructures/1/vms/1', headers=headers) + self.assertEqual(res.text, "Error Removing resources: Access to this infrastructure not granted.") RemoveResource.side_effect = DeletedVMException() - res = RESTRemoveResource("1", "1,2") - self.assertEqual(res, "Error Removing resources: Deleted VM.") + res = self.client.delete('/infrastructures/1/vms/1', headers=headers) + self.assertEqual(res.text, "Error Removing resources: Deleted VM.") RemoveResource.side_effect = IncorrectVMException() - res = RESTRemoveResource("1", "1,2") - self.assertEqual(res, "Error Removing resources: Invalid VM ID") + res = self.client.delete('/infrastructures/1/vms/1', headers=headers) + self.assertEqual(res.text, "Error Removing resources: Invalid VM ID") @patch("IM.InfrastructureManager.InfrastructureManager.AlterVM") - @patch("bottle.request") - def test_AlterVM(self, bottle_request, AlterVM): + def test_AlterVM(self, AlterVM): """Test REST AlterVM.""" - bottle_request.return_value = MagicMock() - bottle_request.headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" - "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " - "username = user; password = pass")} - bottle_request.body = BytesIO(b"radl") - bottle_request.params = {'context': 'yes'} + headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" + "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " + "username = user; password = pass")} AlterVM.return_value = "vm_info" - res = RESTAlterVM("1", "1") - self.assertEqual(res, "vm_info") - - bottle_request.headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" - "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " - "username = user; password = pass"), - "Content-Type": "text/yaml"} - bottle_request.body = read_file_as_bytes("../files/tosca_simple.yml") + res = self.client.put('/infrastructures/1/vms/1?context=yes', headers=headers, data=BytesIO(b"radl")) + self.assertEqual(res.text, "vm_info") - res = RESTAlterVM("1", "1") - self.assertEqual(res, "vm_info") + headers["Content-Type"] = "text/yaml" + res = self.client.put('/infrastructures/1/vms/1', headers=headers, + data=read_file_as_bytes("../files/tosca_simple.yml")) + self.assertEqual(res.text, "vm_info") - bottle_request.headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" - "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " - "username = user; password = pass"), - "Content-Type": "application/json"} - bottle_request.body = read_file_as_bytes("../files/test_simple.json") + headers["Content-Type"] = "application/json" + res = self.client.put('/infrastructures/1/vms/1', headers=headers, + data=read_file_as_bytes("../files/test_simple.json")) + self.assertEqual(res.text, "vm_info") - res = RESTAlterVM("1", "1") - self.assertEqual(res, "vm_info") - - bottle_request.body = read_file_as_bytes("../files/test_simple.json") AlterVM.side_effect = DeletedInfrastructureException() - res = RESTAlterVM("1", "1") - self.assertEqual(res, "Error modifying resources: Deleted infrastructure.") + res = self.client.put('/infrastructures/1/vms/1', headers=headers, + data=read_file_as_bytes("../files/test_simple.json")) + self.assertEqual(res.text, "Error modifying resources: Deleted infrastructure.") - bottle_request.body = read_file_as_bytes("../files/test_simple.json") AlterVM.side_effect = IncorrectInfrastructureException() - res = RESTAlterVM("1", "1") - self.assertEqual(res, "Error modifying resources: Invalid infrastructure ID or access not granted.") + res = self.client.put('/infrastructures/1/vms/1', headers=headers, + data=read_file_as_bytes("../files/test_simple.json")) + self.assertEqual(res.text, "Error modifying resources: Invalid infrastructure ID or access not granted.") - bottle_request.body = read_file_as_bytes("../files/test_simple.json") AlterVM.side_effect = UnauthorizedUserException() - res = RESTAlterVM("1", "1") - self.assertEqual(res, "Error modifying resources: Access to this infrastructure not granted.") + res = self.client.put('/infrastructures/1/vms/1', headers=headers, + data=read_file_as_bytes("../files/test_simple.json")) + self.assertEqual(res.text, "Error modifying resources: Access to this infrastructure not granted.") - bottle_request.body = read_file_as_bytes("../files/test_simple.json") AlterVM.side_effect = DeletedVMException() - res = RESTAlterVM("1", "1") - self.assertEqual(res, "Error modifying resources: Deleted VM.") + res = self.client.put('/infrastructures/1/vms/1', headers=headers, + data=read_file_as_bytes("../files/test_simple.json")) + self.assertEqual(res.text, "Error modifying resources: Deleted VM.") - bottle_request.body = read_file_as_bytes("../files/test_simple.json") AlterVM.side_effect = IncorrectVMException() - res = RESTAlterVM("1", "1") - self.assertEqual(res, "Error modifying resources: Invalid VM ID") + res = self.client.put('/infrastructures/1/vms/1', headers=headers, + data=read_file_as_bytes("../files/test_simple.json")) + self.assertEqual(res.text, "Error modifying resources: Invalid VM ID") @patch("IM.InfrastructureManager.InfrastructureManager.Reconfigure") - @patch("bottle.request") - def test_Reconfigure(self, bottle_request, Reconfigure): + def test_Reconfigure(self, Reconfigure): """Test REST Reconfigure.""" - bottle_request.return_value = MagicMock() - bottle_request.headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" - "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " - "username = user; password = pass")} - bottle_request.body = BytesIO(b"radl") - bottle_request.params = {'vm_list': '1,2'} + headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" + "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " + "username = user; password = pass")} Reconfigure.return_value = "" - res = RESTReconfigureInfrastructure("1") - self.assertEqual(res, "") - - bottle_request.headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" - "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " - "username = user; password = pass"), - "Content-Type": "application/json"} - bottle_request.body = read_file_as_bytes("../files/test_simple.json") + res = self.client.put('/infrastructures/1/reconfigure?vmlist=1,2', headers=headers, data=BytesIO(b"radl")) + self.assertEqual(res.text, "") + self.assertEqual(res.headers['InfID'], "1") - res = RESTReconfigureInfrastructure("1") - self.assertEqual(res, "") + headers["Content-Type"] = "application/json" + res = self.client.put('/infrastructures/1/reconfigure?vmlist=1,2', + headers=headers, data=read_file_as_bytes("../files/test_simple.json")) + self.assertEqual(res.text, "") - bottle_request.body = read_file_as_bytes("../files/test_simple.json") Reconfigure.side_effect = DeletedInfrastructureException() - res = RESTReconfigureInfrastructure("1") - self.assertEqual(res, "Error reconfiguring infrastructure: Deleted infrastructure.") + res = self.client.put('/infrastructures/1/reconfigure?vmlist=1,2', + headers=headers, data=read_file_as_bytes("../files/test_simple.json")) + self.assertEqual(res.text, "Error reconfiguring infrastructure: Deleted infrastructure.") - bottle_request.body = read_file_as_bytes("../files/test_simple.json") Reconfigure.side_effect = IncorrectInfrastructureException() - res = RESTReconfigureInfrastructure("1") - self.assertEqual(res, "Error reconfiguring infrastructure: Invalid infrastructure ID or access not granted.") + res = self.client.put('/infrastructures/1/reconfigure?vmlist=1,2', + headers=headers, data=read_file_as_bytes("../files/test_simple.json")) + self.assertEqual(res.text, ("Error reconfiguring infrastructure: " + + "Invalid infrastructure ID or access not granted.")) - bottle_request.body = read_file_as_bytes("../files/test_simple.json") Reconfigure.side_effect = UnauthorizedUserException() - res = RESTReconfigureInfrastructure("1") - self.assertEqual(res, "Error reconfiguring infrastructure: Access to this infrastructure not granted.") + res = self.client.put('/infrastructures/1/reconfigure?vmlist=1,2', + headers=headers, data=read_file_as_bytes("../files/test_simple.json")) + self.assertEqual(res.text, "Error reconfiguring infrastructure: Access to this infrastructure not granted.") @patch("IM.InfrastructureManager.InfrastructureManager.StartInfrastructure") - @patch("bottle.request") - def test_StartInfrastructure(self, bottle_request, StartInfrastructure): - """Test REST StartInfrastructure.""" - bottle_request.return_value = MagicMock() - bottle_request.headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" - "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " - "username = user; password = pass")} - - StartInfrastructure.return_value = "" - - res = RESTStartInfrastructure("1") - self.assertEqual(res, "") - - StartInfrastructure.side_effect = DeletedInfrastructureException() - res = RESTStartInfrastructure("1") - self.assertEqual(res, "Error starting infrastructure: Deleted infrastructure.") - - StartInfrastructure.side_effect = IncorrectInfrastructureException() - res = RESTStartInfrastructure("1") - self.assertEqual(res, "Error starting infrastructure: Invalid infrastructure ID or access not granted.") - - StartInfrastructure.side_effect = UnauthorizedUserException() - res = RESTStartInfrastructure("1") - self.assertEqual(res, "Error starting infrastructure: Access to this infrastructure not granted.") - @patch("IM.InfrastructureManager.InfrastructureManager.StopInfrastructure") - @patch("bottle.request") - def test_StopInfrastructure(self, bottle_request, StopInfrastructure): - """Test REST StopInfrastructure.""" - bottle_request.return_value = MagicMock() - bottle_request.headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" - "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " - "username = user; password = pass")} - - StopInfrastructure.return_value = "" - - res = RESTStopInfrastructure("1") - self.assertEqual(res, "") - - StopInfrastructure.side_effect = DeletedInfrastructureException() - res = RESTStopInfrastructure("1") - self.assertEqual(res, "Error stopping infrastructure: Deleted infrastructure.") - - StopInfrastructure.side_effect = IncorrectInfrastructureException() - res = RESTStopInfrastructure("1") - self.assertEqual(res, "Error stopping infrastructure: Invalid infrastructure ID or access not granted.") - - StopInfrastructure.side_effect = UnauthorizedUserException() - res = RESTStopInfrastructure("1") - self.assertEqual(res, "Error stopping infrastructure: Access to this infrastructure not granted.") + def test_OperateInfrastructure(self, StopInfrastructure, StartInfrastructure): + """Test REST StartInfrastructure and StopInfrastructure.""" + headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" + "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " + "username = user; password = pass")} + + for op in ["start", "stop"]: + StartInfrastructure.side_effect = None + StopInfrastructure.side_effect = None + StartInfrastructure.return_value = "" + StopInfrastructure.return_value = "" + + res = self.client.put('/infrastructures/1/%s' % op, headers=headers) + self.assertEqual(res.text, "") + + StartInfrastructure.side_effect = DeletedInfrastructureException() + StopInfrastructure.side_effect = DeletedInfrastructureException() + res = self.client.put('/infrastructures/1/%s' % op, headers=headers) + self.assertEqual(res.text, "Error in %s operation: Deleted infrastructure." % op) + + StartInfrastructure.side_effect = IncorrectInfrastructureException() + StopInfrastructure.side_effect = IncorrectInfrastructureException() + res = self.client.put('/infrastructures/1/%s' % op, headers=headers) + self.assertEqual(res.text, "Error in %s operation: Invalid infrastructure ID or access not granted." % op) + + StartInfrastructure.side_effect = UnauthorizedUserException() + StopInfrastructure.side_effect = UnauthorizedUserException() + res = self.client.put('/infrastructures/1/%s' % op, headers=headers) + self.assertEqual(res.text, "Error in %s operation: Access to this infrastructure not granted." % op) @patch("IM.InfrastructureManager.InfrastructureManager.StartVM") - @patch("bottle.request") - def test_StartVM(self, bottle_request, StartVM): - """Test REST StartVM.""" - bottle_request.return_value = MagicMock() - bottle_request.headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" - "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " - "username = user; password = pass")} - - StartVM.return_value = "" - - res = RESTStartVM("1", "1") - self.assertEqual(res, "") - - StartVM.side_effect = DeletedInfrastructureException() - res = RESTStartVM("1", "1") - self.assertEqual(res, "Error starting VM: Deleted infrastructure.") - - StartVM.side_effect = IncorrectInfrastructureException() - res = RESTStartVM("1", "1") - self.assertEqual(res, "Error starting VM: Invalid infrastructure ID or access not granted.") - - StartVM.side_effect = UnauthorizedUserException() - res = RESTStartVM("1", "1") - self.assertEqual(res, "Error starting VM: Access to this infrastructure not granted.") - - StartVM.side_effect = DeletedVMException() - res = RESTStartVM("1", "1") - self.assertEqual(res, "Error starting VM: Deleted VM.") - - StartVM.side_effect = IncorrectVMException() - res = RESTStartVM("1", "1") - self.assertEqual(res, "Error starting VM: Invalid VM ID") - @patch("IM.InfrastructureManager.InfrastructureManager.StopVM") - @patch("bottle.request") - def test_StopVM(self, bottle_request, StopVM): - """Test REST StopVM.""" - bottle_request.return_value = MagicMock() - bottle_request.headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" - "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " - "username = user; password = pass")} - - StopVM.return_value = "" - - res = RESTStopVM("1", "1") - self.assertEqual(res, "") - - StopVM.side_effect = DeletedInfrastructureException() - res = RESTStopVM("1", "1") - self.assertEqual(res, "Error stopping VM: Deleted infrastructure.") - - StopVM.side_effect = IncorrectInfrastructureException() - res = RESTStopVM("1", "1") - self.assertEqual(res, "Error stopping VM: Invalid infrastructure ID or access not granted.") - - StopVM.side_effect = UnauthorizedUserException() - res = RESTStopVM("1", "1") - self.assertEqual(res, "Error stopping VM: Access to this infrastructure not granted.") - - StopVM.side_effect = DeletedVMException() - res = RESTStopVM("1", "1") - self.assertEqual(res, "Error stopping VM: Deleted VM.") - - StopVM.side_effect = IncorrectVMException() - res = RESTStopVM("1", "1") - self.assertEqual(res, "Error stopping VM: Invalid VM ID") - @patch("IM.InfrastructureManager.InfrastructureManager.RebootVM") - @patch("bottle.request") - def test_RebootVM(self, bottle_request, StopVM): - """Test REST RebootVM.""" - bottle_request.return_value = MagicMock() - bottle_request.headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" - "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " - "username = user; password = pass")} - - StopVM.return_value = "" - - res = RESTRebootVM("1", "1") - self.assertEqual(res, "") - - StopVM.side_effect = DeletedInfrastructureException() - res = RESTRebootVM("1", "1") - self.assertEqual(res, "Error rebooting VM: Deleted infrastructure.") - - StopVM.side_effect = IncorrectInfrastructureException() - res = RESTRebootVM("1", "1") - self.assertEqual(res, "Error rebooting VM: Invalid infrastructure ID or access not granted.") - - StopVM.side_effect = UnauthorizedUserException() - res = RESTRebootVM("1", "1") - self.assertEqual(res, "Error rebooting VM: Access to this infrastructure not granted.") - - StopVM.side_effect = DeletedVMException() - res = RESTRebootVM("1", "1") - self.assertEqual(res, "Error rebooting VM: Deleted VM.") - - StopVM.side_effect = IncorrectVMException() - res = RESTRebootVM("1", "1") - self.assertEqual(res, "Error rebooting VM: Invalid VM ID") + def test_OperateVM(self, RebootVM, StopVM, StartVM): + """Test REST StartVM, StopVM and Reboot.""" + headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" + "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " + "username = user; password = pass")} + + for op in ["start", "stop", "reboot"]: + StartVM.side_effect = None + StartVM.return_value = "" + StopVM.side_effect = None + StopVM.return_value = "" + RebootVM.side_effect = None + RebootVM.return_value = "" + + res = self.client.put('/infrastructures/1/vms/1/%s' % op, headers=headers) + self.assertEqual(res.text, "") + + StartVM.side_effect = DeletedInfrastructureException() + StopVM.side_effect = DeletedInfrastructureException() + RebootVM.side_effect = DeletedInfrastructureException() + res = self.client.put('/infrastructures/1/vms/1/%s' % op, headers=headers) + self.assertEqual(res.text, "Error in %s op in VM: Deleted infrastructure." % op) + + StartVM.side_effect = IncorrectInfrastructureException() + StopVM.side_effect = IncorrectInfrastructureException() + RebootVM.side_effect = IncorrectInfrastructureException() + res = self.client.put('/infrastructures/1/vms/1/%s' % op, headers=headers) + self.assertEqual(res.text, "Error in %s op in VM: Invalid infrastructure ID or access not granted." % op) + + StartVM.side_effect = UnauthorizedUserException() + StopVM.side_effect = UnauthorizedUserException() + RebootVM.side_effect = UnauthorizedUserException() + res = self.client.put('/infrastructures/1/vms/1/%s' % op, headers=headers) + self.assertEqual(res.text, "Error in %s op in VM: Access to this infrastructure not granted." % op) + + StartVM.side_effect = DeletedVMException() + StopVM.side_effect = DeletedVMException() + RebootVM.side_effect = DeletedVMException() + res = self.client.put('/infrastructures/1/vms/1/%s' % op, headers=headers) + self.assertEqual(res.text, "Error in %s op in VM: Deleted VM." % op) + + StartVM.side_effect = IncorrectVMException() + StopVM.side_effect = IncorrectVMException() + RebootVM.side_effect = IncorrectVMException() + res = self.client.put('/infrastructures/1/vms/1/%s' % op, headers=headers) + self.assertEqual(res.text, "Error in %s op in VM: Invalid VM ID" % op) def test_GeVersion(self): - res = RESTGetVersion() - self.assertEqual(res, version) + res = self.client.get('/version') + self.assertEqual(res.text, version) @patch("IM.InfrastructureManager.InfrastructureManager.CreateDiskSnapshot") - @patch("bottle.request") - def test_CreateDiskSnapshot(self, bottle_request, CreateDiskSnapshot): + def test_CreateDiskSnapshot(self, CreateDiskSnapshot): """Test REST StopVM.""" - bottle_request.return_value = MagicMock() - bottle_request.headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" - "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " - "username = user; password = pass")} + headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" + "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " + "username = user; password = pass")} - bottle_request.params = {'image_name': 'image_url', 'auto_delete': 'yes'} CreateDiskSnapshot.return_value = "one://server.com/image_url" - res = RESTCreateDiskSnapshot("1", "1", 0) - self.assertEqual(res, "one://server.com/image_url") + res = self.client.put('/infrastructures/1/vms/1/disks/0/snapshot?image_name=image_url&auto_delete=yes', + headers=headers) + self.assertEqual(res.text, "one://server.com/image_url") CreateDiskSnapshot.side_effect = DeletedInfrastructureException() - res = RESTCreateDiskSnapshot("1", "1", 0) - self.assertEqual(res, "Error creating snapshot: Deleted infrastructure.") + res = self.client.put('/infrastructures/1/vms/1/disks/0/snapshot?image_name=image_url&auto_delete=yes', + headers=headers) + self.assertEqual(res.text, "Error creating snapshot: Deleted infrastructure.") CreateDiskSnapshot.side_effect = IncorrectInfrastructureException() - res = RESTCreateDiskSnapshot("1", "1", 0) - self.assertEqual(res, "Error creating snapshot: Invalid infrastructure ID or access not granted.") + res = self.client.put('/infrastructures/1/vms/1/disks/0/snapshot?image_name=image_url&auto_delete=yes', + headers=headers) + self.assertEqual(res.text, "Error creating snapshot: Invalid infrastructure ID or access not granted.") CreateDiskSnapshot.side_effect = UnauthorizedUserException() - res = RESTCreateDiskSnapshot("1", "1", 0) - self.assertEqual(res, "Error creating snapshot: Access to this infrastructure not granted.") + res = self.client.put('/infrastructures/1/vms/1/disks/0/snapshot?image_name=image_url&auto_delete=yes', + headers=headers) + self.assertEqual(res.text, "Error creating snapshot: Access to this infrastructure not granted.") CreateDiskSnapshot.side_effect = DeletedVMException() - res = RESTCreateDiskSnapshot("1", "1", 0) - self.assertEqual(res, "Error creating snapshot: Deleted VM.") + res = self.client.put('/infrastructures/1/vms/1/disks/0/snapshot?image_name=image_url&auto_delete=yes', + headers=headers) + self.assertEqual(res.text, "Error creating snapshot: Deleted VM.") CreateDiskSnapshot.side_effect = IncorrectVMException() - res = RESTCreateDiskSnapshot("1", "1", 0) - self.assertEqual(res, "Error creating snapshot: Invalid VM ID") + res = self.client.put('/infrastructures/1/vms/1/disks/0/snapshot?image_name=image_url&auto_delete=yes', + headers=headers) + self.assertEqual(res.text, "Error creating snapshot: Invalid VM ID") @patch("IM.InfrastructureManager.InfrastructureManager.ExportInfrastructure") - @patch("bottle.request") - def test_ExportInfrastructure(self, bottle_request, ExportInfrastructure): + def test_ExportInfrastructure(self, ExportInfrastructure): """Test REST StopInfrastructure.""" - bottle_request.return_value = MagicMock() - bottle_request.headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" - "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " - "username = user; password = pass")} + headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" + "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " + "username = user; password = pass")} ExportInfrastructure.return_value = "strinf" - res = RESTGetInfrastructureProperty("1", "data") - self.assertEqual(res, '{"data": "strinf"}') + res = self.client.get('/infrastructures/1/data', headers=headers) + self.assertEqual(res.json, {"data": "strinf"}) @patch("IM.InfrastructureManager.InfrastructureManager.ImportInfrastructure") - @patch("bottle.request") - def test_ImportInfrastructure(self, bottle_request, ImportInfrastructure): + def test_ImportInfrastructure(self, ImportInfrastructure): """Test REST StopInfrastructure.""" - bottle_request.return_value = MagicMock() - bottle_request.headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" - "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " - "username = user; password = pass")} - bottle_request.environ = {'HTTP_HOST': 'imserver.com'} + headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" + "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " + "username = user; password = pass")} ImportInfrastructure.return_value = "newid" - res = RESTImportInfrastructure() - self.assertEqual(res, "http://imserver.com/infrastructures/newid") - - @patch("IM.REST.get_media_type") - def test_return_error(self, get_media_type): - get_media_type.return_value = ["application/json"] - msg = return_error(400, "Error msg.") - res = json.loads(msg) - self.assertEqual(res, {"message": "Error msg.", "code": 400}) - get_media_type.return_value = "text/html" - msg = return_error(400, "Error msg.") - self.assertEqual(msg, ('\n\n \n' - ' Error 400.\n \n \n ' - '

Code: 400.

\n

Message: Error msg.

\n \n\n')) - get_media_type.return_value = "text/plain" - msg = return_error(400, "Error msg.") - self.assertEqual(msg, "Error msg.") - - @patch("IM.REST.get_media_type") - def test_format_output(self, get_media_type): - get_media_type.return_value = ["application/json"] - radl = parse_radl("system test (cpu.count = 1)") - info = format_output(radl) - info = json.loads(info) - self.assertEqual(info, [{"cpu.count": 1, "class": "system", "id": "test"}]) - info = format_output(radl, field_name="radl") - info = json.loads(info) - self.assertEqual(info, {"radl": [{"cpu.count": 1, "class": "system", "id": "test"}]}) - - radl = parse_radl("system test ( disk.0.applications contains (name='test'))") - res = list(radl.systems[0].props.values())[0] - info = format_output(res, field_name="cont") - info = json.loads(info) - self.assertEqual(info, {"cont": {"test": {"name": "test"}}}) - - info = format_output(["1", "2"]) - info = json.loads(info) - self.assertEqual(info, ["1", "2"]) - - get_media_type.return_value = ["text/*"] - info = format_output(["1", "2"], field_name="cont", default_type="application/json") - info = json.loads(info) - self.assertEqual(info, {"cont": ["1", "2"]}) - info = format_output(["1", "2"]) - self.assertEqual(info, '1\n2') - - info = format_output(u'contmsg\xe1', field_name="contmsg", default_type="text/plain") - self.assertEqual(info, u'contmsg\xe1') - - get_media_type.return_value = ["application/zip"] - info = format_output(["1", "2"]) - self.assertEqual(info, 'Unsupported Accept Media Types: application/zip') + res = self.client.put('/infrastructures', headers=headers, data=BytesIO(b'{"data": "strinf"}')) + self.assertEqual(res.text, "http://localhost/infrastructures/newid") @patch("IM.VirtualMachine.SSH") @patch("IM.InfrastructureManager.InfrastructureManager.get_infrastructure") @patch("IM.InfrastructureManager.InfrastructureManager.check_auth_data") - @patch("bottle.request") - def test_commands(self, bottle_request, check_auth_data, get_infrastructure, SSH): + def test_commands(self, check_auth_data, get_infrastructure, SSH): """Test REST StopInfrastructure.""" - bottle_request.return_value = MagicMock() - bottle_request.headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" - "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " - "username = user; password = pass")} - bottle_request.environ = {'HTTP_HOST': 'imserver.com'} + headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" + "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " + "username = user; password = pass")} inf = InfrastructureInfo() inf.id = "1" inf.auth = Authentication([{'type': 'InfrastructureManager', 'username': 'user', 'password': 'pass'}]) get_infrastructure.return_value = inf - bottle_request.params = {'step': '1'} - res = RESTGetVMProperty("1", "1", "command") + res = self.client.get('/infrastructures/1/vms/1/command?step=1', headers=headers) auth_str = "Authorization: type = InfrastructureManager; username = user; password = pass" - url = "http://imserver.com/infrastructures/1/vms/1/command?step=2" + url = "http://localhost/infrastructures/1/vms/1/command?step=2" ps_command = "ps aux | grep -v grep | grep 'ssh -N -R'" expected_res = """ res="wait" @@ -930,12 +666,12 @@ def test_commands(self, bottle_request, check_auth_data, get_infrastructure, SSH sleep 20 fi done""" % (auth_str, url, ps_command) - self.assertEqual(res, expected_res) + self.assertEqual(res.text, expected_res) inf.auth = Authentication([{'type': 'InfrastructureManager', 'token': 'token'}]) - res = RESTGetVMProperty("1", "1", "command") + res = self.client.get('/infrastructures/1/vms/1/command?step=1', headers=headers) auth_str = "Authorization: type = InfrastructureManager; token = token" - url = "http://imserver.com/infrastructures/1/vms/1/command?step=2" + url = "http://localhost/infrastructures/1/vms/1/command?step=2" expected_res = """ res="wait" while [ "$res" == "wait" ] @@ -954,7 +690,7 @@ def test_commands(self, bottle_request, check_auth_data, get_infrastructure, SSH sleep 20 fi done""" % (auth_str, url, ps_command) - self.assertEqual(res, expected_res) + self.assertEqual(res.text, expected_res) radl_master = parse_radl(""" network publica (outbound = 'yes') @@ -1005,7 +741,6 @@ def test_commands(self, bottle_request, check_auth_data, get_infrastructure, SSH """) # in the Master VM - bottle_request.params = {'step': '2'} inf.vm_master = VirtualMachine(inf, None, None, radl_master, radl_master) inf.vm_master.creation_im_id = 0 ssh = MagicMock() @@ -1024,66 +759,58 @@ def test_commands(self, bottle_request, check_auth_data, get_infrastructure, SSH vm2.destroy = False inf.vm_list = [inf.vm_master, vm1, vm2] - res = RESTGetVMProperty("1", "0", "command") + res = self.client.get('/infrastructures/1/vms/0/command?step=2', headers=headers) expected_res = "true" - self.assertEqual(res, expected_res) + self.assertEqual(res.text, expected_res) - bottle_request.params = {'step': '2'} - res = RESTGetVMProperty("1", "1", "command") + res = self.client.get('/infrastructures/1/vms/1/command?step=2', headers=headers) expected_res = "true" - self.assertEqual(res, expected_res) + self.assertEqual(res.text, expected_res) # in VM not connected to the Master VM - res = RESTGetVMProperty("1", "2", "command") + res = self.client.get('/infrastructures/1/vms/2/command?step=2', headers=headers) expected_res = ('sshpass -pyoyoyo ssh -N -R 20002:localhost:22 -p 22 -o "UserKnownHostsFile=/dev/null"' ' -o "StrictHostKeyChecking=no" ubuntu@8.8.8.8 &') - self.assertEqual(res, expected_res) + self.assertEqual(res.text, expected_res) - @patch("bottle.request") - def test_GetCloudInfo(self, bottle_request): + def test_GetCloudInfo(self): """Test REST GetCloudInfo.""" - bottle_request.return_value = MagicMock() - bottle_request.headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" - "id = cloud1; type = Dummy; host = http://dummy;")} - bottle_request.environ = {'HTTP_HOST': 'imserver.com'} - - res = RESTGetCloudInfo("cloud1", "images") - self.assertEqual(json.loads(res), {"images": [{"uri": "mock0://linux.for.ev.er/image1", - "name": "Image Name1"}, - {"uri": "mock0://linux.for.ev.er/image2", - "name": "Image Name2"}]}) - - res = RESTGetCloudInfo("cloud1", "quotas") - self.assertEqual(json.loads(res), {"quotas": {"cores": {"used": 1, "limit": 10}, - "ram": {"used": 1, "limit": 10}, - "instances": {"used": 1, "limit": 10}, - "floating_ips": {"used": 1, "limit": 10}, - "security_groups": {"used": 1, "limit": 10}}}) - - @patch("bottle.request") + headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" + "id = cloud1; type = Dummy; host = http://dummy;")} + + res = self.client.get('/clouds/cloud1/images', headers=headers) + self.assertEqual(res.json, {"images": [{"uri": "mock0://linux.for.ev.er/image1", + "name": "Image Name1"}, + {"uri": "mock0://linux.for.ev.er/image2", + "name": "Image Name2"}]}) + + res = self.client.get('/clouds/cloud1/quotas', headers=headers) + self.assertEqual(res.json, {"quotas": {"cores": {"used": 1, "limit": 10}, + "ram": {"used": 1, "limit": 10}, + "instances": {"used": 1, "limit": 10}, + "floating_ips": {"used": 1, "limit": 10}, + "security_groups": {"used": 1, "limit": 10}}}) + @patch("IM.InfrastructureManager.InfrastructureManager.GetCloudImageList") - def test_GetCloudInfo_filters(self, GetCloudImageList, bottle_request): + def test_GetCloudInfo_filters(self, GetCloudImageList): """Test REST GetCloudInfo with filters.""" - bottle_request.return_value = MagicMock() - bottle_request.headers = {"AUTHORIZATION": "type = InfrastructureManager; username = user; password = pass"} + headers = {"AUTHORIZATION": "type = InfrastructureManager; username = user; password = pass"} - bottle_request.params = {'filters': 'region=region_name'} GetCloudImageList.return_value = [] - res = RESTGetCloudInfo("cloud1", "images") - self.assertEqual(json.loads(res), {"images": []}) + res = self.client.get('/clouds/cloud1/images?filters=region=region_name', headers=headers) + self.assertEqual(res.json, {"images": []}) self.assertEqual(GetCloudImageList.call_args_list[0][0][2], {'region': 'region_name'}) - @patch("bottle.request") @patch("IM.InfrastructureManager.InfrastructureManager.ChangeInfrastructureAuth") - def test_ChangeInfrastructureAuth(self, ChangeInfrastructureAuth, bottle_request): + def test_ChangeInfrastructureAuth(self, ChangeInfrastructureAuth): """Test REST ChangeInfrastructureAuth.""" - bottle_request.return_value = MagicMock() - bottle_request.headers = {"AUTHORIZATION": "type = InfrastructureManager; username = user; password = pass"} + headers = {"AUTHORIZATION": "type = InfrastructureManager; username = user; password = pass"} ChangeInfrastructureAuth.return_value = None - bottle_request.body = BytesIO(b'{"username": "new_user", "password": "new_pass"}') - bottle_request.params = {'overwrite': 'yes'} - RESTChangeInfrastructureAuth("infid") + res = self.client.post('/infrastructures/infid/authorization?overwrite=yes', + headers=headers, data=b'{"username": "new_user", "password": "new_pass"}') + self.assertEqual(res.text, "") + self.assertEqual(ChangeInfrastructureAuth.call_args_list[0][0][0], "infid") self.assertEqual(ChangeInfrastructureAuth.call_args_list[0][0][1].auth_list, [{"type": "InfrastructureManager", "username": "new_user", @@ -1091,22 +818,20 @@ def test_ChangeInfrastructureAuth(self, ChangeInfrastructureAuth, bottle_request self.assertEqual(ChangeInfrastructureAuth.call_args_list[0][0][2], True) @patch("IM.InfrastructureManager.InfrastructureManager.GetInfrastructureOwners") - @patch("bottle.request") - def test_GetInfrastructureOwners(self, bottle_request, GetInfrastructureOwners): + def test_GetInfrastructureOwners(self, GetInfrastructureOwners): """Test REST StopInfrastructure.""" - bottle_request.return_value = MagicMock() - bottle_request.headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" - "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " - "username = user; password = pass")} + headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\\n" + "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " + "username = user; password = pass")} GetInfrastructureOwners.return_value = ["user1", "user2"] - res = RESTGetInfrastructureProperty("1", "authorization") - self.assertEqual(res, 'user1\nuser2') + res = self.client.get('/infrastructures/1/authorization', headers=headers) + self.assertEqual(res.text, 'user1\nuser2') - bottle_request.headers["Accept"] = "application/json" - res = RESTGetInfrastructureProperty("1", "authorization") - self.assertEqual(res, '{"authorization": ["user1", "user2"]}') + headers["Accept"] = "application/json" + res = self.client.get('/infrastructures/1/authorization', headers=headers) + self.assertEqual(res.json, {"authorization": ["user1", "user2"]}) if __name__ == "__main__": diff --git a/test/unit/SSH.py b/test/unit/SSH.py index bd4f975a7..3dbe2d608 100644 --- a/test/unit/SSH.py +++ b/test/unit/SSH.py @@ -18,6 +18,10 @@ import unittest import os +import sys + +sys.path.append("..") +sys.path.append(".") from IM.SSHRetry import SSHRetry, SSH from mock import patch, MagicMock